@yasserkhanorg/e2e-agents 0.3.2

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 (221) hide show
  1. package/LICENSE +168 -0
  2. package/README.md +620 -0
  3. package/dist/agent/analysis.d.ts +62 -0
  4. package/dist/agent/analysis.d.ts.map +1 -0
  5. package/dist/agent/analysis.js +292 -0
  6. package/dist/agent/blast_radius.d.ts +4 -0
  7. package/dist/agent/blast_radius.d.ts.map +1 -0
  8. package/dist/agent/blast_radius.js +37 -0
  9. package/dist/agent/cache_utils.d.ts +38 -0
  10. package/dist/agent/cache_utils.d.ts.map +1 -0
  11. package/dist/agent/cache_utils.js +67 -0
  12. package/dist/agent/config.d.ts +148 -0
  13. package/dist/agent/config.d.ts.map +1 -0
  14. package/dist/agent/config.js +640 -0
  15. package/dist/agent/dependency_graph.d.ts +14 -0
  16. package/dist/agent/dependency_graph.d.ts.map +1 -0
  17. package/dist/agent/dependency_graph.js +227 -0
  18. package/dist/agent/feedback.d.ts +55 -0
  19. package/dist/agent/feedback.d.ts.map +1 -0
  20. package/dist/agent/feedback.js +257 -0
  21. package/dist/agent/flags.d.ts +23 -0
  22. package/dist/agent/flags.d.ts.map +1 -0
  23. package/dist/agent/flags.js +171 -0
  24. package/dist/agent/flow_catalog.d.ts +25 -0
  25. package/dist/agent/flow_catalog.d.ts.map +1 -0
  26. package/dist/agent/flow_catalog.js +106 -0
  27. package/dist/agent/flow_mapping.d.ts +10 -0
  28. package/dist/agent/flow_mapping.d.ts.map +1 -0
  29. package/dist/agent/flow_mapping.js +84 -0
  30. package/dist/agent/framework.d.ts +13 -0
  31. package/dist/agent/framework.d.ts.map +1 -0
  32. package/dist/agent/framework.js +149 -0
  33. package/dist/agent/gap_suggestions.d.ts +14 -0
  34. package/dist/agent/gap_suggestions.d.ts.map +1 -0
  35. package/dist/agent/gap_suggestions.js +101 -0
  36. package/dist/agent/generator.d.ts +10 -0
  37. package/dist/agent/generator.d.ts.map +1 -0
  38. package/dist/agent/generator.js +115 -0
  39. package/dist/agent/git.d.ts +11 -0
  40. package/dist/agent/git.d.ts.map +1 -0
  41. package/dist/agent/git.js +90 -0
  42. package/dist/agent/handoff.d.ts +22 -0
  43. package/dist/agent/handoff.d.ts.map +1 -0
  44. package/dist/agent/handoff.js +180 -0
  45. package/dist/agent/impact-analyzer.d.ts +114 -0
  46. package/dist/agent/impact-analyzer.d.ts.map +1 -0
  47. package/dist/agent/impact-analyzer.js +557 -0
  48. package/dist/agent/index.d.ts +21 -0
  49. package/dist/agent/index.d.ts.map +1 -0
  50. package/dist/agent/index.js +38 -0
  51. package/dist/agent/model-router.d.ts +57 -0
  52. package/dist/agent/model-router.d.ts.map +1 -0
  53. package/dist/agent/model-router.js +154 -0
  54. package/dist/agent/operational_insights.d.ts +41 -0
  55. package/dist/agent/operational_insights.d.ts.map +1 -0
  56. package/dist/agent/operational_insights.js +126 -0
  57. package/dist/agent/pipeline.d.ts +23 -0
  58. package/dist/agent/pipeline.d.ts.map +1 -0
  59. package/dist/agent/pipeline.js +609 -0
  60. package/dist/agent/plan.d.ts +91 -0
  61. package/dist/agent/plan.d.ts.map +1 -0
  62. package/dist/agent/plan.js +331 -0
  63. package/dist/agent/playwright_report.d.ts +8 -0
  64. package/dist/agent/playwright_report.d.ts.map +1 -0
  65. package/dist/agent/playwright_report.js +126 -0
  66. package/dist/agent/report-generator.d.ts +24 -0
  67. package/dist/agent/report-generator.d.ts.map +1 -0
  68. package/dist/agent/report-generator.js +250 -0
  69. package/dist/agent/report.d.ts +81 -0
  70. package/dist/agent/report.d.ts.map +1 -0
  71. package/dist/agent/report.js +147 -0
  72. package/dist/agent/runner.d.ts +7 -0
  73. package/dist/agent/runner.d.ts.map +1 -0
  74. package/dist/agent/runner.js +576 -0
  75. package/dist/agent/selectors.d.ts +10 -0
  76. package/dist/agent/selectors.d.ts.map +1 -0
  77. package/dist/agent/selectors.js +75 -0
  78. package/dist/agent/spec-bridge.d.ts +101 -0
  79. package/dist/agent/spec-bridge.d.ts.map +1 -0
  80. package/dist/agent/spec-bridge.js +273 -0
  81. package/dist/agent/spec-builder.d.ts +102 -0
  82. package/dist/agent/spec-builder.d.ts.map +1 -0
  83. package/dist/agent/spec-builder.js +273 -0
  84. package/dist/agent/subsystem_risk.d.ts +23 -0
  85. package/dist/agent/subsystem_risk.d.ts.map +1 -0
  86. package/dist/agent/subsystem_risk.js +207 -0
  87. package/dist/agent/telemetry.d.ts +84 -0
  88. package/dist/agent/telemetry.d.ts.map +1 -0
  89. package/dist/agent/telemetry.js +220 -0
  90. package/dist/agent/test_path.d.ts +2 -0
  91. package/dist/agent/test_path.d.ts.map +1 -0
  92. package/dist/agent/test_path.js +23 -0
  93. package/dist/agent/tests.d.ts +18 -0
  94. package/dist/agent/tests.d.ts.map +1 -0
  95. package/dist/agent/tests.js +106 -0
  96. package/dist/agent/traceability.d.ts +22 -0
  97. package/dist/agent/traceability.d.ts.map +1 -0
  98. package/dist/agent/traceability.js +183 -0
  99. package/dist/agent/traceability_capture.d.ts +18 -0
  100. package/dist/agent/traceability_capture.d.ts.map +1 -0
  101. package/dist/agent/traceability_capture.js +313 -0
  102. package/dist/agent/traceability_ingest.d.ts +21 -0
  103. package/dist/agent/traceability_ingest.d.ts.map +1 -0
  104. package/dist/agent/traceability_ingest.js +237 -0
  105. package/dist/agent/utils.d.ts +13 -0
  106. package/dist/agent/utils.d.ts.map +1 -0
  107. package/dist/agent/utils.js +152 -0
  108. package/dist/agent/validators/selector-validator.d.ts +74 -0
  109. package/dist/agent/validators/selector-validator.d.ts.map +1 -0
  110. package/dist/agent/validators/selector-validator.js +165 -0
  111. package/dist/anthropic_provider.d.ts +65 -0
  112. package/dist/anthropic_provider.d.ts.map +1 -0
  113. package/dist/anthropic_provider.js +332 -0
  114. package/dist/api.d.ts +48 -0
  115. package/dist/api.d.ts.map +1 -0
  116. package/dist/api.js +113 -0
  117. package/dist/base_provider.d.ts +53 -0
  118. package/dist/base_provider.d.ts.map +1 -0
  119. package/dist/base_provider.js +81 -0
  120. package/dist/cli.d.ts +3 -0
  121. package/dist/cli.d.ts.map +1 -0
  122. package/dist/cli.js +843 -0
  123. package/dist/custom_provider.d.ts +20 -0
  124. package/dist/custom_provider.d.ts.map +1 -0
  125. package/dist/custom_provider.js +276 -0
  126. package/dist/e2e-test-gen/index.d.ts +51 -0
  127. package/dist/e2e-test-gen/index.d.ts.map +1 -0
  128. package/dist/e2e-test-gen/index.js +57 -0
  129. package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
  130. package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
  131. package/dist/e2e-test-gen/spec_parser.js +786 -0
  132. package/dist/e2e-test-gen/types.d.ts +185 -0
  133. package/dist/e2e-test-gen/types.d.ts.map +1 -0
  134. package/dist/e2e-test-gen/types.js +4 -0
  135. package/dist/esm/agent/analysis.js +287 -0
  136. package/dist/esm/agent/blast_radius.js +34 -0
  137. package/dist/esm/agent/cache_utils.js +63 -0
  138. package/dist/esm/agent/config.js +637 -0
  139. package/dist/esm/agent/dependency_graph.js +224 -0
  140. package/dist/esm/agent/feedback.js +253 -0
  141. package/dist/esm/agent/flags.js +160 -0
  142. package/dist/esm/agent/flow_catalog.js +103 -0
  143. package/dist/esm/agent/flow_mapping.js +81 -0
  144. package/dist/esm/agent/framework.js +145 -0
  145. package/dist/esm/agent/gap_suggestions.js +98 -0
  146. package/dist/esm/agent/generator.js +112 -0
  147. package/dist/esm/agent/git.js +87 -0
  148. package/dist/esm/agent/handoff.js +177 -0
  149. package/dist/esm/agent/impact-analyzer.js +548 -0
  150. package/dist/esm/agent/index.js +22 -0
  151. package/dist/esm/agent/model-router.js +150 -0
  152. package/dist/esm/agent/operational_insights.js +123 -0
  153. package/dist/esm/agent/pipeline.js +605 -0
  154. package/dist/esm/agent/plan.js +324 -0
  155. package/dist/esm/agent/playwright_report.js +123 -0
  156. package/dist/esm/agent/report-generator.js +247 -0
  157. package/dist/esm/agent/report.js +144 -0
  158. package/dist/esm/agent/runner.js +572 -0
  159. package/dist/esm/agent/selectors.js +71 -0
  160. package/dist/esm/agent/spec-bridge.js +267 -0
  161. package/dist/esm/agent/spec-builder.js +267 -0
  162. package/dist/esm/agent/subsystem_risk.js +204 -0
  163. package/dist/esm/agent/telemetry.js +216 -0
  164. package/dist/esm/agent/test_path.js +20 -0
  165. package/dist/esm/agent/tests.js +101 -0
  166. package/dist/esm/agent/traceability.js +180 -0
  167. package/dist/esm/agent/traceability_capture.js +310 -0
  168. package/dist/esm/agent/traceability_ingest.js +234 -0
  169. package/dist/esm/agent/utils.js +138 -0
  170. package/dist/esm/agent/validators/selector-validator.js +160 -0
  171. package/dist/esm/anthropic_provider.js +324 -0
  172. package/dist/esm/api.js +105 -0
  173. package/dist/esm/base_provider.js +77 -0
  174. package/dist/esm/cli.js +841 -0
  175. package/dist/esm/custom_provider.js +272 -0
  176. package/dist/esm/e2e-test-gen/index.js +50 -0
  177. package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
  178. package/dist/esm/e2e-test-gen/types.js +3 -0
  179. package/dist/esm/index.js +16 -0
  180. package/dist/esm/logger.js +89 -0
  181. package/dist/esm/mcp-server.js +465 -0
  182. package/dist/esm/ollama_provider.js +300 -0
  183. package/dist/esm/openai_provider.js +242 -0
  184. package/dist/esm/package.json +3 -0
  185. package/dist/esm/plan-and-test-constants.js +126 -0
  186. package/dist/esm/provider_factory.js +336 -0
  187. package/dist/esm/provider_interface.js +23 -0
  188. package/dist/esm/provider_utils.js +96 -0
  189. package/dist/index.d.ts +31 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +41 -0
  192. package/dist/logger.d.ts +23 -0
  193. package/dist/logger.d.ts.map +1 -0
  194. package/dist/logger.js +93 -0
  195. package/dist/mcp-server.d.ts +35 -0
  196. package/dist/mcp-server.d.ts.map +1 -0
  197. package/dist/mcp-server.js +469 -0
  198. package/dist/ollama_provider.d.ts +65 -0
  199. package/dist/ollama_provider.d.ts.map +1 -0
  200. package/dist/ollama_provider.js +308 -0
  201. package/dist/openai_provider.d.ts +23 -0
  202. package/dist/openai_provider.d.ts.map +1 -0
  203. package/dist/openai_provider.js +250 -0
  204. package/dist/plan-and-test-constants.d.ts +110 -0
  205. package/dist/plan-and-test-constants.d.ts.map +1 -0
  206. package/dist/plan-and-test-constants.js +132 -0
  207. package/dist/provider_factory.d.ts +99 -0
  208. package/dist/provider_factory.d.ts.map +1 -0
  209. package/dist/provider_factory.js +341 -0
  210. package/dist/provider_interface.d.ts +358 -0
  211. package/dist/provider_interface.d.ts.map +1 -0
  212. package/dist/provider_interface.js +28 -0
  213. package/dist/provider_utils.d.ts +39 -0
  214. package/dist/provider_utils.d.ts.map +1 -0
  215. package/dist/provider_utils.js +103 -0
  216. package/package.json +101 -0
  217. package/schemas/gap.schema.json +18 -0
  218. package/schemas/impact.schema.json +418 -0
  219. package/schemas/plan.schema.json +285 -0
  220. package/schemas/subsystem-risk-map.schema.json +62 -0
  221. package/schemas/traceability-input.schema.json +122 -0
@@ -0,0 +1,557 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
4
+ * See LICENSE.txt for license information.
5
+ *
6
+ * Impact Analysis Engine
7
+ *
8
+ * Analyzes code changes and identifies which user flows are affected,
9
+ * then maps those flows to test coverage gaps.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.detectComparisonBase = detectComparisonBase;
13
+ exports.getGitChanges = getGitChanges;
14
+ exports.loadFlowCatalog = loadFlowCatalog;
15
+ exports.matchFlowToChanges = matchFlowToChanges;
16
+ exports.findExistingTests = findExistingTests;
17
+ exports.identifyTestGaps = identifyTestGaps;
18
+ exports.analyzeImpact = analyzeImpact;
19
+ const child_process_1 = require("child_process");
20
+ const fs_1 = require("fs");
21
+ const path_1 = require("path");
22
+ const minimatch_1 = require("minimatch");
23
+ // =============================================================================
24
+ // GIT CHANGE DETECTION
25
+ // =============================================================================
26
+ /**
27
+ * Intelligently detect the best git reference for comparison:
28
+ * - If on feature branch: use origin/master, origin/main, or master
29
+ * - If on main branch: use HEAD~1
30
+ * Returns the best available reference to compare against
31
+ */
32
+ function detectComparisonBase() {
33
+ try {
34
+ const gitRoot = findGitRoot();
35
+ // Get current branch
36
+ const currentBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
37
+ encoding: 'utf-8',
38
+ cwd: gitRoot,
39
+ }).trim();
40
+ // If on main branch, just check last commit
41
+ if (currentBranch === 'master' || currentBranch === 'main') {
42
+ return 'HEAD~1';
43
+ }
44
+ // On feature branch: try to find the main branch
45
+ // Try origin/master first, then origin/main, then master
46
+ const candidates = ['origin/master', 'origin/main', 'master'];
47
+ for (const candidate of candidates) {
48
+ try {
49
+ (0, child_process_1.execSync)(`git rev-parse ${candidate}`, {
50
+ encoding: 'utf-8',
51
+ cwd: gitRoot,
52
+ stdio: 'ignore',
53
+ });
54
+ return candidate;
55
+ }
56
+ catch {
57
+ // Try next candidate
58
+ }
59
+ }
60
+ // Fallback if none found
61
+ return 'HEAD~1';
62
+ }
63
+ catch {
64
+ // If git commands fail, default to HEAD~1
65
+ return 'HEAD~1';
66
+ }
67
+ }
68
+ /**
69
+ * Check if a file path is a frontend file
70
+ * Frontend includes: webapp and e2e-tests directories
71
+ */
72
+ function isFrontendFile(filePath) {
73
+ return filePath.startsWith('webapp/') || filePath.startsWith('e2e-tests/');
74
+ }
75
+ /**
76
+ * Get git changes since a given reference
77
+ * Filters to only include frontend files (webapp and e2e-tests)
78
+ */
79
+ function getGitChanges(since = 'HEAD~1') {
80
+ try {
81
+ const gitRoot = findGitRoot();
82
+ const result = (0, child_process_1.execSync)(`git diff --name-status ${since}...HEAD`, {
83
+ encoding: 'utf-8',
84
+ cwd: gitRoot,
85
+ });
86
+ return result
87
+ .trim()
88
+ .split('\n')
89
+ .filter((line) => line.length > 0)
90
+ .map((line) => {
91
+ const [status, path] = line.split('\t');
92
+ return {
93
+ path,
94
+ status: status,
95
+ ref: since,
96
+ };
97
+ })
98
+ .filter((change) => isFrontendFile(change.path));
99
+ }
100
+ catch (error) {
101
+ console.warn(`⚠️ Could not get git changes: ${error.message}`);
102
+ return [];
103
+ }
104
+ }
105
+ /**
106
+ * Get git diff stats for a file
107
+ */
108
+ function getFileDiffStats(filePath, since = 'HEAD~1') {
109
+ try {
110
+ const gitRoot = findGitRoot();
111
+ const result = (0, child_process_1.execSync)(`git diff --numstat ${since}...HEAD -- ${filePath}`, {
112
+ encoding: 'utf-8',
113
+ cwd: gitRoot,
114
+ });
115
+ const match = result.match(/(\d+)\s+(\d+)/);
116
+ if (match) {
117
+ return {
118
+ linesAdded: parseInt(match[1], 10),
119
+ linesRemoved: parseInt(match[2], 10),
120
+ };
121
+ }
122
+ }
123
+ catch (error) {
124
+ // Ignore errors
125
+ }
126
+ return { linesAdded: 0, linesRemoved: 0 };
127
+ }
128
+ // =============================================================================
129
+ // FLOW CATALOG LOADING
130
+ // =============================================================================
131
+ /**
132
+ * Load flow catalog from flows.json
133
+ */
134
+ function loadFlowCatalog(catalogPath) {
135
+ let path = catalogPath;
136
+ if (!path) {
137
+ // Try multiple possible paths for flows.json
138
+ const possiblePaths = [
139
+ // If running from e2e-tests/playwright directory
140
+ (0, path_1.join)(process.cwd(), '.e2e-ai-agents/flows.json'),
141
+ // If running from monorepo root
142
+ (0, path_1.join)(process.cwd(), 'e2e-tests/playwright/.e2e-ai-agents/flows.json'),
143
+ ];
144
+ // Find the first path that exists
145
+ path = possiblePaths.find((p) => (0, fs_1.existsSync)(p));
146
+ if (!path) {
147
+ throw new Error(`Flow catalog not found. Tried:\n${possiblePaths.map((p) => ` - ${p}`).join('\n')}`);
148
+ }
149
+ }
150
+ if (!(0, fs_1.existsSync)(path)) {
151
+ throw new Error(`Flow catalog not found at ${path}`);
152
+ }
153
+ const content = (0, fs_1.readFileSync)(path, 'utf-8');
154
+ const data = JSON.parse(content);
155
+ return data.flows || [];
156
+ }
157
+ // =============================================================================
158
+ // FLOW MATCHING
159
+ // =============================================================================
160
+ /**
161
+ * Check if a file path matches a glob pattern
162
+ */
163
+ function pathMatches(filePath, pattern) {
164
+ // Normalize patterns
165
+ const normalizedPattern = pattern.replace(/\\/g, '/');
166
+ const normalizedPath = filePath.replace(/\\/g, '/');
167
+ // Handle ** wildcards
168
+ if (normalizedPattern.includes('**')) {
169
+ // Convert ** pattern to regex
170
+ // Use placeholder to avoid escaping issues with **
171
+ const placeholder = '___DOUBLE_STAR___';
172
+ let regexPattern = normalizedPattern.replace(/\*\*/g, placeholder);
173
+ // Escape regex special chars
174
+ regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
175
+ // Replace placeholder with .* (matches anything including /)
176
+ regexPattern = regexPattern.replace(new RegExp(placeholder, 'g'), '.*');
177
+ // Replace remaining * with [^/]* (matches anything except /)
178
+ regexPattern = regexPattern.replace(/\*/g, '[^/]*');
179
+ // Replace ? with [^/]
180
+ regexPattern = regexPattern.replace(/\?/g, '[^/]');
181
+ return new RegExp(`^${regexPattern}$`).test(normalizedPath);
182
+ }
183
+ return (0, minimatch_1.minimatch)(normalizedPath, normalizedPattern, { matchBase: true });
184
+ }
185
+ /**
186
+ * Calculate confidence score based on match type and quality
187
+ */
188
+ function calculateConfidence(matchType, matchQuality) {
189
+ const typeWeights = {
190
+ path_exact: 100,
191
+ path_pattern: 85,
192
+ keyword_multiple: 75,
193
+ keyword_single: 60,
194
+ import: 50,
195
+ };
196
+ return Math.min(100, typeWeights[matchType] || 50);
197
+ }
198
+ /**
199
+ * Match a flow to changed files
200
+ */
201
+ function matchFlowToChanges(flow, changes) {
202
+ const reasons = [];
203
+ const affectedFiles = [];
204
+ let matchType = 'path';
205
+ let confidence = 0;
206
+ // Check path matching
207
+ const pathMatchedFiles = changes.filter((change) => flow.paths.some((pattern) => pathMatches(change.file, pattern)));
208
+ if (pathMatchedFiles.length > 0) {
209
+ affectedFiles.push(...pathMatchedFiles.map((c) => c.file));
210
+ confidence = Math.max(confidence, 90);
211
+ matchType = 'path';
212
+ reasons.push(`${pathMatchedFiles.length} file(s) match flow paths: ${pathMatchedFiles.map((c) => c.file).join(', ')}`);
213
+ }
214
+ // Check keyword matching
215
+ const flowKeywords = flow.keywords.map((k) => k.toLowerCase());
216
+ const keywordMatches = [];
217
+ for (const change of changes) {
218
+ const changeContent = [
219
+ ...change.functions,
220
+ ...change.classes,
221
+ ...change.imports.flatMap((i) => [i.from, ...i.names]),
222
+ ]
223
+ .join(' ')
224
+ .toLowerCase();
225
+ for (const keyword of flowKeywords) {
226
+ if (changeContent.includes(keyword)) {
227
+ keywordMatches.push(change.file);
228
+ break;
229
+ }
230
+ }
231
+ }
232
+ if (keywordMatches.length > 0) {
233
+ affectedFiles.push(...keywordMatches.filter((f) => !affectedFiles.includes(f)));
234
+ confidence = Math.max(confidence, keywordMatches.length > 1 ? 75 : 60);
235
+ if (pathMatches.length === 0) {
236
+ matchType = 'keyword';
237
+ }
238
+ else {
239
+ matchType = 'combined';
240
+ }
241
+ reasons.push(`${keywordMatches.length} file(s) contain keywords: ${flowKeywords.slice(0, 3).join(', ')}`);
242
+ }
243
+ // Check import matching
244
+ const importMatches = [];
245
+ const flowPaths = flow.paths.map((p) => p.replace(/\*\*/g, '').toLowerCase());
246
+ for (const change of changes) {
247
+ for (const imp of change.imports) {
248
+ const impFrom = imp.from.toLowerCase();
249
+ for (const flowPath of flowPaths) {
250
+ if (impFrom.includes(flowPath.replace(/[\/\\]/g, ''))) {
251
+ importMatches.push(change.file);
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ }
257
+ if (importMatches.length > 0) {
258
+ affectedFiles.push(...importMatches.filter((f) => !affectedFiles.includes(f)));
259
+ confidence = Math.max(confidence, 50);
260
+ if (pathMatches.length === 0 && keywordMatches.length === 0) {
261
+ matchType = 'import';
262
+ }
263
+ else {
264
+ matchType = 'combined';
265
+ }
266
+ }
267
+ // Return null if no matches
268
+ if (confidence === 0) {
269
+ return null;
270
+ }
271
+ return {
272
+ flow,
273
+ matchType,
274
+ confidence: Math.min(100, confidence),
275
+ affectedFiles: [...new Set(affectedFiles)],
276
+ existingTests: [],
277
+ testGaps: [],
278
+ reasons,
279
+ };
280
+ }
281
+ // =============================================================================
282
+ // TEST ANALYSIS
283
+ // =============================================================================
284
+ /**
285
+ * Find existing tests for a flow
286
+ */
287
+ function findExistingTests(flow, repoRoot) {
288
+ const root = repoRoot || findGitRoot();
289
+ const tests = [];
290
+ // Use tests array from flow definition
291
+ if (flow.tests && flow.tests.length > 0) {
292
+ for (const testPath of flow.tests) {
293
+ const fullPath = (0, path_1.join)(root, testPath);
294
+ if ((0, fs_1.existsSync)(fullPath)) {
295
+ tests.push(testPath);
296
+ }
297
+ }
298
+ }
299
+ return tests;
300
+ }
301
+ /**
302
+ * Identify test coverage gaps for a flow
303
+ */
304
+ function identifyTestGaps(flow, existingTests) {
305
+ const gaps = [];
306
+ // If no existing tests, all scenarios are gaps
307
+ if (existingTests.length === 0) {
308
+ gaps.push(`No existing tests for ${flow.name}`);
309
+ // Suggest specific test scenarios based on audience and keywords
310
+ const audiences = flow.audience || [];
311
+ const keywords = flow.keywords || [];
312
+ if (audiences.length > 0) {
313
+ gaps.push(`Missing scenarios for audiences: ${audiences.slice(0, 2).join(', ')}`);
314
+ }
315
+ if (keywords.length > 0) {
316
+ gaps.push(`Missing tests for: ${keywords.slice(0, 2).join(', ')}`);
317
+ }
318
+ }
319
+ // Check for specific audience coverage
320
+ const audiences = flow.audience || [];
321
+ if (audiences.length > 1) {
322
+ // Suggest multi-audience testing
323
+ gaps.push('Consider testing with different user roles');
324
+ }
325
+ // Check for edge cases based on flow type
326
+ if (flow.keywords.includes('realtime') || flow.keywords.includes('websocket')) {
327
+ gaps.push('Consider testing offline/reconnection scenarios');
328
+ }
329
+ if (flow.keywords.includes('edit') || flow.keywords.includes('delete')) {
330
+ gaps.push('Consider testing permissions/authorization edge cases');
331
+ }
332
+ return gaps;
333
+ }
334
+ // =============================================================================
335
+ // MAIN ANALYSIS
336
+ // =============================================================================
337
+ /**
338
+ * Analyze code impact and return comprehensive report
339
+ */
340
+ async function analyzeImpact(changes, flows, options = {}) {
341
+ const repoRoot = options.repoRoot || findGitRoot();
342
+ // Analyze each changed file
343
+ const analyses = [];
344
+ for (const change of changes) {
345
+ const analysis = analyzeFile(change.path, change.status);
346
+ if (analysis) {
347
+ analyses.push(analysis);
348
+ }
349
+ }
350
+ if (options.verbose) {
351
+ console.log(`📊 Analyzed ${analyses.length} changed files`);
352
+ }
353
+ // Match changes to flows
354
+ const impactedFlows = [];
355
+ for (const flow of flows) {
356
+ const impact = matchFlowToChanges(flow, analyses);
357
+ if (impact && impact.confidence > 0) {
358
+ // Find existing tests
359
+ impact.existingTests = findExistingTests(flow, repoRoot);
360
+ // Identify test gaps
361
+ impact.testGaps = identifyTestGaps(flow, impact.existingTests);
362
+ impactedFlows.push(impact);
363
+ }
364
+ }
365
+ // Sort by priority and confidence
366
+ impactedFlows.sort((a, b) => {
367
+ const priorityOrder = { P0: 0, P1: 1, P2: 2 };
368
+ const aPriority = priorityOrder[a.flow.priority];
369
+ const bPriority = priorityOrder[b.flow.priority];
370
+ if (aPriority !== bPriority) {
371
+ return aPriority - bPriority;
372
+ }
373
+ return b.confidence - a.confidence;
374
+ });
375
+ if (options.verbose) {
376
+ console.log(`🎯 Found ${impactedFlows.length} affected flows`);
377
+ }
378
+ // Generate recommendations
379
+ const recommendations = generateRecommendations(impactedFlows);
380
+ // Group flows by relationships
381
+ const { groups, ungrouped } = groupFlowsByRelationships(impactedFlows, flows);
382
+ return {
383
+ timestamp: new Date().toISOString(),
384
+ gitRef: changes[0]?.ref || 'HEAD',
385
+ totalChanges: changes.length,
386
+ affectedFlows: impactedFlows,
387
+ flowGroups: groups,
388
+ ungroupedFlows: ungrouped,
389
+ priorityBreakdown: {
390
+ p0: impactedFlows.filter((f) => f.flow.priority === 'P0').length,
391
+ p1: impactedFlows.filter((f) => f.flow.priority === 'P1').length,
392
+ p2: impactedFlows.filter((f) => f.flow.priority === 'P2').length,
393
+ },
394
+ testCoverage: {
395
+ total: impactedFlows.length,
396
+ covered: impactedFlows.filter((f) => f.existingTests.length > 0).length,
397
+ gaps: impactedFlows.filter((f) => f.testGaps.length > 0).length,
398
+ },
399
+ recommendations,
400
+ hasP0Impact: impactedFlows.some((f) => f.flow.priority === 'P0'),
401
+ };
402
+ }
403
+ // =============================================================================
404
+ // HELPER FUNCTIONS
405
+ // =============================================================================
406
+ /**
407
+ * Load flow groups from flows.json
408
+ */
409
+ function loadFlowGroups() {
410
+ const possiblePaths = [
411
+ // If running from e2e-tests/playwright directory
412
+ (0, path_1.join)(process.cwd(), '.e2e-ai-agents/flows.json'),
413
+ // If running from monorepo root
414
+ (0, path_1.join)(process.cwd(), 'e2e-tests/playwright/.e2e-ai-agents/flows.json'),
415
+ ];
416
+ const path = possiblePaths.find((p) => (0, fs_1.existsSync)(p));
417
+ if (!path) {
418
+ return {}; // Return empty object if flows.json not found
419
+ }
420
+ try {
421
+ const content = (0, fs_1.readFileSync)(path, 'utf-8');
422
+ const data = JSON.parse(content);
423
+ return data.flowGroups || {};
424
+ }
425
+ catch (error) {
426
+ return {}; // Return empty object on parse error
427
+ }
428
+ }
429
+ /**
430
+ * Group related flows into test journeys based on flowGroup metadata
431
+ */
432
+ function groupFlowsByRelationships(impactedFlows, flowCatalog) {
433
+ const groups = [];
434
+ const grouped = new Set();
435
+ // Load flow group definitions from flows.json
436
+ const flowGroupDefs = loadFlowGroups();
437
+ // For each flow group definition
438
+ for (const [groupId, groupDef] of Object.entries(flowGroupDefs)) {
439
+ const groupFlows = groupDef.flows || [];
440
+ // Find which flows in this group are impacted
441
+ const affectedInGroup = impactedFlows.filter((impact) => groupFlows.includes(impact.flow.id));
442
+ if (affectedInGroup.length > 0) {
443
+ groups.push({
444
+ id: groupId,
445
+ name: groupDef.name || groupId,
446
+ description: groupDef.description || '',
447
+ flows: groupFlows,
448
+ testStrategy: groupDef.testStrategy || 'mixed',
449
+ priority: groupDef.priority || 'P1',
450
+ affectedFlows: affectedInGroup,
451
+ });
452
+ // Mark as grouped
453
+ affectedInGroup.forEach((impact) => grouped.add(impact.flow.id));
454
+ }
455
+ }
456
+ // Remaining ungrouped flows
457
+ const ungrouped = impactedFlows.filter((impact) => !grouped.has(impact.flow.id));
458
+ return { groups, ungrouped };
459
+ }
460
+ /**
461
+ * Analyze a single file to extract functions, classes, and imports
462
+ */
463
+ /**
464
+ * Find the git repository root directory
465
+ */
466
+ function findGitRoot() {
467
+ let currentDir = process.cwd();
468
+ const root = (0, path_1.resolve)('/');
469
+ while (currentDir !== root) {
470
+ if ((0, fs_1.existsSync)((0, path_1.join)(currentDir, '.git'))) {
471
+ return currentDir;
472
+ }
473
+ currentDir = (0, path_1.resolve)(currentDir, '..');
474
+ }
475
+ // Fallback to process.cwd()
476
+ return process.cwd();
477
+ }
478
+ function analyzeFile(filePath, status) {
479
+ const repoRoot = findGitRoot();
480
+ const fullPath = (0, path_1.join)(repoRoot, filePath);
481
+ const stats = getFileDiffStats(filePath);
482
+ // Extract imports, functions, and classes from the file
483
+ const functions = [];
484
+ const classes = [];
485
+ const imports = [];
486
+ try {
487
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf-8');
488
+ // Extract imports (handles: import X from 'Y', import * as X from 'Y', etc.)
489
+ const importRegex = /import\s+(?:{([^}]+)}|(?:\*\s+as\s+)?(\w+))\s+from\s+['"]([^'"]+)['"]/g;
490
+ let importMatch;
491
+ while ((importMatch = importRegex.exec(content)) !== null) {
492
+ const namedImports = importMatch[1]?.split(',').map((s) => s.trim().split(/\s+as\s+/)[0]) || [];
493
+ const defaultImport = importMatch[2] ? [importMatch[2]] : [];
494
+ const allNames = [...namedImports, ...defaultImport].filter(Boolean);
495
+ if (allNames.length > 0) {
496
+ imports.push({
497
+ from: importMatch[3],
498
+ names: allNames,
499
+ });
500
+ }
501
+ }
502
+ // Extract function declarations (handles: function X, const X = () => {}, export function X)
503
+ const functionRegex = /(?:export\s+)?(?:async\s+)?(?:function\s+(\w+)|const\s+(\w+)\s*=)/g;
504
+ let functionMatch;
505
+ const seenFunctions = new Set();
506
+ while ((functionMatch = functionRegex.exec(content)) !== null) {
507
+ const funcName = functionMatch[1] || functionMatch[2];
508
+ if (funcName && !seenFunctions.has(funcName)) {
509
+ functions.push(funcName);
510
+ seenFunctions.add(funcName);
511
+ }
512
+ }
513
+ // Extract class declarations
514
+ const classRegex = /(?:export\s+)?class\s+(\w+)/g;
515
+ let classMatch;
516
+ while ((classMatch = classRegex.exec(content)) !== null) {
517
+ if (classMatch[1]) {
518
+ classes.push(classMatch[1]);
519
+ }
520
+ }
521
+ }
522
+ catch {
523
+ // If file doesn't exist or can't be read, continue with empty arrays
524
+ }
525
+ return {
526
+ file: filePath,
527
+ status: status,
528
+ linesAdded: stats.linesAdded,
529
+ linesRemoved: stats.linesRemoved,
530
+ functions,
531
+ classes,
532
+ imports,
533
+ };
534
+ }
535
+ /**
536
+ * Generate actionable recommendations based on impact
537
+ */
538
+ function generateRecommendations(impactedFlows) {
539
+ const recommendations = [];
540
+ const p0Flows = impactedFlows.filter((f) => f.flow.priority === 'P0');
541
+ const p1Flows = impactedFlows.filter((f) => f.flow.priority === 'P1');
542
+ if (p0Flows.length > 0) {
543
+ recommendations.push(`✅ Run critical (P0) flow tests immediately: ${p0Flows.map((f) => f.flow.id).join(', ')}`);
544
+ }
545
+ if (p1Flows.length > 0) {
546
+ recommendations.push(`🟡 Run high-priority (P1) flow tests: ${p1Flows.map((f) => f.flow.id).join(', ')}`);
547
+ }
548
+ const gapFlows = impactedFlows.filter((f) => f.testGaps.length > 0);
549
+ if (gapFlows.length > 0) {
550
+ recommendations.push(`📝 Generate tests to cover ${gapFlows.length} flow(s) with test gaps`);
551
+ }
552
+ const lowConfidenceFlows = impactedFlows.filter((f) => f.confidence < 60);
553
+ if (lowConfidenceFlows.length > 0) {
554
+ recommendations.push(`🔍 Review ${lowConfidenceFlows.length} flow(s) with low confidence matches (< 60%)`);
555
+ }
556
+ return recommendations;
557
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Agent Module Exports
3
+ *
4
+ * Centralizes all agent-related functionality including:
5
+ * - Impact analysis (detecting affected flows from code changes)
6
+ * - Model routing (intelligent LLM model selection for cost optimization)
7
+ * - Telemetry (metrics collection and reporting)
8
+ * - Report generation (formatted output)
9
+ * - Spec building (test specification generation)
10
+ */
11
+ export { analyzeImpact, detectComparisonBase, getGitChanges, loadFlowCatalog, matchFlowToChanges, findExistingTests, identifyTestGaps, } from './impact-analyzer.js';
12
+ export type { GitChange, ChangeAnalysis, Flow, FlowImpact, FlowGroup, ImpactReport } from './impact-analyzer.js';
13
+ export { ModelRouter } from './model-router.js';
14
+ export type { TaskContext, TaskComplexity, ModelConfig } from './model-router.js';
15
+ export { TelemetryCollector } from './telemetry.js';
16
+ export type { GenerationMetric, TelemetryReport } from './telemetry.js';
17
+ export { generateReports } from './report-generator.js';
18
+ export type { ReportOptions } from './report-generator.js';
19
+ export { SpecBridge, createAnthropicBridge, createOllamaBridge } from './spec-builder.js';
20
+ export type { SpecBridgeConfig, ConversionResult } from './spec-builder.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAGH,OAAO,EACH,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,GACnB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGjH,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGlF,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,38 @@
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.createOllamaBridge = exports.createAnthropicBridge = exports.SpecBridge = exports.generateReports = exports.TelemetryCollector = exports.ModelRouter = exports.identifyTestGaps = exports.findExistingTests = exports.matchFlowToChanges = exports.loadFlowCatalog = exports.getGitChanges = exports.detectComparisonBase = exports.analyzeImpact = void 0;
6
+ /**
7
+ * Agent Module Exports
8
+ *
9
+ * Centralizes all agent-related functionality including:
10
+ * - Impact analysis (detecting affected flows from code changes)
11
+ * - Model routing (intelligent LLM model selection for cost optimization)
12
+ * - Telemetry (metrics collection and reporting)
13
+ * - Report generation (formatted output)
14
+ * - Spec building (test specification generation)
15
+ */
16
+ // Impact Analysis
17
+ var impact_analyzer_js_1 = require("./impact-analyzer.js");
18
+ Object.defineProperty(exports, "analyzeImpact", { enumerable: true, get: function () { return impact_analyzer_js_1.analyzeImpact; } });
19
+ Object.defineProperty(exports, "detectComparisonBase", { enumerable: true, get: function () { return impact_analyzer_js_1.detectComparisonBase; } });
20
+ Object.defineProperty(exports, "getGitChanges", { enumerable: true, get: function () { return impact_analyzer_js_1.getGitChanges; } });
21
+ Object.defineProperty(exports, "loadFlowCatalog", { enumerable: true, get: function () { return impact_analyzer_js_1.loadFlowCatalog; } });
22
+ Object.defineProperty(exports, "matchFlowToChanges", { enumerable: true, get: function () { return impact_analyzer_js_1.matchFlowToChanges; } });
23
+ Object.defineProperty(exports, "findExistingTests", { enumerable: true, get: function () { return impact_analyzer_js_1.findExistingTests; } });
24
+ Object.defineProperty(exports, "identifyTestGaps", { enumerable: true, get: function () { return impact_analyzer_js_1.identifyTestGaps; } });
25
+ // Model Routing (Cost Optimization - TinyDancer pattern)
26
+ var model_router_js_1 = require("./model-router.js");
27
+ Object.defineProperty(exports, "ModelRouter", { enumerable: true, get: function () { return model_router_js_1.ModelRouter; } });
28
+ // Telemetry (Metrics Collection)
29
+ var telemetry_js_1 = require("./telemetry.js");
30
+ Object.defineProperty(exports, "TelemetryCollector", { enumerable: true, get: function () { return telemetry_js_1.TelemetryCollector; } });
31
+ // Report Generation (Console, Markdown, JSON)
32
+ var report_generator_js_1 = require("./report-generator.js");
33
+ Object.defineProperty(exports, "generateReports", { enumerable: true, get: function () { return report_generator_js_1.generateReports; } });
34
+ // Spec Bridge (PDF → Playwright specs)
35
+ var spec_builder_js_1 = require("./spec-builder.js");
36
+ Object.defineProperty(exports, "SpecBridge", { enumerable: true, get: function () { return spec_builder_js_1.SpecBridge; } });
37
+ Object.defineProperty(exports, "createAnthropicBridge", { enumerable: true, get: function () { return spec_builder_js_1.createAnthropicBridge; } });
38
+ Object.defineProperty(exports, "createOllamaBridge", { enumerable: true, get: function () { return spec_builder_js_1.createOllamaBridge; } });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * TinyDancer-Style Model Router
3
+ *
4
+ * Intelligently routes test generation tasks to appropriate LLM models based on complexity:
5
+ * - Simple tasks (healing, validation): Claude Haiku ($0.25/1M tokens) - 40-60% cost reduction
6
+ * - Moderate tasks (test generation): Claude Sonnet ($3/1M tokens) - balanced quality/cost
7
+ * - Complex tasks (critical failures): Claude Opus ($15/1M tokens) - best quality
8
+ *
9
+ * This achieves 40-60% cost reduction by using cheaper models for simple operations.
10
+ */
11
+ export interface TaskComplexity {
12
+ type: 'simple' | 'moderate' | 'complex' | 'critical';
13
+ confidence: number;
14
+ reasoning: string;
15
+ }
16
+ export interface TaskContext {
17
+ operation: 'explore' | 'generate' | 'heal' | 'validate' | 'score';
18
+ attemptNumber?: number;
19
+ codeSize?: number;
20
+ uiMapCoverage?: number;
21
+ errorType?: string;
22
+ previousFailures?: number;
23
+ }
24
+ export interface ModelConfig {
25
+ simpleModel: string;
26
+ moderateModel: string;
27
+ complexModel: string;
28
+ criticalModel: string;
29
+ }
30
+ export declare class ModelRouter {
31
+ private modelConfig;
32
+ constructor(config?: Partial<ModelConfig>);
33
+ /**
34
+ * Classify task complexity based on operation, attempt number, and context
35
+ */
36
+ classifyTask(context: TaskContext): TaskComplexity;
37
+ /**
38
+ * Select appropriate model based on task complexity
39
+ */
40
+ selectModel(complexity: TaskComplexity): string;
41
+ /**
42
+ * Get estimated cost for a task
43
+ */
44
+ estimateCost(complexity: TaskComplexity, estimatedTokens?: number): number;
45
+ /**
46
+ * Get cost savings vs always using Sonnet
47
+ */
48
+ estimateSavings(complexity: TaskComplexity, estimatedTokens?: number): {
49
+ savedCost: number;
50
+ savingsPercent: number;
51
+ };
52
+ /**
53
+ * Format complexity for logging
54
+ */
55
+ formatComplexity(complexity: TaskComplexity, tokensUsed?: number): string;
56
+ }
57
+ //# sourceMappingURL=model-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-router.d.ts","sourceRoot":"","sources":["../../src/agent/model-router.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IACxB,SAAS,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACzB;AAQD,qBAAa,WAAW;IACpB,OAAO,CAAC,WAAW,CAAc;gBAErB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM;IAS7C;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;IA2FlD;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,cAAc,GAAG,MAAM;IAa/C;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,cAAc,EAAE,eAAe,GAAE,MAAa,GAAG,MAAM;IAMhF;;OAEG;IACH,eAAe,CACX,UAAU,EAAE,cAAc,EAC1B,eAAe,GAAE,MAAa,GAC/B;QACC,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;KAC1B;IAaD;;OAEG;IACH,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM;CAS5E"}