@yasserkhanorg/e2e-agents 0.5.16 → 0.7.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.
Files changed (113) hide show
  1. package/dist/agent/pipeline.d.ts +1 -1
  2. package/dist/agent/pipeline.d.ts.map +1 -1
  3. package/dist/agent/plan.d.ts +2 -13
  4. package/dist/agent/plan.d.ts.map +1 -1
  5. package/dist/agent/plan.js +0 -365
  6. package/dist/agent/types.d.ts +42 -0
  7. package/dist/agent/types.d.ts.map +1 -0
  8. package/dist/agent/types.js +4 -0
  9. package/dist/api.d.ts +14 -14
  10. package/dist/api.d.ts.map +1 -1
  11. package/dist/api.js +67 -59
  12. package/dist/cli.js +86 -176
  13. package/dist/engine/ai_enrichment.d.ts +43 -0
  14. package/dist/engine/ai_enrichment.d.ts.map +1 -0
  15. package/dist/engine/ai_enrichment.js +235 -0
  16. package/dist/engine/diff_loader.d.ts +11 -0
  17. package/dist/engine/diff_loader.d.ts.map +1 -0
  18. package/dist/engine/diff_loader.js +74 -0
  19. package/dist/engine/impact_engine.d.ts +36 -0
  20. package/dist/engine/impact_engine.d.ts.map +1 -0
  21. package/dist/engine/impact_engine.js +196 -0
  22. package/dist/engine/plan_builder.d.ts +10 -0
  23. package/dist/engine/plan_builder.d.ts.map +1 -0
  24. package/dist/engine/plan_builder.js +374 -0
  25. package/dist/esm/agent/plan.js +1 -360
  26. package/dist/esm/agent/types.js +3 -0
  27. package/dist/esm/api.js +62 -54
  28. package/dist/esm/cli.js +87 -177
  29. package/dist/esm/engine/ai_enrichment.js +232 -0
  30. package/dist/esm/engine/diff_loader.js +70 -0
  31. package/dist/esm/engine/impact_engine.js +191 -0
  32. package/dist/esm/engine/plan_builder.js +368 -0
  33. package/dist/esm/index.js +6 -3
  34. package/dist/esm/knowledge/route_families.js +59 -1
  35. package/dist/index.d.ts +9 -4
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +14 -5
  38. package/dist/knowledge/route_families.d.ts +19 -0
  39. package/dist/knowledge/route_families.d.ts.map +1 -1
  40. package/dist/knowledge/route_families.js +62 -1
  41. package/package.json +1 -1
  42. package/dist/agent/ai_flow_analysis.d.ts +0 -13
  43. package/dist/agent/ai_flow_analysis.d.ts.map +0 -1
  44. package/dist/agent/ai_flow_analysis.js +0 -334
  45. package/dist/agent/ai_mapping.d.ts +0 -14
  46. package/dist/agent/ai_mapping.d.ts.map +0 -1
  47. package/dist/agent/ai_mapping.js +0 -560
  48. package/dist/agent/analysis.d.ts +0 -64
  49. package/dist/agent/analysis.d.ts.map +0 -1
  50. package/dist/agent/analysis.js +0 -292
  51. package/dist/agent/blast_radius.d.ts +0 -4
  52. package/dist/agent/blast_radius.d.ts.map +0 -1
  53. package/dist/agent/blast_radius.js +0 -37
  54. package/dist/agent/dependency_graph.d.ts +0 -14
  55. package/dist/agent/dependency_graph.d.ts.map +0 -1
  56. package/dist/agent/dependency_graph.js +0 -227
  57. package/dist/agent/flags.d.ts +0 -23
  58. package/dist/agent/flags.d.ts.map +0 -1
  59. package/dist/agent/flags.js +0 -171
  60. package/dist/agent/flow_catalog.d.ts +0 -25
  61. package/dist/agent/flow_catalog.d.ts.map +0 -1
  62. package/dist/agent/flow_catalog.js +0 -115
  63. package/dist/agent/flow_mapping.d.ts +0 -10
  64. package/dist/agent/flow_mapping.d.ts.map +0 -1
  65. package/dist/agent/flow_mapping.js +0 -84
  66. package/dist/agent/framework.d.ts +0 -13
  67. package/dist/agent/framework.d.ts.map +0 -1
  68. package/dist/agent/framework.js +0 -149
  69. package/dist/agent/gap_suggestions.d.ts +0 -14
  70. package/dist/agent/gap_suggestions.d.ts.map +0 -1
  71. package/dist/agent/gap_suggestions.js +0 -101
  72. package/dist/agent/generator.d.ts +0 -10
  73. package/dist/agent/generator.d.ts.map +0 -1
  74. package/dist/agent/generator.js +0 -115
  75. package/dist/agent/operational_insights.d.ts +0 -41
  76. package/dist/agent/operational_insights.d.ts.map +0 -1
  77. package/dist/agent/operational_insights.js +0 -127
  78. package/dist/agent/report.d.ts +0 -97
  79. package/dist/agent/report.d.ts.map +0 -1
  80. package/dist/agent/report.js +0 -159
  81. package/dist/agent/runner.d.ts +0 -7
  82. package/dist/agent/runner.d.ts.map +0 -1
  83. package/dist/agent/runner.js +0 -898
  84. package/dist/agent/selectors.d.ts +0 -10
  85. package/dist/agent/selectors.d.ts.map +0 -1
  86. package/dist/agent/selectors.js +0 -75
  87. package/dist/agent/subsystem_risk.d.ts +0 -23
  88. package/dist/agent/subsystem_risk.d.ts.map +0 -1
  89. package/dist/agent/subsystem_risk.js +0 -207
  90. package/dist/agent/tests.d.ts +0 -19
  91. package/dist/agent/tests.d.ts.map +0 -1
  92. package/dist/agent/tests.js +0 -116
  93. package/dist/agent/traceability.d.ts +0 -22
  94. package/dist/agent/traceability.d.ts.map +0 -1
  95. package/dist/agent/traceability.js +0 -183
  96. package/dist/esm/agent/ai_flow_analysis.js +0 -331
  97. package/dist/esm/agent/ai_mapping.js +0 -557
  98. package/dist/esm/agent/analysis.js +0 -287
  99. package/dist/esm/agent/blast_radius.js +0 -34
  100. package/dist/esm/agent/dependency_graph.js +0 -224
  101. package/dist/esm/agent/flags.js +0 -160
  102. package/dist/esm/agent/flow_catalog.js +0 -112
  103. package/dist/esm/agent/flow_mapping.js +0 -81
  104. package/dist/esm/agent/framework.js +0 -145
  105. package/dist/esm/agent/gap_suggestions.js +0 -98
  106. package/dist/esm/agent/generator.js +0 -112
  107. package/dist/esm/agent/operational_insights.js +0 -124
  108. package/dist/esm/agent/report.js +0 -156
  109. package/dist/esm/agent/runner.js +0 -894
  110. package/dist/esm/agent/selectors.js +0 -71
  111. package/dist/esm/agent/subsystem_risk.js +0 -204
  112. package/dist/esm/agent/tests.js +0 -111
  113. package/dist/esm/agent/traceability.js +0 -180
@@ -1,331 +0,0 @@
1
- // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
- // See LICENSE.txt for license information.
3
- import { existsSync, readFileSync } from 'fs';
4
- import { isAbsolute, join } from 'path';
5
- import { LLMProviderFactory } from '../provider_factory.js';
6
- import { normalizePath, tokenize, uniqueTokens } from './utils.js';
7
- function extractJson(text) {
8
- const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
9
- const candidates = fenced ? [fenced[1], text] : [text];
10
- for (const candidate of candidates) {
11
- const start = candidate.indexOf('{');
12
- const end = candidate.lastIndexOf('}');
13
- if (start < 0 || end <= start) {
14
- continue;
15
- }
16
- const raw = candidate.slice(start, end + 1);
17
- try {
18
- const parsed = JSON.parse(raw);
19
- if (parsed && Array.isArray(parsed.flows)) {
20
- return parsed;
21
- }
22
- }
23
- catch {
24
- // Ignore parse failure and keep trying other candidates.
25
- }
26
- }
27
- return null;
28
- }
29
- function resolveContextFiles(appRoot, testsRoot, files) {
30
- const resolved = [];
31
- const seen = new Set();
32
- const maxCharsPerFile = 12000;
33
- const maxTotalChars = 32000;
34
- let totalChars = 0;
35
- for (const file of files) {
36
- const candidates = isAbsolute(file)
37
- ? [file]
38
- : [join(testsRoot, file), join(appRoot, file)];
39
- for (const candidate of candidates) {
40
- const normalized = normalizePath(candidate);
41
- if (seen.has(normalized) || !existsSync(candidate)) {
42
- continue;
43
- }
44
- const content = readFileSync(candidate, 'utf-8');
45
- const trimmed = content.trim();
46
- if (!trimmed) {
47
- seen.add(normalized);
48
- continue;
49
- }
50
- const remaining = Math.max(0, maxTotalChars - totalChars);
51
- if (remaining <= 0) {
52
- return resolved;
53
- }
54
- const clipped = trimmed.slice(0, Math.min(maxCharsPerFile, remaining));
55
- resolved.push({ path: normalized, content: clipped });
56
- seen.add(normalized);
57
- totalChars += clipped.length;
58
- break;
59
- }
60
- }
61
- return resolved;
62
- }
63
- function priorityFromEntry(entry) {
64
- if (entry.priority === 'P0' || entry.priority === 'P1' || entry.priority === 'P2') {
65
- return entry.priority;
66
- }
67
- const score = typeof entry.score === 'number' ? entry.score : 0;
68
- if (score >= 8) {
69
- return 'P0';
70
- }
71
- if (score >= 5) {
72
- return 'P1';
73
- }
74
- return 'P2';
75
- }
76
- function normalizeFlowId(value) {
77
- return normalizePath(value)
78
- .replace(/[^a-zA-Z0-9/_-]+/g, '_')
79
- .replace(/\/{2,}/g, '/')
80
- .replace(/^\/+/, '')
81
- .replace(/\/+$/, '')
82
- .trim();
83
- }
84
- function sanitizeReasons(reasons, fallback) {
85
- if (!Array.isArray(reasons)) {
86
- return [fallback];
87
- }
88
- const cleaned = reasons.filter((entry) => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
89
- return cleaned.length > 0 ? cleaned : [fallback];
90
- }
91
- function sanitizeKeywords(keywords, fallbackTokens) {
92
- if (!Array.isArray(keywords)) {
93
- return uniqueTokens(fallbackTokens).slice(0, 20);
94
- }
95
- const fromAI = keywords.filter((entry) => typeof entry === 'string').flatMap((entry) => tokenize(entry));
96
- return uniqueTokens([...fromAI, ...fallbackTokens]).slice(0, 20);
97
- }
98
- function summarizeFiles(files, changedFileSet, maxFiles) {
99
- const sorted = [...files].sort((a, b) => {
100
- const aChanged = changedFileSet.has(a.relativePath) ? 1 : 0;
101
- const bChanged = changedFileSet.has(b.relativePath) ? 1 : 0;
102
- if (aChanged !== bChanged) {
103
- return bChanged - aChanged;
104
- }
105
- const aSignals = (a.isUI ? 1 : 0) + (a.isScreen ? 1 : 0) + (a.isState ? 1 : 0) + (a.hasInteractions ? 1 : 0);
106
- const bSignals = (b.isUI ? 1 : 0) + (b.isScreen ? 1 : 0) + (b.isState ? 1 : 0) + (b.hasInteractions ? 1 : 0);
107
- if (aSignals !== bSignals) {
108
- return bSignals - aSignals;
109
- }
110
- return a.relativePath.localeCompare(b.relativePath);
111
- }).slice(0, Math.max(20, maxFiles));
112
- return sorted.map((file) => ({
113
- path: file.relativePath,
114
- changed: changedFileSet.has(file.relativePath),
115
- isUI: file.isUI,
116
- isScreen: file.isScreen,
117
- isComponent: file.isComponent,
118
- isState: file.isState,
119
- isStyle: file.isStyle,
120
- hasInteractions: file.hasInteractions,
121
- keywords: file.keywords.slice(0, 20),
122
- audience: file.audience,
123
- flags: file.flags?.map((flag) => ({ name: flag.name, source: flag.source, defaultState: flag.defaultState })),
124
- }));
125
- }
126
- function mergeFlow(existing, candidate) {
127
- if (!existing) {
128
- return candidate;
129
- }
130
- const priorityOrder = { P0: 0, P1: 1, P2: 2 };
131
- const priority = priorityOrder[candidate.priority] < priorityOrder[existing.priority]
132
- ? candidate.priority
133
- : existing.priority;
134
- return {
135
- ...existing,
136
- name: existing.name || candidate.name,
137
- kind: existing.kind || candidate.kind,
138
- score: Math.max(existing.score, candidate.score),
139
- priority,
140
- reasons: uniqueTokens([...(existing.reasons || []), ...(candidate.reasons || [])]),
141
- keywords: uniqueTokens([...(existing.keywords || []), ...(candidate.keywords || [])]),
142
- files: uniqueTokens([...(existing.files || []), ...(candidate.files || [])]),
143
- audience: uniqueTokens([...(existing.audience || []), ...(candidate.audience || [])]),
144
- flags: [...(existing.flags || []), ...(candidate.flags || [])],
145
- };
146
- }
147
- export async function mapAIFlowsFromFiles(appRoot, testsRoot, config, files, changedFiles) {
148
- const providerName = config.provider === 'auto' ? 'auto' : config.provider;
149
- const warnings = [];
150
- if (!config.enabled) {
151
- return {
152
- enabled: false,
153
- used: false,
154
- ran: false,
155
- provider: providerName,
156
- flowCount: 0,
157
- warnings,
158
- flows: [],
159
- };
160
- }
161
- if (files.length === 0) {
162
- warnings.push('AI flow analysis skipped: no analyzable files were found.');
163
- return {
164
- enabled: true,
165
- used: false,
166
- ran: false,
167
- provider: providerName,
168
- flowCount: 0,
169
- warnings,
170
- flows: [],
171
- };
172
- }
173
- const changedFileSet = new Set(changedFiles.map((entry) => normalizePath(entry)));
174
- const summarizedFiles = summarizeFiles(files, changedFileSet, config.maxFilesPerRequest);
175
- const allowedFiles = new Set(files.map((entry) => entry.relativePath));
176
- const fileByPath = new Map(files.map((entry) => [entry.relativePath, entry]));
177
- const contextFiles = resolveContextFiles(appRoot, testsRoot, config.contextFiles || []);
178
- const contextBlock = contextFiles.length > 0
179
- ? contextFiles.map((entry) => `### Context: ${entry.path}\n${entry.content}`).join('\n\n')
180
- : 'No optional markdown context files were found.';
181
- if (contextFiles.length === 0) {
182
- warnings.push('AI flow analysis context files were not found; continuing without optional markdown context.');
183
- }
184
- let provider;
185
- try {
186
- provider = config.provider === 'auto'
187
- ? await LLMProviderFactory.createFromEnv()
188
- : LLMProviderFactory.createFromString(config.provider);
189
- }
190
- catch (error) {
191
- const message = error instanceof Error ? error.message : String(error);
192
- warnings.push(`AI flow analysis unavailable (${providerName}): ${message}`);
193
- return {
194
- enabled: true,
195
- used: false,
196
- ran: false,
197
- provider: providerName,
198
- flowCount: 0,
199
- warnings,
200
- flows: [],
201
- };
202
- }
203
- const prompt = [
204
- 'You are an expert frontend impact analyst for Mattermost.',
205
- 'Build impacted user flows from changed frontend files.',
206
- 'This must be flow-centric, not file-centric.',
207
- '',
208
- 'Return strict JSON only with this exact shape:',
209
- '{"flows":[{"id":"<flow_id>","name":"<name>","kind":"flow|screen","priority":"P0|P1|P2","score":10,"reasons":["..."],"keywords":["..."],"files":["relative/path.tsx"]}]}',
210
- '',
211
- 'Rules:',
212
- '- Use only file paths listed in FILES.',
213
- '- Every flow must have at least one file.',
214
- '- Keep IDs stable and lowercase with underscores when possible.',
215
- '- Prioritize true user-impacting flows; avoid low-value internal buckets.',
216
- '- Keep at most 6 file paths per flow.',
217
- `- Keep at most ${Math.max(1, config.maxFlowsPerRequest)} flows.`,
218
- '',
219
- `CHANGED_FILES (${changedFileSet.size}):`,
220
- JSON.stringify(Array.from(changedFileSet), null, 2),
221
- '',
222
- `FILES (${summarizedFiles.length}):`,
223
- JSON.stringify(summarizedFiles, null, 2),
224
- '',
225
- contextBlock,
226
- ].join('\n');
227
- let parsed = null;
228
- try {
229
- const response = await provider.generateText(prompt, {
230
- maxTokens: Math.max(800, config.maxTokens),
231
- temperature: Math.max(0, Math.min(1, config.temperature)),
232
- timeout: 45000,
233
- systemPrompt: 'Return only valid JSON. Do not include markdown fences unless necessary.',
234
- });
235
- parsed = extractJson(response.text);
236
- }
237
- catch (error) {
238
- const message = error instanceof Error ? error.message : String(error);
239
- warnings.push(`AI flow analysis request failed (${provider.name}): ${message}`);
240
- return {
241
- enabled: true,
242
- used: false,
243
- ran: false,
244
- provider: provider.name,
245
- flowCount: 0,
246
- warnings,
247
- flows: [],
248
- };
249
- }
250
- if (!parsed) {
251
- warnings.push(`AI flow analysis returned invalid JSON (${provider.name}).`);
252
- return {
253
- enabled: true,
254
- used: false,
255
- ran: false,
256
- provider: provider.name,
257
- flowCount: 0,
258
- warnings,
259
- flows: [],
260
- };
261
- }
262
- const flowsById = new Map();
263
- for (const entry of parsed.flows) {
264
- if (!entry || !Array.isArray(entry.files)) {
265
- continue;
266
- }
267
- const validFiles = Array.from(new Set(entry.files
268
- .filter((value) => typeof value === 'string')
269
- .map((value) => normalizePath(value))
270
- .filter((value) => allowedFiles.has(value)))).slice(0, 6);
271
- if (validFiles.length === 0) {
272
- continue;
273
- }
274
- const rawId = typeof entry.id === 'string' && entry.id.trim()
275
- ? entry.id
276
- : (typeof entry.name === 'string' && entry.name.trim() ? entry.name : validFiles[0]);
277
- const id = normalizeFlowId(rawId);
278
- if (!id) {
279
- continue;
280
- }
281
- const fallbackTokens = uniqueTokens([
282
- ...tokenize(id),
283
- ...(typeof entry.name === 'string' ? tokenize(entry.name) : []),
284
- ...validFiles.flatMap((value) => tokenize(value)),
285
- ]);
286
- const linkedFiles = validFiles.map((path) => fileByPath.get(path)).filter(Boolean);
287
- const audience = uniqueTokens(linkedFiles.flatMap((file) => file.audience || []));
288
- const flags = linkedFiles.flatMap((file) => file.flags || []);
289
- const score = typeof entry.score === 'number' && Number.isFinite(entry.score)
290
- ? Math.max(1, Math.min(20, Math.round(entry.score)))
291
- : Math.max(4, validFiles.length * 2);
292
- const flow = {
293
- id,
294
- name: typeof entry.name === 'string' && entry.name.trim() ? entry.name.trim() : id.replace(/[_/.-]+/g, ' '),
295
- kind: entry.kind === 'screen' ? 'screen' : 'flow',
296
- score,
297
- priority: priorityFromEntry(entry),
298
- reasons: sanitizeReasons(entry.reasons, 'AI flow analysis identified impacted behavior'),
299
- keywords: sanitizeKeywords(entry.keywords, fallbackTokens),
300
- files: validFiles,
301
- audience,
302
- flags,
303
- };
304
- flowsById.set(id, mergeFlow(flowsById.get(id), flow));
305
- if (flowsById.size >= Math.max(1, config.maxFlowsPerRequest)) {
306
- break;
307
- }
308
- }
309
- const flows = Array.from(flowsById.values());
310
- if (flows.length === 0) {
311
- warnings.push('AI flow analysis did not return any valid flows linked to changed files.');
312
- return {
313
- enabled: true,
314
- used: false,
315
- ran: true, // AI was called and responded — no user-facing flows impacted
316
- provider: provider.name,
317
- flowCount: 0,
318
- warnings,
319
- flows: [],
320
- };
321
- }
322
- return {
323
- enabled: true,
324
- used: true,
325
- ran: true,
326
- provider: provider.name,
327
- flowCount: flows.length,
328
- warnings,
329
- flows,
330
- };
331
- }