@yasserkhanorg/e2e-agents 0.4.0 → 0.5.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.
package/dist/cli.js CHANGED
@@ -72,7 +72,7 @@ function printUsage() {
72
72
  ' e2e-ai-agents feedback --path <app-root> --feedback-input <json>',
73
73
  ' e2e-ai-agents traceability-capture --path <app-root> --traceability-report <json>',
74
74
  ' e2e-ai-agents traceability-ingest --path <app-root> --traceability-input <json>',
75
- ' e2e-ai-agents analyze --path <app-root> [--tests-root <path>] [--since <ref>] [--generate] [--generate-output <dir>]',
75
+ ' e2e-ai-agents analyze --path <app-root> [--tests-root <path>] [--since <ref>] [--generate] [--generate-output <dir>] [--heal] [--heal-report <json>]',
76
76
  ' e2e-ai-agents llm-health',
77
77
  '',
78
78
  'Options:',
@@ -493,6 +493,15 @@ function parseArgs(argv) {
493
493
  i += 1;
494
494
  continue;
495
495
  }
496
+ if (arg === '--heal') {
497
+ parsed.analyzeHeal = true;
498
+ continue;
499
+ }
500
+ if (arg === '--heal-report' && next) {
501
+ parsed.analyzeHealReport = next;
502
+ i += 1;
503
+ continue;
504
+ }
496
505
  }
497
506
  return parsed;
498
507
  }
@@ -528,6 +537,9 @@ async function main() {
528
537
  if (args.analyzeGenerate) {
529
538
  analyzeStages.push('generation');
530
539
  }
540
+ if (args.analyzeHeal || args.analyzeHealReport) {
541
+ analyzeStages.push('heal');
542
+ }
531
543
  const result = await (0, orchestrator_js_1.runPipeline)({
532
544
  appPath: config.path,
533
545
  testsRoot,
@@ -541,6 +553,17 @@ async function main() {
541
553
  dryRun: args.dryRun,
542
554
  }
543
555
  : undefined,
556
+ heal: (args.analyzeHeal || args.analyzeHealReport)
557
+ ? {
558
+ mcp: args.pipelineMcp ?? true,
559
+ mcpAllowFallback: args.pipelineMcpAllowFallback ?? false,
560
+ mcpOnly: args.pipelineMcpOnly ?? false,
561
+ mcpCommandTimeoutMs: args.pipelineMcpTimeoutMs,
562
+ mcpRetries: args.pipelineMcpRetries ?? 1,
563
+ dryRun: args.dryRun,
564
+ }
565
+ : undefined,
566
+ playwrightReportPath: args.analyzeHealReport,
544
567
  });
545
568
  // eslint-disable-next-line no-console
546
569
  console.log(`Analyze report: ${result.reportPath}`);
@@ -563,6 +586,12 @@ async function main() {
563
586
  console.log(` ${g.mode}: ${g.specPath}`);
564
587
  }
565
588
  }
589
+ if (result.healResult) {
590
+ const healed = result.healResult.summary.results.filter((r) => r.healStatus === 'success').length;
591
+ const healFailed = result.healResult.summary.results.filter((r) => r.healStatus === 'failed').length;
592
+ // eslint-disable-next-line no-console
593
+ console.log(`Analyze heal targets: ${result.healResult.targets.length} (healed=${healed}, failed=${healFailed})`);
594
+ }
566
595
  if (result.warnings.length > 0) {
567
596
  // eslint-disable-next-line no-console
568
597
  console.log(`Analyze warnings: ${result.warnings.join(' | ')}`);
package/dist/esm/cli.js CHANGED
@@ -70,7 +70,7 @@ function printUsage() {
70
70
  ' e2e-ai-agents feedback --path <app-root> --feedback-input <json>',
71
71
  ' e2e-ai-agents traceability-capture --path <app-root> --traceability-report <json>',
72
72
  ' e2e-ai-agents traceability-ingest --path <app-root> --traceability-input <json>',
73
- ' e2e-ai-agents analyze --path <app-root> [--tests-root <path>] [--since <ref>] [--generate] [--generate-output <dir>]',
73
+ ' e2e-ai-agents analyze --path <app-root> [--tests-root <path>] [--since <ref>] [--generate] [--generate-output <dir>] [--heal] [--heal-report <json>]',
74
74
  ' e2e-ai-agents llm-health',
75
75
  '',
76
76
  'Options:',
@@ -491,6 +491,15 @@ function parseArgs(argv) {
491
491
  i += 1;
492
492
  continue;
493
493
  }
494
+ if (arg === '--heal') {
495
+ parsed.analyzeHeal = true;
496
+ continue;
497
+ }
498
+ if (arg === '--heal-report' && next) {
499
+ parsed.analyzeHealReport = next;
500
+ i += 1;
501
+ continue;
502
+ }
494
503
  }
495
504
  return parsed;
496
505
  }
@@ -526,6 +535,9 @@ async function main() {
526
535
  if (args.analyzeGenerate) {
527
536
  analyzeStages.push('generation');
528
537
  }
538
+ if (args.analyzeHeal || args.analyzeHealReport) {
539
+ analyzeStages.push('heal');
540
+ }
529
541
  const result = await runPipeline({
530
542
  appPath: config.path,
531
543
  testsRoot,
@@ -539,6 +551,17 @@ async function main() {
539
551
  dryRun: args.dryRun,
540
552
  }
541
553
  : undefined,
554
+ heal: (args.analyzeHeal || args.analyzeHealReport)
555
+ ? {
556
+ mcp: args.pipelineMcp ?? true,
557
+ mcpAllowFallback: args.pipelineMcpAllowFallback ?? false,
558
+ mcpOnly: args.pipelineMcpOnly ?? false,
559
+ mcpCommandTimeoutMs: args.pipelineMcpTimeoutMs,
560
+ mcpRetries: args.pipelineMcpRetries ?? 1,
561
+ dryRun: args.dryRun,
562
+ }
563
+ : undefined,
564
+ playwrightReportPath: args.analyzeHealReport,
542
565
  });
543
566
  // eslint-disable-next-line no-console
544
567
  console.log(`Analyze report: ${result.reportPath}`);
@@ -561,6 +584,12 @@ async function main() {
561
584
  console.log(` ${g.mode}: ${g.specPath}`);
562
585
  }
563
586
  }
587
+ if (result.healResult) {
588
+ const healed = result.healResult.summary.results.filter((r) => r.healStatus === 'success').length;
589
+ const healFailed = result.healResult.summary.results.filter((r) => r.healStatus === 'failed').length;
590
+ // eslint-disable-next-line no-console
591
+ console.log(`Analyze heal targets: ${result.healResult.targets.length} (healed=${healed}, failed=${healFailed})`);
592
+ }
564
593
  if (result.warnings.length > 0) {
565
594
  // eslint-disable-next-line no-console
566
595
  console.log(`Analyze warnings: ${result.warnings.join(' | ')}`);
package/dist/esm/index.js CHANGED
@@ -18,6 +18,8 @@ export { captureTraceabilityInput } from './agent/traceability_capture.js';
18
18
  export { runPipeline } from './pipeline/orchestrator.js';
19
19
  export { runGenerationStage } from './pipeline/stage3_generation.js';
20
20
  export { buildGenerationPrompt, parseGenerationResponse, detectHallucinatedMethods } from './prompts/generation.js';
21
+ export { runHealStage, healFromReport, resolveHealTargets, renderHealMarkdown } from './pipeline/stage4_heal.js';
22
+ export { buildHealPrompt, buildQualityFixPrompt } from './prompts/heal.js';
21
23
  // Knowledge modules
22
24
  export { loadRouteFamilyManifest, bindFilesToFamilies } from './knowledge/route_families.js';
23
25
  export { buildApiSurface, loadOrBuildApiSurface } from './knowledge/api_surface.js';
@@ -7,6 +7,7 @@ import { preprocess } from './stage0_preprocess.js';
7
7
  import { runImpactStage } from './stage1_impact.js';
8
8
  import { runCoverageStage } from './stage2_coverage.js';
9
9
  import { runGenerationStage } from './stage3_generation.js';
10
+ import { runHealStage, resolveHealTargets, renderHealMarkdown } from './stage4_heal.js';
10
11
  import { buildSummary } from '../validation/output_schema.js';
11
12
  import { computeCannotDetermineRatio } from '../validation/guardrails.js';
12
13
  function createRunId() {
@@ -31,6 +32,7 @@ export async function runPipeline(config) {
31
32
  const allWarnings = [];
32
33
  const stages = config.stages || ['preprocess', 'impact', 'coverage'];
33
34
  let generatedSpecs;
35
+ let healResult;
34
36
  // Step 1: Get changed files
35
37
  const gitResult = getChangedFiles(config.appPath, config.gitSince, {
36
38
  includeUncommitted: config.gitIncludeUncommitted,
@@ -87,6 +89,20 @@ export async function runPipeline(config) {
87
89
  generatedSpecs = generationResult.generated;
88
90
  allWarnings.push(...generationResult.warnings);
89
91
  }
92
+ // Step 6: Heal stage — MCP-backed playwright-test-healer for failing/flaky specs
93
+ if (stages.includes('heal')) {
94
+ const healTargets = resolveHealTargets(config.testsRoot, {
95
+ playwrightReportPath: config.playwrightReportPath,
96
+ generatedSpecs,
97
+ }, decisions);
98
+ if (healTargets.length > 0) {
99
+ healResult = await runHealStage(config.testsRoot, healTargets, config.heal || { mcp: true });
100
+ allWarnings.push(...healResult.warnings);
101
+ }
102
+ else {
103
+ allWarnings.push('Heal stage: no targets found (no failing specs in report, no generated specs).');
104
+ }
105
+ }
90
106
  // Build report
91
107
  const report = {
92
108
  runId,
@@ -101,10 +117,10 @@ export async function runPipeline(config) {
101
117
  generationAgent: stages.includes('generation') ? (config.generation?.provider || 'auto') : undefined,
102
118
  },
103
119
  };
104
- const reportPath = writeReport(config.testsRoot, report);
105
- return { report, reportPath, warnings: allWarnings, generated: generatedSpecs };
120
+ const reportPath = writeReport(config.testsRoot, report, healResult);
121
+ return { report, reportPath, warnings: allWarnings, generated: generatedSpecs, healResult };
106
122
  }
107
- function writeReport(testsRoot, report) {
123
+ function writeReport(testsRoot, report, healResult) {
108
124
  const outputDir = join(testsRoot, '.e2e-ai-agents');
109
125
  if (!existsSync(outputDir)) {
110
126
  mkdirSync(outputDir, { recursive: true });
@@ -112,10 +128,10 @@ function writeReport(testsRoot, report) {
112
128
  const jsonPath = join(outputDir, 'pipeline-report.json');
113
129
  writeFileSync(jsonPath, JSON.stringify(report, null, 2), 'utf-8');
114
130
  const mdPath = join(outputDir, 'pipeline-report.md');
115
- writeFileSync(mdPath, renderMarkdown(report), 'utf-8');
131
+ writeFileSync(mdPath, renderMarkdown(report, healResult), 'utf-8');
116
132
  return jsonPath;
117
133
  }
118
- function renderMarkdown(report) {
134
+ function renderMarkdown(report, healResult) {
119
135
  const lines = [
120
136
  `# Impact Analysis Pipeline Report`,
121
137
  '',
@@ -188,6 +204,10 @@ function renderMarkdown(report) {
188
204
  lines.push('');
189
205
  }
190
206
  }
207
+ if (healResult) {
208
+ lines.push('');
209
+ lines.push(renderHealMarkdown(healResult));
210
+ }
191
211
  if (report.warnings.length > 0) {
192
212
  lines.push('## Warnings', '');
193
213
  for (const w of report.warnings) {
@@ -0,0 +1,145 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ import { existsSync } from 'fs';
4
+ import { join, resolve } from 'path';
5
+ import { runTargetedSpecHeal } from '../agent/pipeline.js';
6
+ import { extractPlaywrightUnstableSpecs } from '../agent/playwright_report.js';
7
+ /**
8
+ * Resolve heal targets from one or more sources, in priority order:
9
+ * 1. Playwright JSON report (CI failures/flakes)
10
+ * 2. Stage 3 generated specs (newly written files that need runtime validation)
11
+ * 3. Explicit target list
12
+ */
13
+ export function resolveHealTargets(testsRoot, options, decisions) {
14
+ const targets = [];
15
+ const seen = new Set();
16
+ const addTarget = (specPath, status, reason) => {
17
+ // Normalize to absolute path so relative (from Playwright report) and absolute
18
+ // (from generated specs) deduplicate correctly
19
+ let normalized = specPath.replace(/\\/g, '/');
20
+ if (!specPath.startsWith('/') && !(/^[A-Za-z]:[\\/]/).test(specPath)) {
21
+ normalized = join(testsRoot, specPath).replace(/\\/g, '/');
22
+ }
23
+ if (seen.has(normalized)) {
24
+ return;
25
+ }
26
+ seen.add(normalized);
27
+ // Try to match to a FlowDecision for context
28
+ const decision = findDecisionForSpec(normalized, decisions, testsRoot);
29
+ targets.push({ specPath: normalized, status, decision, reason });
30
+ };
31
+ // Source 1: Playwright JSON report
32
+ if (options.playwrightReportPath) {
33
+ const reportPath = resolve(options.playwrightReportPath);
34
+ if (existsSync(reportPath)) {
35
+ const unstable = extractPlaywrightUnstableSpecs(reportPath, [testsRoot]);
36
+ for (const spec of unstable) {
37
+ addTarget(spec.specPath, spec.status, `Playwright report: failingTests=${spec.failingTests}, flakyTests=${spec.flakyTests}`);
38
+ }
39
+ }
40
+ }
41
+ // Source 2: Stage 3 generated specs (heal immediately after generation)
42
+ if (options.generatedSpecs) {
43
+ for (const gen of options.generatedSpecs) {
44
+ if (gen.written) {
45
+ addTarget(gen.specPath, 'failed', `Newly generated spec — needs runtime validation`);
46
+ }
47
+ }
48
+ }
49
+ // Source 3: Explicit targets
50
+ if (options.explicitTargets) {
51
+ for (const t of options.explicitTargets) {
52
+ addTarget(t.specPath, t.status, t.reason);
53
+ }
54
+ }
55
+ return targets;
56
+ }
57
+ /**
58
+ * Find the FlowDecision most relevant to a given spec path by matching
59
+ * targetSpec / newSpecPath / specPath suffix against decisions.
60
+ */
61
+ function findDecisionForSpec(specPath, decisions, testsRoot) {
62
+ const normalizedRoot = testsRoot.replace(/\\/g, '/');
63
+ const relative = specPath.startsWith(normalizedRoot)
64
+ ? specPath.slice(normalizedRoot.length).replace(/^\//, '')
65
+ : specPath;
66
+ return decisions.find((d) => {
67
+ const target = (d.targetSpec || d.newSpecPath || '').replace(/\\/g, '/');
68
+ return target && (target === relative || target === specPath || relative.endsWith(target) || target.endsWith(relative));
69
+ });
70
+ }
71
+ export async function runHealStage(testsRoot, targets, config) {
72
+ const warnings = [];
73
+ if (targets.length === 0) {
74
+ return {
75
+ targets,
76
+ summary: {
77
+ runner: 'package-native',
78
+ results: [],
79
+ warnings: ['No heal targets provided.'],
80
+ },
81
+ warnings,
82
+ };
83
+ }
84
+ const healTargets = targets.map((t) => ({
85
+ specPath: t.specPath,
86
+ status: t.status,
87
+ reason: t.reason,
88
+ }));
89
+ const pipelineConfig = {
90
+ enabled: true,
91
+ scenarios: 1,
92
+ outputDir: config.outputDir || 'specs/functional/ai-assisted',
93
+ heal: true,
94
+ dryRun: config.dryRun,
95
+ mcp: config.mcp ?? true,
96
+ mcpAllowFallback: config.mcpAllowFallback ?? false,
97
+ mcpOnly: config.mcpOnly ?? false,
98
+ mcpCommandTimeoutMs: config.mcpCommandTimeoutMs,
99
+ mcpRetries: config.mcpRetries ?? 1,
100
+ };
101
+ const summary = runTargetedSpecHeal(testsRoot, healTargets, pipelineConfig);
102
+ warnings.push(...summary.warnings);
103
+ return { targets, summary, warnings };
104
+ }
105
+ /**
106
+ * Convenience: extract heal targets from a complete pipeline report + optional
107
+ * Playwright run results, then run the heal stage.
108
+ */
109
+ export async function healFromReport(testsRoot, report, options) {
110
+ const targets = resolveHealTargets(testsRoot, {
111
+ playwrightReportPath: options.playwrightReportPath,
112
+ generatedSpecs: options.generatedSpecs,
113
+ }, report.decisions);
114
+ return runHealStage(testsRoot, targets, options.healConfig || { mcp: true });
115
+ }
116
+ /**
117
+ * Write a heal summary section to the pipeline report markdown.
118
+ */
119
+ export function renderHealMarkdown(result) {
120
+ const lines = ['## Heal Results', ''];
121
+ const healedCount = result.summary.results.filter((r) => r.healStatus === 'success').length;
122
+ const failedCount = result.summary.results.filter((r) => r.healStatus === 'failed').length;
123
+ const skippedCount = result.summary.results.filter((r) => r.healStatus === 'skipped').length;
124
+ lines.push(`| Metric | Value |`);
125
+ lines.push(`|--------|-------|`);
126
+ lines.push(`| Targets | ${result.targets.length} |`);
127
+ lines.push(`| Healed | ${healedCount} |`);
128
+ lines.push(`| Failed | ${failedCount} |`);
129
+ lines.push(`| Skipped | ${skippedCount} |`);
130
+ lines.push('');
131
+ for (const r of result.summary.results) {
132
+ const icon = r.healStatus === 'success' ? '✅' : r.healStatus === 'failed' ? '❌' : '⏭';
133
+ lines.push(`- ${icon} \`${r.flowId}\` — heal: ${r.healStatus || 'n/a'}`);
134
+ if (r.error) {
135
+ lines.push(` - Error: ${r.error}`);
136
+ }
137
+ }
138
+ if (result.warnings.length > 0) {
139
+ lines.push('', '### Heal Warnings', '');
140
+ for (const w of result.warnings) {
141
+ lines.push(`- ${w}`);
142
+ }
143
+ }
144
+ return lines.join('\n');
145
+ }
@@ -0,0 +1,69 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ /**
4
+ * Builds a route-family-aware heal prompt for the playwright-test-healer agent.
5
+ * Enriches the base healer constraints with flow context so the agent understands
6
+ * what the test is supposed to verify, reducing hallucination during selector repair.
7
+ */
8
+ export function buildHealPrompt(ctx) {
9
+ const flowBlock = ctx.decision
10
+ ? [
11
+ '',
12
+ 'FLOW CONTEXT (use to understand test intent — do not change test objectives):',
13
+ ` Flow: ${ctx.decision.flowName}`,
14
+ ` Route Family: ${ctx.decision.routeFamily}${ctx.decision.featureId ? ` / ${ctx.decision.featureId}` : ''}`,
15
+ ` Route: ${ctx.decision.specificRoute || '(family-level)'}`,
16
+ ` User Actions: ${ctx.decision.userActions.join('; ') || 'not specified'}`,
17
+ ` Evidence: ${ctx.decision.evidence}`,
18
+ ].join('\n')
19
+ : '';
20
+ const statusNote = ctx.status === 'flaky'
21
+ ? 'This test is FLAKY (passes sometimes, fails other times). Look for race conditions, missing waits, or order-dependent state.'
22
+ : 'This test is FAILING consistently. The selector, URL, or API call is likely broken.';
23
+ const failureBlock = ctx.failureDetail
24
+ ? `\nFailure detail:\n${ctx.failureDetail}`
25
+ : '';
26
+ return [
27
+ 'Heal this specific Playwright test file and keep edits minimal.',
28
+ '',
29
+ `Target test file: ${ctx.specPath}`,
30
+ `Status: ${ctx.status.toUpperCase()} — ${statusNote}`,
31
+ failureBlock,
32
+ flowBlock,
33
+ '',
34
+ 'Healing constraints (must follow):',
35
+ '- Import ONLY from "@mattermost/playwright-lib". Do not use "@playwright/test" directly.',
36
+ '- Do not use test.describe or test.only.',
37
+ '- Keep a single tag string matching the route family (e.g. "@channels", "@scheduled_posts").',
38
+ '- Use only existing Mattermost Playwright fixture and page-object APIs.',
39
+ '- Do NOT invent new pw.* clients or page object methods that do not exist.',
40
+ '- Avoid brittle class selectors (.backstage-navbar, .admin-console__wrapper, .left-panel, .panel-card).',
41
+ '- Prefer stable assertions using URL patterns, data-testid attributes, ARIA roles, and page-object methods.',
42
+ '- For flaky tests: add explicit waits (waitFor, expect().toBeVisible()) before interactions.',
43
+ '- Keep the test intent and scenario unchanged — only fix what is broken.',
44
+ '- If behavior is genuinely broken server-side, mark test.fixme with a clear comment explaining why.',
45
+ '',
46
+ 'Run and fix this test until it compiles and passes, or mark test.fixme when the behavior is truly broken.',
47
+ ].filter((line) => line !== null).join('\n');
48
+ }
49
+ /**
50
+ * Builds a minimal quality-fix prompt for spec files that fail content validation
51
+ * (e.g. contain test.describe, test.only, wrong imports).
52
+ */
53
+ export function buildQualityFixPrompt(specPath, qualityIssues) {
54
+ return [
55
+ 'Fix quality issues in this Playwright spec file. Make minimal edits only.',
56
+ '',
57
+ `Target file: ${specPath}`,
58
+ '',
59
+ 'Issues to fix:',
60
+ ...qualityIssues.map((issue) => ` - ${issue}`),
61
+ '',
62
+ 'Rules:',
63
+ '- Import only from "@mattermost/playwright-lib".',
64
+ '- Remove test.describe wrappers (flatten to top-level test() calls).',
65
+ '- Remove test.only calls.',
66
+ '- Ensure each test has exactly one tag string.',
67
+ '- Do not change test logic — only fix structural quality issues.',
68
+ ].join('\n');
69
+ }
package/dist/index.d.ts CHANGED
@@ -35,6 +35,10 @@ export { runGenerationStage } from './pipeline/stage3_generation.js';
35
35
  export type { GenerationConfig, GenerationResult, GeneratedSpec } from './pipeline/stage3_generation.js';
36
36
  export { buildGenerationPrompt, parseGenerationResponse, detectHallucinatedMethods } from './prompts/generation.js';
37
37
  export type { GenerationPromptContext, GenerationAgentResponse } from './prompts/generation.js';
38
+ export { runHealStage, healFromReport, resolveHealTargets, renderHealMarkdown } from './pipeline/stage4_heal.js';
39
+ export type { HealConfig, HealTarget, HealResult } from './pipeline/stage4_heal.js';
40
+ export { buildHealPrompt, buildQualityFixPrompt } from './prompts/heal.js';
41
+ export type { HealPromptContext } from './prompts/heal.js';
38
42
  export { loadRouteFamilyManifest, bindFilesToFamilies } from './knowledge/route_families.js';
39
43
  export type { RouteFamily, RouteFeature, RouteFamilyManifest, FileBinding } from './knowledge/route_families.js';
40
44
  export { buildApiSurface, loadOrBuildApiSurface } from './knowledge/api_surface.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAGH,YAAY,EACR,WAAW,EACX,eAAe,EACf,UAAU,EACV,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,YAAY,GACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,gBAAgB,EAAE,0BAA0B,EAAC,MAAM,yBAAyB,CAAC;AAGrF,OAAO,EAAC,iBAAiB,EAAE,mBAAmB,EAAC,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAC,cAAc,EAAE,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAC,cAAc,EAAE,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAC,cAAc,EAAC,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAC,kBAAkB,EAAE,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAChF,YAAY,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAC,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,mBAAmB,EAAC,MAAM,UAAU,CAAC;AACjI,YAAY,EACR,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,4BAA4B,EAC5B,6BAA6B,GAChC,MAAM,UAAU,CAAC;AAClB,OAAO,EAAC,0BAA0B,EAAE,eAAe,EAAC,MAAM,qBAAqB,CAAC;AAChF,YAAY,EAAC,2BAA2B,EAAE,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAC,sBAAsB,EAAC,MAAM,oBAAoB,CAAC;AAC1D,YAAY,EAAC,6BAA6B,EAAE,4BAA4B,EAAC,MAAM,oBAAoB,CAAC;AACpG,OAAO,EAAC,uBAAuB,EAAC,MAAM,gCAAgC,CAAC;AACvE,YAAY,EAAC,yBAAyB,EAAE,wBAAwB,EAAE,uBAAuB,EAAC,MAAM,gCAAgC,CAAC;AACjI,OAAO,EAAC,wBAAwB,EAAC,MAAM,iCAAiC,CAAC;AACzE,YAAY,EAAC,0BAA0B,EAAE,yBAAyB,EAAC,MAAM,iCAAiC,CAAC;AAG3G,OAAO,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAC;AACvD,YAAY,EAAC,cAAc,EAAE,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAC/E,YAAY,EAAC,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,UAAU,EAAE,cAAc,EAAC,MAAM,+BAA+B,CAAC;AACrI,OAAO,EAAC,kBAAkB,EAAC,MAAM,iCAAiC,CAAC;AACnE,YAAY,EAAC,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAC,MAAM,iCAAiC,CAAC;AACvG,OAAO,EAAC,qBAAqB,EAAE,uBAAuB,EAAE,yBAAyB,EAAC,MAAM,yBAAyB,CAAC;AAClH,YAAY,EAAC,uBAAuB,EAAE,uBAAuB,EAAC,MAAM,yBAAyB,CAAC;AAG9F,OAAO,EAAC,uBAAuB,EAAE,mBAAmB,EAAC,MAAM,+BAA+B,CAAC;AAC3F,YAAY,EAAC,WAAW,EAAE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAC,MAAM,+BAA+B,CAAC;AAC/G,OAAO,EAAC,eAAe,EAAE,qBAAqB,EAAC,MAAM,4BAA4B,CAAC;AAClF,YAAY,EAAC,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAC,cAAc,EAAE,iBAAiB,EAAC,MAAM,2BAA2B,CAAC;AAC5E,YAAY,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
3
  // See LICENSE.txt for license information.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.getSpecsForFamily = exports.buildSpecIndex = exports.loadOrBuildApiSurface = exports.buildApiSurface = exports.bindFilesToFamilies = exports.loadRouteFamilyManifest = exports.detectHallucinatedMethods = exports.parseGenerationResponse = exports.buildGenerationPrompt = exports.runGenerationStage = exports.runPipeline = exports.captureTraceabilityInput = exports.ingestTraceabilityInput = exports.finalizeGeneratedTests = exports.readCalibration = exports.appendFeedbackAndRecompute = exports.captureTraceability = exports.ingestTraceability = exports.handoffGeneratedTests = exports.recommendTests = exports.findGaps = exports.analyzeImpact = exports.validateProviderSetup = exports.LLMProviderFactory = exports.CustomProvider = exports.checkOpenAISetup = exports.OpenAIProvider = exports.checkOllamaSetup = exports.OllamaProvider = exports.checkAnthropicSetup = exports.AnthropicProvider = exports.UnsupportedCapabilityError = exports.LLMProviderError = void 0;
5
+ exports.getSpecsForFamily = exports.buildSpecIndex = exports.loadOrBuildApiSurface = exports.buildApiSurface = exports.bindFilesToFamilies = exports.loadRouteFamilyManifest = exports.buildQualityFixPrompt = exports.buildHealPrompt = exports.renderHealMarkdown = exports.resolveHealTargets = exports.healFromReport = exports.runHealStage = exports.detectHallucinatedMethods = exports.parseGenerationResponse = exports.buildGenerationPrompt = exports.runGenerationStage = exports.runPipeline = exports.captureTraceabilityInput = exports.ingestTraceabilityInput = exports.finalizeGeneratedTests = exports.readCalibration = exports.appendFeedbackAndRecompute = exports.captureTraceability = exports.ingestTraceability = exports.handoffGeneratedTests = exports.recommendTests = exports.findGaps = exports.analyzeImpact = exports.validateProviderSetup = exports.LLMProviderFactory = exports.CustomProvider = exports.checkOpenAISetup = exports.OpenAIProvider = exports.checkOllamaSetup = exports.OllamaProvider = exports.checkAnthropicSetup = exports.AnthropicProvider = exports.UnsupportedCapabilityError = exports.LLMProviderError = void 0;
6
6
  var provider_interface_js_1 = require("./provider_interface.js");
7
7
  Object.defineProperty(exports, "LLMProviderError", { enumerable: true, get: function () { return provider_interface_js_1.LLMProviderError; } });
8
8
  Object.defineProperty(exports, "UnsupportedCapabilityError", { enumerable: true, get: function () { return provider_interface_js_1.UnsupportedCapabilityError; } });
@@ -48,6 +48,14 @@ var generation_js_1 = require("./prompts/generation.js");
48
48
  Object.defineProperty(exports, "buildGenerationPrompt", { enumerable: true, get: function () { return generation_js_1.buildGenerationPrompt; } });
49
49
  Object.defineProperty(exports, "parseGenerationResponse", { enumerable: true, get: function () { return generation_js_1.parseGenerationResponse; } });
50
50
  Object.defineProperty(exports, "detectHallucinatedMethods", { enumerable: true, get: function () { return generation_js_1.detectHallucinatedMethods; } });
51
+ var stage4_heal_js_1 = require("./pipeline/stage4_heal.js");
52
+ Object.defineProperty(exports, "runHealStage", { enumerable: true, get: function () { return stage4_heal_js_1.runHealStage; } });
53
+ Object.defineProperty(exports, "healFromReport", { enumerable: true, get: function () { return stage4_heal_js_1.healFromReport; } });
54
+ Object.defineProperty(exports, "resolveHealTargets", { enumerable: true, get: function () { return stage4_heal_js_1.resolveHealTargets; } });
55
+ Object.defineProperty(exports, "renderHealMarkdown", { enumerable: true, get: function () { return stage4_heal_js_1.renderHealMarkdown; } });
56
+ var heal_js_1 = require("./prompts/heal.js");
57
+ Object.defineProperty(exports, "buildHealPrompt", { enumerable: true, get: function () { return heal_js_1.buildHealPrompt; } });
58
+ Object.defineProperty(exports, "buildQualityFixPrompt", { enumerable: true, get: function () { return heal_js_1.buildQualityFixPrompt; } });
51
59
  // Knowledge modules
52
60
  var route_families_js_1 = require("./knowledge/route_families.js");
53
61
  Object.defineProperty(exports, "loadRouteFamilyManifest", { enumerable: true, get: function () { return route_families_js_1.loadRouteFamilyManifest; } });
@@ -1,6 +1,7 @@
1
1
  import { type ImpactConfig } from './stage1_impact.js';
2
2
  import { type CoverageConfig } from './stage2_coverage.js';
3
3
  import { type GenerationConfig, type GeneratedSpec } from './stage3_generation.js';
4
+ import { type HealConfig, type HealResult } from './stage4_heal.js';
4
5
  import { type FlowDecisionReport } from '../validation/output_schema.js';
5
6
  import type { RouteFamilyConfig } from '../knowledge/route_families.js';
6
7
  import type { ApiSurfaceConfig } from '../knowledge/api_surface.js';
@@ -14,13 +15,17 @@ export interface PipelineConfig {
14
15
  impact?: ImpactConfig;
15
16
  coverage?: CoverageConfig;
16
17
  generation?: GenerationConfig;
17
- stages?: Array<'preprocess' | 'impact' | 'coverage' | 'generation'>;
18
+ heal?: HealConfig;
19
+ /** Path to a Playwright JSON report for heal-from-report mode */
20
+ playwrightReportPath?: string;
21
+ stages?: Array<'preprocess' | 'impact' | 'coverage' | 'generation' | 'heal'>;
18
22
  }
19
23
  export interface PipelineResult {
20
24
  report: FlowDecisionReport;
21
25
  reportPath: string;
22
26
  warnings: string[];
23
27
  generated?: GeneratedSpec[];
28
+ healResult?: HealResult;
24
29
  }
25
30
  export declare function runPipeline(config: PipelineConfig): Promise<PipelineResult>;
26
31
  //# sourceMappingURL=orchestrator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/pipeline/orchestrator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAiB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAmB,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAqB,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAC,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAe,KAAK,kBAAkB,EAAoB,MAAM,gCAAgC,CAAC;AAExG,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AAElE,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;CAC/B;AAoBD,wBAAsB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA6GjF"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/pipeline/orchestrator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAiB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAmB,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAqB,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAC,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAuD,KAAK,UAAU,EAAE,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACxH,OAAO,EAAe,KAAK,kBAAkB,EAAoB,MAAM,gCAAgC,CAAC;AAExG,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AAElE,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAoBD,wBAAsB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAgIjF"}
@@ -10,6 +10,7 @@ const stage0_preprocess_js_1 = require("./stage0_preprocess.js");
10
10
  const stage1_impact_js_1 = require("./stage1_impact.js");
11
11
  const stage2_coverage_js_1 = require("./stage2_coverage.js");
12
12
  const stage3_generation_js_1 = require("./stage3_generation.js");
13
+ const stage4_heal_js_1 = require("./stage4_heal.js");
13
14
  const output_schema_js_1 = require("../validation/output_schema.js");
14
15
  const guardrails_js_1 = require("../validation/guardrails.js");
15
16
  function createRunId() {
@@ -34,6 +35,7 @@ async function runPipeline(config) {
34
35
  const allWarnings = [];
35
36
  const stages = config.stages || ['preprocess', 'impact', 'coverage'];
36
37
  let generatedSpecs;
38
+ let healResult;
37
39
  // Step 1: Get changed files
38
40
  const gitResult = (0, git_js_1.getChangedFiles)(config.appPath, config.gitSince, {
39
41
  includeUncommitted: config.gitIncludeUncommitted,
@@ -90,6 +92,20 @@ async function runPipeline(config) {
90
92
  generatedSpecs = generationResult.generated;
91
93
  allWarnings.push(...generationResult.warnings);
92
94
  }
95
+ // Step 6: Heal stage — MCP-backed playwright-test-healer for failing/flaky specs
96
+ if (stages.includes('heal')) {
97
+ const healTargets = (0, stage4_heal_js_1.resolveHealTargets)(config.testsRoot, {
98
+ playwrightReportPath: config.playwrightReportPath,
99
+ generatedSpecs,
100
+ }, decisions);
101
+ if (healTargets.length > 0) {
102
+ healResult = await (0, stage4_heal_js_1.runHealStage)(config.testsRoot, healTargets, config.heal || { mcp: true });
103
+ allWarnings.push(...healResult.warnings);
104
+ }
105
+ else {
106
+ allWarnings.push('Heal stage: no targets found (no failing specs in report, no generated specs).');
107
+ }
108
+ }
93
109
  // Build report
94
110
  const report = {
95
111
  runId,
@@ -104,10 +120,10 @@ async function runPipeline(config) {
104
120
  generationAgent: stages.includes('generation') ? (config.generation?.provider || 'auto') : undefined,
105
121
  },
106
122
  };
107
- const reportPath = writeReport(config.testsRoot, report);
108
- return { report, reportPath, warnings: allWarnings, generated: generatedSpecs };
123
+ const reportPath = writeReport(config.testsRoot, report, healResult);
124
+ return { report, reportPath, warnings: allWarnings, generated: generatedSpecs, healResult };
109
125
  }
110
- function writeReport(testsRoot, report) {
126
+ function writeReport(testsRoot, report, healResult) {
111
127
  const outputDir = (0, path_1.join)(testsRoot, '.e2e-ai-agents');
112
128
  if (!(0, fs_1.existsSync)(outputDir)) {
113
129
  (0, fs_1.mkdirSync)(outputDir, { recursive: true });
@@ -115,10 +131,10 @@ function writeReport(testsRoot, report) {
115
131
  const jsonPath = (0, path_1.join)(outputDir, 'pipeline-report.json');
116
132
  (0, fs_1.writeFileSync)(jsonPath, JSON.stringify(report, null, 2), 'utf-8');
117
133
  const mdPath = (0, path_1.join)(outputDir, 'pipeline-report.md');
118
- (0, fs_1.writeFileSync)(mdPath, renderMarkdown(report), 'utf-8');
134
+ (0, fs_1.writeFileSync)(mdPath, renderMarkdown(report, healResult), 'utf-8');
119
135
  return jsonPath;
120
136
  }
121
- function renderMarkdown(report) {
137
+ function renderMarkdown(report, healResult) {
122
138
  const lines = [
123
139
  `# Impact Analysis Pipeline Report`,
124
140
  '',
@@ -191,6 +207,10 @@ function renderMarkdown(report) {
191
207
  lines.push('');
192
208
  }
193
209
  }
210
+ if (healResult) {
211
+ lines.push('');
212
+ lines.push((0, stage4_heal_js_1.renderHealMarkdown)(healResult));
213
+ }
194
214
  if (report.warnings.length > 0) {
195
215
  lines.push('## Warnings', '');
196
216
  for (const w of report.warnings) {
@@ -0,0 +1,56 @@
1
+ import type { PipelineSummary } from '../agent/pipeline.js';
2
+ import type { FlowDecision, FlowDecisionReport } from '../validation/output_schema.js';
3
+ import type { GeneratedSpec } from './stage3_generation.js';
4
+ export interface HealConfig {
5
+ /** Enable MCP-backed heal via playwright-test-healer agent */
6
+ mcp?: boolean;
7
+ mcpAllowFallback?: boolean;
8
+ mcpOnly?: boolean;
9
+ mcpCommandTimeoutMs?: number;
10
+ mcpRetries?: number;
11
+ dryRun?: boolean;
12
+ /** Output directory for healed/re-generated specs */
13
+ outputDir?: string;
14
+ }
15
+ export interface HealTarget {
16
+ specPath: string;
17
+ status: 'failed' | 'flaky';
18
+ /** Matching FlowDecision for context-enriched prompts */
19
+ decision?: FlowDecision;
20
+ reason?: string;
21
+ }
22
+ export interface HealResult {
23
+ targets: HealTarget[];
24
+ summary: PipelineSummary;
25
+ warnings: string[];
26
+ }
27
+ /**
28
+ * Resolve heal targets from one or more sources, in priority order:
29
+ * 1. Playwright JSON report (CI failures/flakes)
30
+ * 2. Stage 3 generated specs (newly written files that need runtime validation)
31
+ * 3. Explicit target list
32
+ */
33
+ export declare function resolveHealTargets(testsRoot: string, options: {
34
+ playwrightReportPath?: string;
35
+ generatedSpecs?: GeneratedSpec[];
36
+ explicitTargets?: Array<{
37
+ specPath: string;
38
+ status: 'failed' | 'flaky';
39
+ reason?: string;
40
+ }>;
41
+ }, decisions: FlowDecision[]): HealTarget[];
42
+ export declare function runHealStage(testsRoot: string, targets: HealTarget[], config: HealConfig): Promise<HealResult>;
43
+ /**
44
+ * Convenience: extract heal targets from a complete pipeline report + optional
45
+ * Playwright run results, then run the heal stage.
46
+ */
47
+ export declare function healFromReport(testsRoot: string, report: FlowDecisionReport, options: {
48
+ playwrightReportPath?: string;
49
+ generatedSpecs?: GeneratedSpec[];
50
+ healConfig?: HealConfig;
51
+ }): Promise<HealResult>;
52
+ /**
53
+ * Write a heal summary section to the pipeline report markdown.
54
+ */
55
+ export declare function renderHealMarkdown(result: HealResult): string;
56
+ //# sourceMappingURL=stage4_heal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stage4_heal.d.ts","sourceRoot":"","sources":["../../src/pipeline/stage4_heal.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAiB,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAE1E,OAAO,KAAK,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,gCAAgC,CAAC;AACrF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAE1D,MAAM,WAAW,UAAU;IACvB,8DAA8D;IAC9D,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IACL,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,eAAe,CAAC,EAAE,KAAK,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CAC5F,EACD,SAAS,EAAE,YAAY,EAAE,GAC1B,UAAU,EAAE,CAqDd;AAsBD,wBAAsB,YAAY,CAC9B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,UAAU,EAAE,EACrB,MAAM,EAAE,UAAU,GACnB,OAAO,CAAC,UAAU,CAAC,CAsCrB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IACL,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B,GACF,OAAO,CAAC,UAAU,CAAC,CAWrB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CA8B7D"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
+ // See LICENSE.txt for license information.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.resolveHealTargets = resolveHealTargets;
6
+ exports.runHealStage = runHealStage;
7
+ exports.healFromReport = healFromReport;
8
+ exports.renderHealMarkdown = renderHealMarkdown;
9
+ const fs_1 = require("fs");
10
+ const path_1 = require("path");
11
+ const pipeline_js_1 = require("../agent/pipeline.js");
12
+ const playwright_report_js_1 = require("../agent/playwright_report.js");
13
+ /**
14
+ * Resolve heal targets from one or more sources, in priority order:
15
+ * 1. Playwright JSON report (CI failures/flakes)
16
+ * 2. Stage 3 generated specs (newly written files that need runtime validation)
17
+ * 3. Explicit target list
18
+ */
19
+ function resolveHealTargets(testsRoot, options, decisions) {
20
+ const targets = [];
21
+ const seen = new Set();
22
+ const addTarget = (specPath, status, reason) => {
23
+ // Normalize to absolute path so relative (from Playwright report) and absolute
24
+ // (from generated specs) deduplicate correctly
25
+ let normalized = specPath.replace(/\\/g, '/');
26
+ if (!specPath.startsWith('/') && !(/^[A-Za-z]:[\\/]/).test(specPath)) {
27
+ normalized = (0, path_1.join)(testsRoot, specPath).replace(/\\/g, '/');
28
+ }
29
+ if (seen.has(normalized)) {
30
+ return;
31
+ }
32
+ seen.add(normalized);
33
+ // Try to match to a FlowDecision for context
34
+ const decision = findDecisionForSpec(normalized, decisions, testsRoot);
35
+ targets.push({ specPath: normalized, status, decision, reason });
36
+ };
37
+ // Source 1: Playwright JSON report
38
+ if (options.playwrightReportPath) {
39
+ const reportPath = (0, path_1.resolve)(options.playwrightReportPath);
40
+ if ((0, fs_1.existsSync)(reportPath)) {
41
+ const unstable = (0, playwright_report_js_1.extractPlaywrightUnstableSpecs)(reportPath, [testsRoot]);
42
+ for (const spec of unstable) {
43
+ addTarget(spec.specPath, spec.status, `Playwright report: failingTests=${spec.failingTests}, flakyTests=${spec.flakyTests}`);
44
+ }
45
+ }
46
+ }
47
+ // Source 2: Stage 3 generated specs (heal immediately after generation)
48
+ if (options.generatedSpecs) {
49
+ for (const gen of options.generatedSpecs) {
50
+ if (gen.written) {
51
+ addTarget(gen.specPath, 'failed', `Newly generated spec — needs runtime validation`);
52
+ }
53
+ }
54
+ }
55
+ // Source 3: Explicit targets
56
+ if (options.explicitTargets) {
57
+ for (const t of options.explicitTargets) {
58
+ addTarget(t.specPath, t.status, t.reason);
59
+ }
60
+ }
61
+ return targets;
62
+ }
63
+ /**
64
+ * Find the FlowDecision most relevant to a given spec path by matching
65
+ * targetSpec / newSpecPath / specPath suffix against decisions.
66
+ */
67
+ function findDecisionForSpec(specPath, decisions, testsRoot) {
68
+ const normalizedRoot = testsRoot.replace(/\\/g, '/');
69
+ const relative = specPath.startsWith(normalizedRoot)
70
+ ? specPath.slice(normalizedRoot.length).replace(/^\//, '')
71
+ : specPath;
72
+ return decisions.find((d) => {
73
+ const target = (d.targetSpec || d.newSpecPath || '').replace(/\\/g, '/');
74
+ return target && (target === relative || target === specPath || relative.endsWith(target) || target.endsWith(relative));
75
+ });
76
+ }
77
+ async function runHealStage(testsRoot, targets, config) {
78
+ const warnings = [];
79
+ if (targets.length === 0) {
80
+ return {
81
+ targets,
82
+ summary: {
83
+ runner: 'package-native',
84
+ results: [],
85
+ warnings: ['No heal targets provided.'],
86
+ },
87
+ warnings,
88
+ };
89
+ }
90
+ const healTargets = targets.map((t) => ({
91
+ specPath: t.specPath,
92
+ status: t.status,
93
+ reason: t.reason,
94
+ }));
95
+ const pipelineConfig = {
96
+ enabled: true,
97
+ scenarios: 1,
98
+ outputDir: config.outputDir || 'specs/functional/ai-assisted',
99
+ heal: true,
100
+ dryRun: config.dryRun,
101
+ mcp: config.mcp ?? true,
102
+ mcpAllowFallback: config.mcpAllowFallback ?? false,
103
+ mcpOnly: config.mcpOnly ?? false,
104
+ mcpCommandTimeoutMs: config.mcpCommandTimeoutMs,
105
+ mcpRetries: config.mcpRetries ?? 1,
106
+ };
107
+ const summary = (0, pipeline_js_1.runTargetedSpecHeal)(testsRoot, healTargets, pipelineConfig);
108
+ warnings.push(...summary.warnings);
109
+ return { targets, summary, warnings };
110
+ }
111
+ /**
112
+ * Convenience: extract heal targets from a complete pipeline report + optional
113
+ * Playwright run results, then run the heal stage.
114
+ */
115
+ async function healFromReport(testsRoot, report, options) {
116
+ const targets = resolveHealTargets(testsRoot, {
117
+ playwrightReportPath: options.playwrightReportPath,
118
+ generatedSpecs: options.generatedSpecs,
119
+ }, report.decisions);
120
+ return runHealStage(testsRoot, targets, options.healConfig || { mcp: true });
121
+ }
122
+ /**
123
+ * Write a heal summary section to the pipeline report markdown.
124
+ */
125
+ function renderHealMarkdown(result) {
126
+ const lines = ['## Heal Results', ''];
127
+ const healedCount = result.summary.results.filter((r) => r.healStatus === 'success').length;
128
+ const failedCount = result.summary.results.filter((r) => r.healStatus === 'failed').length;
129
+ const skippedCount = result.summary.results.filter((r) => r.healStatus === 'skipped').length;
130
+ lines.push(`| Metric | Value |`);
131
+ lines.push(`|--------|-------|`);
132
+ lines.push(`| Targets | ${result.targets.length} |`);
133
+ lines.push(`| Healed | ${healedCount} |`);
134
+ lines.push(`| Failed | ${failedCount} |`);
135
+ lines.push(`| Skipped | ${skippedCount} |`);
136
+ lines.push('');
137
+ for (const r of result.summary.results) {
138
+ const icon = r.healStatus === 'success' ? '✅' : r.healStatus === 'failed' ? '❌' : '⏭';
139
+ lines.push(`- ${icon} \`${r.flowId}\` — heal: ${r.healStatus || 'n/a'}`);
140
+ if (r.error) {
141
+ lines.push(` - Error: ${r.error}`);
142
+ }
143
+ }
144
+ if (result.warnings.length > 0) {
145
+ lines.push('', '### Heal Warnings', '');
146
+ for (const w of result.warnings) {
147
+ lines.push(`- ${w}`);
148
+ }
149
+ }
150
+ return lines.join('\n');
151
+ }
@@ -0,0 +1,19 @@
1
+ import type { FlowDecision } from '../validation/output_schema.js';
2
+ export interface HealPromptContext {
3
+ specPath: string;
4
+ status: 'failed' | 'flaky';
5
+ decision?: FlowDecision;
6
+ failureDetail?: string;
7
+ }
8
+ /**
9
+ * Builds a route-family-aware heal prompt for the playwright-test-healer agent.
10
+ * Enriches the base healer constraints with flow context so the agent understands
11
+ * what the test is supposed to verify, reducing hallucination during selector repair.
12
+ */
13
+ export declare function buildHealPrompt(ctx: HealPromptContext): string;
14
+ /**
15
+ * Builds a minimal quality-fix prompt for spec files that fail content validation
16
+ * (e.g. contain test.describe, test.only, wrong imports).
17
+ */
18
+ export declare function buildQualityFixPrompt(specPath: string, qualityIssues: string[]): string;
19
+ //# sourceMappingURL=heal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heal.d.ts","sourceRoot":"","sources":["../../src/prompts/heal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AAEjE,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CA2C9D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAgBvF"}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
+ // See LICENSE.txt for license information.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.buildHealPrompt = buildHealPrompt;
6
+ exports.buildQualityFixPrompt = buildQualityFixPrompt;
7
+ /**
8
+ * Builds a route-family-aware heal prompt for the playwright-test-healer agent.
9
+ * Enriches the base healer constraints with flow context so the agent understands
10
+ * what the test is supposed to verify, reducing hallucination during selector repair.
11
+ */
12
+ function buildHealPrompt(ctx) {
13
+ const flowBlock = ctx.decision
14
+ ? [
15
+ '',
16
+ 'FLOW CONTEXT (use to understand test intent — do not change test objectives):',
17
+ ` Flow: ${ctx.decision.flowName}`,
18
+ ` Route Family: ${ctx.decision.routeFamily}${ctx.decision.featureId ? ` / ${ctx.decision.featureId}` : ''}`,
19
+ ` Route: ${ctx.decision.specificRoute || '(family-level)'}`,
20
+ ` User Actions: ${ctx.decision.userActions.join('; ') || 'not specified'}`,
21
+ ` Evidence: ${ctx.decision.evidence}`,
22
+ ].join('\n')
23
+ : '';
24
+ const statusNote = ctx.status === 'flaky'
25
+ ? 'This test is FLAKY (passes sometimes, fails other times). Look for race conditions, missing waits, or order-dependent state.'
26
+ : 'This test is FAILING consistently. The selector, URL, or API call is likely broken.';
27
+ const failureBlock = ctx.failureDetail
28
+ ? `\nFailure detail:\n${ctx.failureDetail}`
29
+ : '';
30
+ return [
31
+ 'Heal this specific Playwright test file and keep edits minimal.',
32
+ '',
33
+ `Target test file: ${ctx.specPath}`,
34
+ `Status: ${ctx.status.toUpperCase()} — ${statusNote}`,
35
+ failureBlock,
36
+ flowBlock,
37
+ '',
38
+ 'Healing constraints (must follow):',
39
+ '- Import ONLY from "@mattermost/playwright-lib". Do not use "@playwright/test" directly.',
40
+ '- Do not use test.describe or test.only.',
41
+ '- Keep a single tag string matching the route family (e.g. "@channels", "@scheduled_posts").',
42
+ '- Use only existing Mattermost Playwright fixture and page-object APIs.',
43
+ '- Do NOT invent new pw.* clients or page object methods that do not exist.',
44
+ '- Avoid brittle class selectors (.backstage-navbar, .admin-console__wrapper, .left-panel, .panel-card).',
45
+ '- Prefer stable assertions using URL patterns, data-testid attributes, ARIA roles, and page-object methods.',
46
+ '- For flaky tests: add explicit waits (waitFor, expect().toBeVisible()) before interactions.',
47
+ '- Keep the test intent and scenario unchanged — only fix what is broken.',
48
+ '- If behavior is genuinely broken server-side, mark test.fixme with a clear comment explaining why.',
49
+ '',
50
+ 'Run and fix this test until it compiles and passes, or mark test.fixme when the behavior is truly broken.',
51
+ ].filter((line) => line !== null).join('\n');
52
+ }
53
+ /**
54
+ * Builds a minimal quality-fix prompt for spec files that fail content validation
55
+ * (e.g. contain test.describe, test.only, wrong imports).
56
+ */
57
+ function buildQualityFixPrompt(specPath, qualityIssues) {
58
+ return [
59
+ 'Fix quality issues in this Playwright spec file. Make minimal edits only.',
60
+ '',
61
+ `Target file: ${specPath}`,
62
+ '',
63
+ 'Issues to fix:',
64
+ ...qualityIssues.map((issue) => ` - ${issue}`),
65
+ '',
66
+ 'Rules:',
67
+ '- Import only from "@mattermost/playwright-lib".',
68
+ '- Remove test.describe wrappers (flatten to top-level test() calls).',
69
+ '- Remove test.only calls.',
70
+ '- Ensure each test has exactly one tag string.',
71
+ '- Do not change test logic — only fix structural quality issues.',
72
+ ].join('\n');
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",