@yasserkhanorg/e2e-agents 1.7.7 → 1.8.1

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.
Files changed (119) hide show
  1. package/README.md +55 -0
  2. package/dist/agent/git.d.ts +5 -0
  3. package/dist/agent/git.d.ts.map +1 -1
  4. package/dist/agent/git.js +13 -0
  5. package/dist/agents/coverage-evaluator.d.ts +8 -0
  6. package/dist/agents/coverage-evaluator.d.ts.map +1 -0
  7. package/dist/agents/coverage-evaluator.js +41 -0
  8. package/dist/agents/cross-impact.d.ts +13 -0
  9. package/dist/agents/cross-impact.d.ts.map +1 -0
  10. package/dist/agents/cross-impact.js +135 -0
  11. package/dist/agents/executor.d.ts +8 -0
  12. package/dist/agents/executor.d.ts.map +1 -0
  13. package/dist/agents/executor.js +70 -0
  14. package/dist/agents/explorer.d.ts +12 -0
  15. package/dist/agents/explorer.d.ts.map +1 -0
  16. package/dist/agents/explorer.js +43 -0
  17. package/dist/agents/generator.d.ts +8 -0
  18. package/dist/agents/generator.d.ts.map +1 -0
  19. package/dist/agents/generator.js +77 -0
  20. package/dist/agents/healer.d.ts +8 -0
  21. package/dist/agents/healer.d.ts.map +1 -0
  22. package/dist/agents/healer.js +31 -0
  23. package/dist/agents/impact-analyst.d.ts +8 -0
  24. package/dist/agents/impact-analyst.d.ts.map +1 -0
  25. package/dist/agents/impact-analyst.js +38 -0
  26. package/dist/agents/regression-advisor.d.ts +8 -0
  27. package/dist/agents/regression-advisor.d.ts.map +1 -0
  28. package/dist/agents/regression-advisor.js +116 -0
  29. package/dist/agents/strategist.d.ts +9 -0
  30. package/dist/agents/strategist.d.ts.map +1 -0
  31. package/dist/agents/strategist.js +87 -0
  32. package/dist/agents/test-designer.d.ts +8 -0
  33. package/dist/agents/test-designer.d.ts.map +1 -0
  34. package/dist/agents/test-designer.js +106 -0
  35. package/dist/cli/commands/crew.d.ts +3 -0
  36. package/dist/cli/commands/crew.d.ts.map +1 -0
  37. package/dist/cli/commands/crew.js +137 -0
  38. package/dist/cli/parse_args.d.ts.map +1 -1
  39. package/dist/cli/parse_args.js +2 -1
  40. package/dist/cli/types.d.ts +2 -1
  41. package/dist/cli/types.d.ts.map +1 -1
  42. package/dist/cli.js +5 -0
  43. package/dist/crew/context.d.ts +40 -0
  44. package/dist/crew/context.d.ts.map +1 -0
  45. package/dist/crew/context.js +36 -0
  46. package/dist/crew/orchestrator.d.ts +36 -0
  47. package/dist/crew/orchestrator.d.ts.map +1 -0
  48. package/dist/crew/orchestrator.js +171 -0
  49. package/dist/crew/protocol.d.ts +33 -0
  50. package/dist/crew/protocol.d.ts.map +1 -0
  51. package/dist/crew/protocol.js +4 -0
  52. package/dist/crew/provider.d.ts +3 -0
  53. package/dist/crew/provider.d.ts.map +1 -0
  54. package/dist/crew/provider.js +16 -0
  55. package/dist/crew/sanitize.d.ts +3 -0
  56. package/dist/crew/sanitize.d.ts.map +1 -0
  57. package/dist/crew/sanitize.js +31 -0
  58. package/dist/crew/types.d.ts +52 -0
  59. package/dist/crew/types.d.ts.map +1 -0
  60. package/dist/crew/types.js +4 -0
  61. package/dist/crew/workflows.d.ts +52 -0
  62. package/dist/crew/workflows.d.ts.map +1 -0
  63. package/dist/crew/workflows.js +36 -0
  64. package/dist/esm/agent/git.js +12 -0
  65. package/dist/esm/agents/coverage-evaluator.js +37 -0
  66. package/dist/esm/agents/cross-impact.js +131 -0
  67. package/dist/esm/agents/executor.js +66 -0
  68. package/dist/esm/agents/explorer.js +39 -0
  69. package/dist/esm/agents/generator.js +73 -0
  70. package/dist/esm/agents/healer.js +27 -0
  71. package/dist/esm/agents/impact-analyst.js +34 -0
  72. package/dist/esm/agents/regression-advisor.js +112 -0
  73. package/dist/esm/agents/strategist.js +83 -0
  74. package/dist/esm/agents/test-designer.js +102 -0
  75. package/dist/esm/cli/commands/crew.js +134 -0
  76. package/dist/esm/cli/parse_args.js +2 -1
  77. package/dist/esm/cli.js +5 -0
  78. package/dist/esm/crew/context.js +32 -0
  79. package/dist/esm/crew/orchestrator.js +167 -0
  80. package/dist/esm/crew/protocol.js +3 -0
  81. package/dist/esm/crew/provider.js +13 -0
  82. package/dist/esm/crew/sanitize.js +27 -0
  83. package/dist/esm/crew/types.js +3 -0
  84. package/dist/esm/crew/workflows.js +33 -0
  85. package/dist/esm/index.js +14 -0
  86. package/dist/esm/knowledge/route_families.js +2 -2
  87. package/dist/esm/logger.js +1 -2
  88. package/dist/esm/ollama_provider.js +1 -1
  89. package/dist/esm/prompts/cross-impact.js +71 -0
  90. package/dist/esm/prompts/strategist.js +79 -0
  91. package/dist/esm/prompts/test-designer.js +107 -0
  92. package/dist/esm/provider_factory.js +6 -10
  93. package/dist/esm/training/enricher.js +4 -3
  94. package/dist/esm/training/validator.js +2 -1
  95. package/dist/index.d.ts +17 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +27 -1
  98. package/dist/knowledge/route_families.d.ts.map +1 -1
  99. package/dist/knowledge/route_families.js +2 -2
  100. package/dist/logger.d.ts +1 -2
  101. package/dist/logger.d.ts.map +1 -1
  102. package/dist/logger.js +1 -2
  103. package/dist/ollama_provider.js +1 -1
  104. package/dist/prompts/cross-impact.d.ts +22 -0
  105. package/dist/prompts/cross-impact.d.ts.map +1 -0
  106. package/dist/prompts/cross-impact.js +75 -0
  107. package/dist/prompts/strategist.d.ts +25 -0
  108. package/dist/prompts/strategist.d.ts.map +1 -0
  109. package/dist/prompts/strategist.js +83 -0
  110. package/dist/prompts/test-designer.d.ts +33 -0
  111. package/dist/prompts/test-designer.d.ts.map +1 -0
  112. package/dist/prompts/test-designer.js +111 -0
  113. package/dist/provider_factory.d.ts.map +1 -1
  114. package/dist/provider_factory.js +6 -10
  115. package/dist/training/enricher.d.ts.map +1 -1
  116. package/dist/training/enricher.js +4 -3
  117. package/dist/training/validator.d.ts.map +1 -1
  118. package/dist/training/validator.js +2 -1
  119. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,9 @@ AI-powered E2E test impact analysis, generation, healing, and autonomous QA for
11
11
  Given a git diff, `e2e-ai-agents` determines which E2E test flows are impacted, identifies coverage gaps, and can generate or heal Playwright tests — all from the CLI. The companion `e2e-qa-agent` goes further: it opens a real browser, explores your app autonomously, and produces a QA report with findings and a release-readiness verdict.
12
12
 
13
13
  **Pipeline:** `impact` → `plan` → `generate` → `heal` → `finalize`
14
+ **Crew (v1.8.0):** `impact` + `cross-impact` + `regression-advisor` → `strategist` → `test-designer` → `generator` → `executor` → `healer`
15
+
16
+ > **How does this compare to other tools?** See [docs/comparison.md](docs/comparison.md) for a detailed analysis against Launchable, Codecov ATS, Qodo, Testsigma, mabl, GitHub Copilot, and others.
14
17
 
15
18
  ## Installation
16
19
 
@@ -54,6 +57,58 @@ npx e2e-ai-agents llm-health
54
57
 
55
58
  `plan` and `suggest` are aliases. `analyze` is a convenience wrapper that runs impact + plan and optionally generation/healing in one invocation. Use `--help` for all available flags.
56
59
 
60
+ ## Multi-Agent Crew (v1.8.0)
61
+
62
+ The Crew orchestrates 10 specialized agents for deep test analysis. While the standard pipeline gives a fast pass/fail gate, the Crew produces structured test designs, cross-family impact maps, and prioritized test strategies.
63
+
64
+ ```bash
65
+ # Quick strategy: impact + strategy recommendations (~$0.10, ~1 min)
66
+ npx e2e-ai-agents crew --workflow quick-check --path /path/to/project --tests-root ./e2e-tests --since origin/master
67
+
68
+ # Full test design without generation (~$0.50-2.00, ~5-40 min)
69
+ npx e2e-ai-agents crew --workflow design-only --path /path/to/project --tests-root ./e2e-tests --since origin/master
70
+
71
+ # End-to-end: design + generate + execute + heal (~$2-5, ~10-60 min)
72
+ npx e2e-ai-agents crew --workflow full-qa --path /path/to/project --tests-root ./e2e-tests --since origin/master
73
+
74
+ # With budget cap and JSON output
75
+ npx e2e-ai-agents crew --workflow design-only --budget-usd 2.00 --json --path /path/to/project --tests-root ./e2e-tests --since origin/master
76
+ ```
77
+
78
+ ### What the Crew Adds Beyond the Pipeline
79
+
80
+ | Capability | Pipeline | Crew |
81
+ |-----------|---------|------|
82
+ | Impact detection | Per-family, isolated | Same + cross-family ripple detection |
83
+ | Test scenarios | Flat `scenariosToAdd` strings | Structured `TestCase[]` with type, preconditions, steps, expected outcome, rationale |
84
+ | Test categories | None | 9: happy-path, edge-case, boundary, negative, state-transition, race-condition, permission, accessibility, performance |
85
+ | Strategy | None | Per-flow approach (full-test / smoke-test / skip) with priority and rationale |
86
+ | Regression awareness | None | Risk scoring from flaky history, calibration data, and file-pattern heuristics |
87
+
88
+ ### Programmatic API
89
+
90
+ ```typescript
91
+ import { CrewOrchestrator, ImpactAnalystAgent, StrategistAgent, TestDesignerAgent, CrossImpactAgent, RegressionAdvisorAgent } from '@yasserkhanorg/e2e-agents';
92
+
93
+ const orchestrator = new CrewOrchestrator();
94
+ orchestrator.registerAgent(new ImpactAnalystAgent());
95
+ orchestrator.registerAgent(new CrossImpactAgent());
96
+ orchestrator.registerAgent(new RegressionAdvisorAgent());
97
+ orchestrator.registerAgent(new StrategistAgent());
98
+ orchestrator.registerAgent(new TestDesignerAgent());
99
+
100
+ const result = await orchestrator.run({
101
+ appPath: './webapp',
102
+ testsRoot: './e2e-tests',
103
+ gitSince: 'origin/master',
104
+ workflow: 'design-only',
105
+ });
106
+
107
+ console.log(result.context.testDesigns); // Structured test cases
108
+ console.log(result.context.crossImpacts); // Cross-family links
109
+ console.log(result.context.strategyEntries); // Prioritized strategy
110
+ ```
111
+
57
112
  ## Route-Families Training
58
113
 
59
114
  ### What it produces
@@ -9,5 +9,10 @@ export interface GitChangeResult {
9
9
  export interface GitChangeOptions {
10
10
  includeUncommitted?: boolean;
11
11
  }
12
+ /**
13
+ * Check if a file path is a test file (spec, test, or in test directories).
14
+ * Shared across pipeline and crew orchestrators.
15
+ */
16
+ export declare function isTestFile(file: string): boolean;
12
17
  export declare function getChangedFiles(appRoot: string, since: string, options?: GitChangeOptions): GitChangeResult;
13
18
  //# sourceMappingURL=git.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/agent/git.ts"],"names":[],"mappings":"AAkHA,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kLAAkL;IAClL,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAChC;AAiFD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,eAAe,CA8D3G"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/agent/git.ts"],"names":[],"mappings":"AAkHA,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,kLAAkL;IAClL,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAChC;AAiFD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOhD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,eAAe,CA8D3G"}
package/dist/agent/git.js CHANGED
@@ -2,6 +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.isTestFile = isTestFile;
5
6
  exports.getChangedFiles = getChangedFiles;
6
7
  const child_process_1 = require("child_process");
7
8
  const utils_js_1 = require("./utils.js");
@@ -178,6 +179,18 @@ function isCommentOnlyDiff(file, repoRoot, baseRef) {
178
179
  return content === '' || commentEntry.pattern.test(content);
179
180
  });
180
181
  }
182
+ /**
183
+ * Check if a file path is a test file (spec, test, or in test directories).
184
+ * Shared across pipeline and crew orchestrators.
185
+ */
186
+ function isTestFile(file) {
187
+ const normalized = file.replace(/\\/g, '/');
188
+ return /\.(spec|test)\.(ts|tsx|js|jsx)$/.test(normalized) ||
189
+ /_test\.go$/.test(normalized) ||
190
+ normalized.includes('__tests__/') ||
191
+ normalized.includes('/tests/') ||
192
+ normalized.includes('/test/');
193
+ }
181
194
  function getChangedFiles(appRoot, since, options) {
182
195
  try {
183
196
  const files = new Set();
@@ -0,0 +1,8 @@
1
+ import type { Agent, AgentTask, AgentResult } from '../crew/protocol.js';
2
+ import type { CrewContext } from '../crew/context.js';
3
+ import type { AgentRole } from '../crew/types.js';
4
+ export declare class CoverageEvaluatorAgent implements Agent {
5
+ readonly role: AgentRole;
6
+ execute(_task: AgentTask, ctx: CrewContext): Promise<AgentResult>;
7
+ }
8
+ //# sourceMappingURL=coverage-evaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-evaluator.d.ts","sourceRoot":"","sources":["../../src/agents/coverage-evaluator.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAEhD,qBAAa,sBAAuB,YAAW,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAwB;IAE1C,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAmC1E"}
@@ -0,0 +1,41 @@
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.CoverageEvaluatorAgent = void 0;
6
+ /**
7
+ * Coverage Evaluator Agent — wraps pipeline stage2 (coverage evaluation) in the Agent interface.
8
+ */
9
+ const stage2_coverage_js_1 = require("../pipeline/stage2_coverage.js");
10
+ class CoverageEvaluatorAgent {
11
+ constructor() {
12
+ this.role = 'coverage-evaluator';
13
+ }
14
+ async execute(_task, ctx) {
15
+ const warnings = [];
16
+ if (ctx.impactedFlows.length === 0) {
17
+ warnings.push('Coverage evaluator: no impacted flows to evaluate.');
18
+ return { role: this.role, status: 'partial', output: [], warnings };
19
+ }
20
+ try {
21
+ const result = await (0, stage2_coverage_js_1.runCoverageStage)(ctx.impactedFlows, ctx.specIndex, ctx.context, ctx.testsRoot, { provider: ctx.providerOverride });
22
+ // Replace impacted flows with coverage-enriched versions.
23
+ // This is intentionally a full replace (not push) because coverage evaluation
24
+ // returns the same flow IDs with updated coverage fields.
25
+ ctx.impactedFlows = result.decisions;
26
+ warnings.push(...result.warnings);
27
+ return {
28
+ role: this.role,
29
+ status: 'success',
30
+ output: result.decisions,
31
+ warnings,
32
+ };
33
+ }
34
+ catch (error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ warnings.push(`Coverage evaluator failed: ${message}`);
37
+ return { role: this.role, status: 'failed', output: null, warnings };
38
+ }
39
+ }
40
+ }
41
+ exports.CoverageEvaluatorAgent = CoverageEvaluatorAgent;
@@ -0,0 +1,13 @@
1
+ import type { Agent, AgentTask, AgentResult } from '../crew/protocol.js';
2
+ import type { CrewContext } from '../crew/context.js';
3
+ import type { AgentRole } from '../crew/types.js';
4
+ export declare class CrossImpactAgent implements Agent {
5
+ readonly role: AgentRole;
6
+ execute(_task: AgentTask, ctx: CrewContext): Promise<AgentResult>;
7
+ /**
8
+ * Deterministic cross-impact detection: find families that share webapp/server paths
9
+ * or components with the directly impacted families.
10
+ */
11
+ private detectDeterministic;
12
+ }
13
+ //# sourceMappingURL=cross-impact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-impact.d.ts","sourceRoot":"","sources":["../../src/agents/cross-impact.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,kBAAkB,CAAC;AAK7D,qBAAa,gBAAiB,YAAW,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAkB;IAEpC,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAuFvE;;;OAGG;IACH,OAAO,CAAC,mBAAmB;CAyC9B"}
@@ -0,0 +1,135 @@
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.CrossImpactAgent = void 0;
6
+ /**
7
+ * Cross-Impact Analyst Agent — finds ripple effects across route families
8
+ * by analyzing shared dependencies between changed families and all other families.
9
+ */
10
+ const provider_js_1 = require("../crew/provider.js");
11
+ const cross_impact_js_1 = require("../prompts/cross-impact.js");
12
+ const VALID_RISK = new Set(['high', 'medium', 'low']);
13
+ const MAX_CROSS_IMPACTS = 50;
14
+ class CrossImpactAgent {
15
+ constructor() {
16
+ this.role = 'cross-impact';
17
+ }
18
+ async execute(_task, ctx) {
19
+ const warnings = [];
20
+ if (ctx.routeFamilies.length === 0) {
21
+ warnings.push('Cross-impact: no route families available.');
22
+ return { role: this.role, status: 'partial', output: [], warnings };
23
+ }
24
+ // Determine directly impacted families from family groups
25
+ const directlyImpacted = new Set(ctx.familyGroups.map((g) => g.familyId));
26
+ if (directlyImpacted.size === 0) {
27
+ warnings.push('Cross-impact: no directly impacted families.');
28
+ return { role: this.role, status: 'partial', output: [], warnings };
29
+ }
30
+ // First: deterministic cross-impact detection via shared paths
31
+ const deterministicCrossImpacts = this.detectDeterministic(ctx, directlyImpacted)
32
+ .slice(0, MAX_CROSS_IMPACTS);
33
+ ctx.crossImpacts.push(...deterministicCrossImpacts);
34
+ // Then: LLM-enriched analysis for semantic cross-impacts
35
+ try {
36
+ const provider = await (0, provider_js_1.getCrewProvider)(ctx.providerOverride);
37
+ const prompt = (0, cross_impact_js_1.buildCrossImpactPrompt)({
38
+ changedFiles: ctx.changedFiles,
39
+ families: ctx.routeFamilies,
40
+ directlyImpactedFamilyIds: Array.from(directlyImpacted),
41
+ });
42
+ const response = await provider.generateText(prompt, {
43
+ maxTokens: 3000,
44
+ temperature: 0,
45
+ timeout: 45000,
46
+ systemPrompt: 'Return only valid JSON. Do not include markdown fences unless necessary.',
47
+ });
48
+ const parsed = (0, cross_impact_js_1.parseCrossImpactResponse)(response.text);
49
+ if (parsed && parsed.crossImpacts.length > 0) {
50
+ const familyIds = new Set(ctx.routeFamilies.map((f) => f.id));
51
+ const llmCrossImpacts = parsed.crossImpacts
52
+ .filter((ci) => familyIds.has(ci.sourceFamily) &&
53
+ familyIds.has(ci.affectedFamily) &&
54
+ ci.sourceFamily !== ci.affectedFamily)
55
+ .map((ci) => ({
56
+ sourceFamily: ci.sourceFamily,
57
+ affectedFamily: ci.affectedFamily,
58
+ sharedDependency: ci.sharedDependency || 'unknown',
59
+ riskLevel: VALID_RISK.has(ci.riskLevel) ? ci.riskLevel : 'low',
60
+ evidence: ci.evidence || '',
61
+ }));
62
+ // Deduplicate against deterministic results
63
+ const existing = new Set(ctx.crossImpacts.map((ci) => `${ci.sourceFamily}->${ci.affectedFamily}`));
64
+ for (const ci of llmCrossImpacts) {
65
+ if (ctx.crossImpacts.length >= MAX_CROSS_IMPACTS)
66
+ break;
67
+ const key = `${ci.sourceFamily}->${ci.affectedFamily}`;
68
+ if (!existing.has(key)) {
69
+ ctx.crossImpacts.push(ci);
70
+ existing.add(key);
71
+ }
72
+ }
73
+ }
74
+ return {
75
+ role: this.role,
76
+ status: ctx.crossImpacts.length > 0 ? 'success' : 'partial',
77
+ output: ctx.crossImpacts,
78
+ usage: provider.getUsageStats(),
79
+ warnings,
80
+ };
81
+ }
82
+ catch (error) {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ warnings.push(`Cross-impact LLM analysis failed: ${message}. Using deterministic results only.`);
85
+ return {
86
+ role: this.role,
87
+ status: deterministicCrossImpacts.length > 0 ? 'partial' : 'failed',
88
+ output: ctx.crossImpacts,
89
+ warnings,
90
+ };
91
+ }
92
+ }
93
+ /**
94
+ * Deterministic cross-impact detection: find families that share webapp/server paths
95
+ * or components with the directly impacted families.
96
+ */
97
+ detectDeterministic(ctx, directlyImpacted) {
98
+ const results = [];
99
+ for (const sourceId of directlyImpacted) {
100
+ const source = ctx.routeFamilies.find((f) => f.id === sourceId);
101
+ if (!source)
102
+ continue;
103
+ const sourcePaths = new Set([
104
+ ...(source.webappPaths || []),
105
+ ...(source.serverPaths || []),
106
+ ...(source.components || []),
107
+ ]);
108
+ if (sourcePaths.size === 0)
109
+ continue;
110
+ for (const target of ctx.routeFamilies) {
111
+ if (target.id === sourceId)
112
+ continue;
113
+ const targetPaths = [
114
+ ...(target.webappPaths || []),
115
+ ...(target.serverPaths || []),
116
+ ...(target.components || []),
117
+ ];
118
+ for (const path of targetPaths) {
119
+ if (sourcePaths.has(path)) {
120
+ results.push({
121
+ sourceFamily: sourceId,
122
+ affectedFamily: target.id,
123
+ sharedDependency: path,
124
+ riskLevel: 'medium',
125
+ evidence: `Shared path: ${path} is referenced by both ${sourceId} and ${target.id}`,
126
+ });
127
+ break; // One match per family pair is enough
128
+ }
129
+ }
130
+ }
131
+ }
132
+ return results;
133
+ }
134
+ }
135
+ exports.CrossImpactAgent = CrossImpactAgent;
@@ -0,0 +1,8 @@
1
+ import type { Agent, AgentTask, AgentResult } from '../crew/protocol.js';
2
+ import type { CrewContext } from '../crew/context.js';
3
+ import type { AgentRole } from '../crew/types.js';
4
+ export declare class ExecutorAgent implements Agent {
5
+ readonly role: AgentRole;
6
+ execute(_task: AgentTask, ctx: CrewContext): Promise<AgentResult>;
7
+ }
8
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/agents/executor.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAKhD,qBAAa,aAAc,YAAW,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAc;IAEhC,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAwD1E"}
@@ -0,0 +1,70 @@
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.ExecutorAgent = void 0;
6
+ /**
7
+ * Executor Agent — wraps agentic test execution in the Agent interface.
8
+ * Runs generated specs through Playwright and collects results.
9
+ */
10
+ const provider_js_1 = require("../crew/provider.js");
11
+ const runner_js_1 = require("../agentic/runner.js");
12
+ const MAX_FIX_ATTEMPTS = 2;
13
+ const TEST_TIMEOUT_MS = 120000;
14
+ class ExecutorAgent {
15
+ constructor() {
16
+ this.role = 'executor';
17
+ }
18
+ async execute(_task, ctx) {
19
+ const warnings = [];
20
+ const writtenSpecs = ctx.generatedSpecs.filter((s) => s.written);
21
+ if (writtenSpecs.length === 0) {
22
+ warnings.push('Executor: no written specs to execute.');
23
+ return { role: this.role, status: 'partial', output: null, warnings };
24
+ }
25
+ // Build ScenarioInput[] from generated specs + impacted flows
26
+ const flowMap = new Map(ctx.impactedFlows.map((f) => [f.flowId, f]));
27
+ const scenarios = writtenSpecs.map((spec) => {
28
+ const flow = flowMap.get(spec.flowId);
29
+ return {
30
+ id: spec.flowId,
31
+ name: flow?.flowName || spec.flowId,
32
+ scenarios: flow?.scenariosToAdd || [],
33
+ routeFamily: flow?.routeFamily || 'unknown',
34
+ priority: flow?.priority || 'P2',
35
+ targetSpec: spec.mode === 'add_scenarios' ? spec.specPath : undefined,
36
+ changedFiles: flow?.changedFiles,
37
+ evidence: flow?.evidence,
38
+ };
39
+ });
40
+ try {
41
+ const provider = await (0, provider_js_1.getCrewProvider)(ctx.providerOverride);
42
+ const summary = await (0, runner_js_1.runAgenticGeneration)({
43
+ scenarios,
44
+ config: {
45
+ maxAttempts: MAX_FIX_ATTEMPTS,
46
+ project: 'chrome',
47
+ testTimeoutMs: TEST_TIMEOUT_MS,
48
+ provider: ctx.providerOverride,
49
+ testsRoot: ctx.testsRoot,
50
+ },
51
+ provider,
52
+ apiSurface: ctx.apiSurface,
53
+ });
54
+ warnings.push(...summary.warnings);
55
+ return {
56
+ role: this.role,
57
+ status: summary.totalPassed > 0 ? 'success' : 'partial',
58
+ output: summary,
59
+ usage: provider.getUsageStats(),
60
+ warnings,
61
+ };
62
+ }
63
+ catch (error) {
64
+ const message = error instanceof Error ? error.message : String(error);
65
+ warnings.push(`Executor failed: ${message}`);
66
+ return { role: this.role, status: 'failed', output: null, warnings };
67
+ }
68
+ }
69
+ }
70
+ exports.ExecutorAgent = ExecutorAgent;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Explorer Agent — wraps the QA agent browser exploration loop in the Agent interface.
3
+ * This agent is optional and only runs when a browser environment is available.
4
+ */
5
+ import type { Agent, AgentTask, AgentResult } from '../crew/protocol.js';
6
+ import type { CrewContext } from '../crew/context.js';
7
+ import type { AgentRole } from '../crew/types.js';
8
+ export declare class ExplorerAgent implements Agent {
9
+ readonly role: AgentRole;
10
+ execute(_task: AgentTask, ctx: CrewContext): Promise<AgentResult>;
11
+ }
12
+ //# sourceMappingURL=explorer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explorer.d.ts","sourceRoot":"","sources":["../../src/agents/explorer.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAEhD,qBAAa,aAAc,YAAW,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAc;IAEhC,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAmC1E"}
@@ -0,0 +1,43 @@
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.ExplorerAgent = void 0;
6
+ class ExplorerAgent {
7
+ constructor() {
8
+ this.role = 'explorer';
9
+ }
10
+ async execute(_task, ctx) {
11
+ const warnings = [];
12
+ // Explorer requires browser environment — skip gracefully if not available
13
+ try {
14
+ // Build target flows from impacted flows
15
+ const targetFlows = ctx.impactedFlows
16
+ .filter((d) => d.action !== 'cannot_determine')
17
+ .map((d) => ({
18
+ id: d.flowId,
19
+ name: d.flowName,
20
+ url: d.specificRoute,
21
+ priority: d.priority,
22
+ }));
23
+ if (targetFlows.length === 0) {
24
+ warnings.push('Explorer: no target flows for exploration.');
25
+ return { role: this.role, status: 'partial', output: null, warnings };
26
+ }
27
+ // Convert QA findings to crew findings
28
+ warnings.push(`Explorer: ${targetFlows.length} flows available for exploration (requires browser environment).`);
29
+ return {
30
+ role: this.role,
31
+ status: 'partial',
32
+ output: { targetFlows },
33
+ warnings,
34
+ };
35
+ }
36
+ catch (error) {
37
+ const message = error instanceof Error ? error.message : String(error);
38
+ warnings.push(`Explorer failed: ${message}`);
39
+ return { role: this.role, status: 'failed', output: null, warnings };
40
+ }
41
+ }
42
+ }
43
+ exports.ExplorerAgent = ExplorerAgent;
@@ -0,0 +1,8 @@
1
+ import type { Agent, AgentTask, AgentResult } from '../crew/protocol.js';
2
+ import type { CrewContext } from '../crew/context.js';
3
+ import type { AgentRole } from '../crew/types.js';
4
+ export declare class GeneratorAgent implements Agent {
5
+ readonly role: AgentRole;
6
+ execute(_task: AgentTask, ctx: CrewContext): Promise<AgentResult>;
7
+ }
8
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/agents/generator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AA6ChD,qBAAa,cAAe,YAAW,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAe;IAEjC,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAoC1E"}
@@ -0,0 +1,77 @@
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.GeneratorAgent = void 0;
6
+ /**
7
+ * Generator Agent — wraps pipeline stage3 (test generation) in the Agent interface.
8
+ * Enhanced to accept TestCase[] from the Test Designer in addition to flat scenariosToAdd.
9
+ */
10
+ const stage3_generation_js_1 = require("../pipeline/stage3_generation.js");
11
+ /**
12
+ * Enrich FlowDecisions with TestDesign data from the crew context.
13
+ * Converts structured TestCase[] into scenariosToAdd strings that the
14
+ * existing generation prompt can consume.
15
+ */
16
+ function enrichDecisionsWithTestDesigns(ctx) {
17
+ if (ctx.testDesigns.length === 0) {
18
+ return ctx.impactedFlows;
19
+ }
20
+ const designsByFlow = new Map(ctx.testDesigns.map((td) => [td.flowId, td]));
21
+ return ctx.impactedFlows.map((decision) => {
22
+ const design = designsByFlow.get(decision.flowId);
23
+ if (!design || design.testCases.length === 0) {
24
+ return decision;
25
+ }
26
+ // Convert structured test cases to scenario descriptions for the generator prompt
27
+ const designedScenarios = design.testCases.map((tc) => {
28
+ const steps = tc.steps.join(' → ');
29
+ return `[${tc.type}] ${tc.name}: ${steps} → Expected: ${tc.expectedOutcome}`;
30
+ });
31
+ // Merge with any existing scenarios, preferring designed ones
32
+ const existingScenarios = decision.scenariosToAdd || [];
33
+ const mergedScenarios = [...designedScenarios, ...existingScenarios];
34
+ return {
35
+ ...decision,
36
+ scenariosToAdd: mergedScenarios,
37
+ // Intentionally promote run_existing → add_scenarios when the test-designer
38
+ // produced new test cases. This ensures designed tests are generated even if
39
+ // impact-analyst thought existing coverage was sufficient. The test-designer
40
+ // only runs for flows the strategist deemed worth testing.
41
+ action: decision.action === 'run_existing' && designedScenarios.length > 0
42
+ ? 'add_scenarios'
43
+ : decision.action,
44
+ };
45
+ });
46
+ }
47
+ class GeneratorAgent {
48
+ constructor() {
49
+ this.role = 'generator';
50
+ }
51
+ async execute(_task, ctx) {
52
+ const warnings = [];
53
+ const enrichedDecisions = enrichDecisionsWithTestDesigns(ctx);
54
+ const actionable = enrichedDecisions.filter((d) => d.action === 'create_spec' || d.action === 'add_scenarios');
55
+ if (actionable.length === 0) {
56
+ warnings.push('Generator: no actionable decisions for generation.');
57
+ return { role: this.role, status: 'partial', output: [], warnings };
58
+ }
59
+ try {
60
+ const result = await (0, stage3_generation_js_1.runGenerationStage)(enrichedDecisions, ctx.apiSurface, ctx.testsRoot, { provider: ctx.providerOverride });
61
+ ctx.generatedSpecs.push(...result.generated);
62
+ warnings.push(...result.warnings);
63
+ return {
64
+ role: this.role,
65
+ status: result.generatedCount > 0 ? 'success' : 'partial',
66
+ output: result,
67
+ warnings,
68
+ };
69
+ }
70
+ catch (error) {
71
+ const message = error instanceof Error ? error.message : String(error);
72
+ warnings.push(`Generator failed: ${message}`);
73
+ return { role: this.role, status: 'failed', output: null, warnings };
74
+ }
75
+ }
76
+ }
77
+ exports.GeneratorAgent = GeneratorAgent;
@@ -0,0 +1,8 @@
1
+ import type { Agent, AgentTask, AgentResult } from '../crew/protocol.js';
2
+ import type { CrewContext } from '../crew/context.js';
3
+ import type { AgentRole } from '../crew/types.js';
4
+ export declare class HealerAgent implements Agent {
5
+ readonly role: AgentRole;
6
+ execute(_task: AgentTask, ctx: CrewContext): Promise<AgentResult>;
7
+ }
8
+ //# sourceMappingURL=healer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"healer.d.ts","sourceRoot":"","sources":["../../src/agents/healer.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAEhD,qBAAa,WAAY,YAAW,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAY;IAE9B,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAwB1E"}
@@ -0,0 +1,31 @@
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.HealerAgent = void 0;
6
+ /**
7
+ * Healer Agent — wraps pipeline stage4 (test healing) in the Agent interface.
8
+ */
9
+ const stage4_heal_js_1 = require("../pipeline/stage4_heal.js");
10
+ class HealerAgent {
11
+ constructor() {
12
+ this.role = 'healer';
13
+ }
14
+ async execute(_task, ctx) {
15
+ const warnings = [];
16
+ const healTargets = (0, stage4_heal_js_1.resolveHealTargets)(ctx.testsRoot, { generatedSpecs: ctx.generatedSpecs }, ctx.impactedFlows);
17
+ if (healTargets.length === 0) {
18
+ warnings.push('Healer: no heal targets found.');
19
+ return { role: this.role, status: 'partial', output: null, warnings };
20
+ }
21
+ const result = await (0, stage4_heal_js_1.runHealStage)(ctx.testsRoot, healTargets, { mcp: true });
22
+ warnings.push(...result.warnings);
23
+ return {
24
+ role: this.role,
25
+ status: result.healSuccess > 0 ? 'success' : 'partial',
26
+ output: result,
27
+ warnings,
28
+ };
29
+ }
30
+ }
31
+ exports.HealerAgent = HealerAgent;
@@ -0,0 +1,8 @@
1
+ import type { Agent, AgentTask, AgentResult } from '../crew/protocol.js';
2
+ import type { CrewContext } from '../crew/context.js';
3
+ import type { AgentRole } from '../crew/types.js';
4
+ export declare class ImpactAnalystAgent implements Agent {
5
+ readonly role: AgentRole;
6
+ execute(_task: AgentTask, ctx: CrewContext): Promise<AgentResult>;
7
+ }
8
+ //# sourceMappingURL=impact-analyst.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impact-analyst.d.ts","sourceRoot":"","sources":["../../src/agents/impact-analyst.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,KAAK,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAC;AAEhD,qBAAa,kBAAmB,YAAW,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAoB;IAEtC,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAiC1E"}