@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,180 @@
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 { normalizePath } from './utils.js';
6
+ function ratio(numerator, denominator) {
7
+ if (denominator <= 0) {
8
+ return 0;
9
+ }
10
+ return Number((numerator / denominator).toFixed(4));
11
+ }
12
+ function resolveManifestPath(root, configuredPath) {
13
+ if (isAbsolute(configuredPath)) {
14
+ return configuredPath;
15
+ }
16
+ return join(root, configuredPath);
17
+ }
18
+ function safeReadManifest(path) {
19
+ if (!existsSync(path)) {
20
+ return null;
21
+ }
22
+ try {
23
+ return JSON.parse(readFileSync(path, 'utf-8'));
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function normalizeFiles(files) {
30
+ if (!files) {
31
+ return [];
32
+ }
33
+ return files.map((file) => normalizePath(file)).filter(Boolean);
34
+ }
35
+ function normalizeTests(tests) {
36
+ if (!tests) {
37
+ return [];
38
+ }
39
+ return tests.map((test) => normalizePath(test)).filter(Boolean);
40
+ }
41
+ function buildFileToTestsMap(manifest) {
42
+ const fileToTests = new Map();
43
+ const setMapping = (file, test) => {
44
+ if (!file || !test) {
45
+ return;
46
+ }
47
+ const key = normalizePath(file);
48
+ const value = normalizePath(test);
49
+ if (!fileToTests.has(key)) {
50
+ fileToTests.set(key, new Set());
51
+ }
52
+ fileToTests.get(key)?.add(value);
53
+ };
54
+ if (manifest.tests) {
55
+ for (const entry of manifest.tests) {
56
+ const files = normalizeFiles(entry.touchedFiles);
57
+ for (const file of files) {
58
+ setMapping(file, entry.test);
59
+ }
60
+ }
61
+ }
62
+ if (manifest.fileToTests) {
63
+ for (const [file, tests] of Object.entries(manifest.fileToTests)) {
64
+ const normalizedTests = normalizeTests(tests);
65
+ for (const test of normalizedTests) {
66
+ setMapping(file, test);
67
+ }
68
+ }
69
+ }
70
+ if (manifest.mappings) {
71
+ for (const entry of manifest.mappings) {
72
+ const normalizedTests = normalizeTests(entry.tests);
73
+ for (const test of normalizedTests) {
74
+ setMapping(entry.file, test);
75
+ }
76
+ }
77
+ }
78
+ return fileToTests;
79
+ }
80
+ export function mapTraceabilityToFlows(appRoot, config, flows) {
81
+ const manifestPath = resolveManifestPath(appRoot, config.manifestPath);
82
+ const warnings = [];
83
+ const fallbackStats = {
84
+ source: 'manifest',
85
+ enabled: config.enabled,
86
+ manifestPath,
87
+ manifestFound: false,
88
+ manifestTests: 0,
89
+ manifestEdges: 0,
90
+ matchedFlows: 0,
91
+ totalFlows: flows.length,
92
+ matchedTests: 0,
93
+ coverageRatio: 0,
94
+ };
95
+ if (!config.enabled) {
96
+ return {
97
+ coverage: [],
98
+ warnings,
99
+ stats: fallbackStats,
100
+ };
101
+ }
102
+ const manifest = safeReadManifest(manifestPath);
103
+ if (!manifest) {
104
+ warnings.push(`Traceability manifest not found or invalid: ${manifestPath}`);
105
+ return {
106
+ coverage: [],
107
+ warnings,
108
+ stats: fallbackStats,
109
+ };
110
+ }
111
+ const fileToTests = buildFileToTestsMap(manifest);
112
+ let manifestEdges = 0;
113
+ for (const tests of fileToTests.values()) {
114
+ manifestEdges += tests.size;
115
+ }
116
+ const manifestTests = new Set();
117
+ const coverage = [];
118
+ const matchedTests = new Set();
119
+ let matchedFlows = 0;
120
+ for (const tests of fileToTests.values()) {
121
+ for (const test of tests) {
122
+ manifestTests.add(test);
123
+ }
124
+ }
125
+ for (const flow of flows) {
126
+ const signalCounts = new Map();
127
+ const files = flow.files.map((file) => normalizePath(file));
128
+ for (const file of files) {
129
+ const tests = fileToTests.get(file);
130
+ if (!tests) {
131
+ continue;
132
+ }
133
+ for (const test of tests) {
134
+ signalCounts.set(test, (signalCounts.get(test) || 0) + 1);
135
+ }
136
+ }
137
+ const coveredBy = Array.from(signalCounts.entries())
138
+ .filter(([, count]) => count >= Math.max(1, config.minSignalsPerTest))
139
+ .map(([test]) => test)
140
+ .sort();
141
+ const score = Array.from(signalCounts.values()).reduce((acc, value) => acc + value, 0);
142
+ if (coveredBy.length > 0) {
143
+ matchedFlows += 1;
144
+ for (const test of coveredBy) {
145
+ matchedTests.add(test);
146
+ }
147
+ }
148
+ coverage.push({
149
+ flowId: flow.id,
150
+ flowName: flow.name,
151
+ priority: flow.priority,
152
+ coveredBy,
153
+ score,
154
+ source: 'traceability',
155
+ });
156
+ }
157
+ const stats = {
158
+ source: 'manifest',
159
+ enabled: config.enabled,
160
+ manifestPath,
161
+ manifestFound: true,
162
+ manifestTests: manifestTests.size,
163
+ manifestEdges,
164
+ matchedFlows,
165
+ totalFlows: flows.length,
166
+ matchedTests: matchedTests.size,
167
+ coverageRatio: ratio(matchedFlows, flows.length),
168
+ };
169
+ if (manifestEdges === 0) {
170
+ warnings.push(`Traceability manifest has no file-to-test mappings: ${manifestPath}`);
171
+ }
172
+ else if (stats.coverageRatio < 0.4) {
173
+ warnings.push(`Traceability coverage is low (${stats.matchedFlows}/${stats.totalFlows} flows mapped). Recommendations may require heuristic fallback.`);
174
+ }
175
+ return {
176
+ coverage,
177
+ warnings,
178
+ stats,
179
+ };
180
+ }
@@ -0,0 +1,310 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4
+ import { dirname, isAbsolute, resolve } from 'path';
5
+ import { getChangedFiles } from './git.js';
6
+ import { normalizePath } from './utils.js';
7
+ function asRecord(value) {
8
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
9
+ return null;
10
+ }
11
+ return value;
12
+ }
13
+ function asArray(value) {
14
+ return Array.isArray(value) ? value : [];
15
+ }
16
+ function resolveFilePath(cwd, value) {
17
+ if (isAbsolute(value)) {
18
+ return value;
19
+ }
20
+ return resolve(cwd, value);
21
+ }
22
+ function normalizeList(values) {
23
+ return Array.from(new Set(values
24
+ .map((value) => normalizePath(value))
25
+ .filter(Boolean)));
26
+ }
27
+ function parseStringArray(value) {
28
+ return normalizeList(asArray(value)
29
+ .filter((item) => typeof item === 'string')
30
+ .map((item) => item));
31
+ }
32
+ function isExecutedStatus(status) {
33
+ return status !== 'skipped';
34
+ }
35
+ function specExecuted(spec) {
36
+ const tests = asArray(spec.tests);
37
+ if (tests.length === 0) {
38
+ return false;
39
+ }
40
+ for (const testValue of tests) {
41
+ const testNode = asRecord(testValue);
42
+ if (!testNode) {
43
+ continue;
44
+ }
45
+ const testStatus = typeof testNode.status === 'string' ? testNode.status : undefined;
46
+ if (testStatus && isExecutedStatus(testStatus)) {
47
+ return true;
48
+ }
49
+ const outcome = typeof testNode.outcome === 'string' ? testNode.outcome : undefined;
50
+ if (outcome && isExecutedStatus(outcome)) {
51
+ return true;
52
+ }
53
+ const results = asArray(testNode.results);
54
+ for (const resultValue of results) {
55
+ const resultNode = asRecord(resultValue);
56
+ if (!resultNode) {
57
+ continue;
58
+ }
59
+ const status = typeof resultNode.status === 'string' ? resultNode.status : undefined;
60
+ if (status && isExecutedStatus(status)) {
61
+ return true;
62
+ }
63
+ }
64
+ }
65
+ return false;
66
+ }
67
+ function relativizePath(path, roots) {
68
+ const normalized = normalizePath(path);
69
+ for (const root of roots) {
70
+ const normalizedRoot = normalizePath(resolve(root));
71
+ if (normalized === normalizedRoot) {
72
+ return '.';
73
+ }
74
+ if (normalized.startsWith(`${normalizedRoot}/`)) {
75
+ return normalized.slice(normalizedRoot.length + 1);
76
+ }
77
+ }
78
+ return normalized;
79
+ }
80
+ function collectExecutedSpecs(value, roots, output) {
81
+ const node = asRecord(value);
82
+ if (!node) {
83
+ return;
84
+ }
85
+ const specs = asArray(node.specs);
86
+ for (const specValue of specs) {
87
+ const specNode = asRecord(specValue);
88
+ if (!specNode) {
89
+ continue;
90
+ }
91
+ const file = typeof specNode.file === 'string' ? specNode.file : '';
92
+ if (!file) {
93
+ continue;
94
+ }
95
+ if (!specExecuted(specNode)) {
96
+ continue;
97
+ }
98
+ output.add(relativizePath(file, roots));
99
+ }
100
+ const suites = asArray(node.suites);
101
+ for (const suite of suites) {
102
+ collectExecutedSpecs(suite, roots, output);
103
+ }
104
+ }
105
+ function loadPlaywrightExecutedSpecs(reportPath, roots) {
106
+ const raw = JSON.parse(readFileSync(reportPath, 'utf-8'));
107
+ const specs = new Set();
108
+ collectExecutedSpecs(raw, roots, specs);
109
+ return Array.from(specs).sort();
110
+ }
111
+ function loadChangedFilesFromPath(filePath) {
112
+ const rawText = readFileSync(filePath, 'utf-8');
113
+ const trimmed = rawText.trim();
114
+ if (!trimmed) {
115
+ return [];
116
+ }
117
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
118
+ const parsed = JSON.parse(trimmed);
119
+ if (Array.isArray(parsed)) {
120
+ return normalizeList(parsed.filter((value) => typeof value === 'string'));
121
+ }
122
+ const node = asRecord(parsed);
123
+ if (!node) {
124
+ return [];
125
+ }
126
+ if (Array.isArray(node.files)) {
127
+ return normalizeList(node.files.filter((value) => typeof value === 'string'));
128
+ }
129
+ if (Array.isArray(node.changedFiles)) {
130
+ return normalizeList(node.changedFiles.filter((value) => typeof value === 'string'));
131
+ }
132
+ return [];
133
+ }
134
+ return normalizeList(rawText
135
+ .split('\n')
136
+ .map((line) => line.trim())
137
+ .filter(Boolean));
138
+ }
139
+ function addCoverageEntry(map, test, files) {
140
+ if (!test || files.length === 0) {
141
+ return;
142
+ }
143
+ const normalizedTest = normalizePath(test);
144
+ if (!map.has(normalizedTest)) {
145
+ map.set(normalizedTest, new Set());
146
+ }
147
+ const bucket = map.get(normalizedTest);
148
+ for (const file of files) {
149
+ bucket?.add(normalizePath(file));
150
+ }
151
+ }
152
+ function loadCoverageMap(path) {
153
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
154
+ const map = new Map();
155
+ const appendTestEntry = (testValue, filesValue) => {
156
+ if (typeof testValue !== 'string') {
157
+ return;
158
+ }
159
+ const files = parseStringArray(filesValue);
160
+ addCoverageEntry(map, testValue, files);
161
+ };
162
+ if (Array.isArray(raw)) {
163
+ for (const entry of raw) {
164
+ const node = asRecord(entry);
165
+ if (!node) {
166
+ continue;
167
+ }
168
+ appendTestEntry(node.test, node.touchedFiles);
169
+ }
170
+ return map;
171
+ }
172
+ const node = asRecord(raw);
173
+ if (!node) {
174
+ return map;
175
+ }
176
+ for (const entry of asArray(node.tests)) {
177
+ const testNode = asRecord(entry);
178
+ if (!testNode) {
179
+ continue;
180
+ }
181
+ appendTestEntry(testNode.test, Array.isArray(testNode.touchedFiles) ? testNode.touchedFiles : testNode.files);
182
+ }
183
+ for (const entry of asArray(node.runs)) {
184
+ const runNode = asRecord(entry);
185
+ if (!runNode) {
186
+ continue;
187
+ }
188
+ const files = Array.isArray(runNode.touchedFiles)
189
+ ? runNode.touchedFiles
190
+ : (Array.isArray(runNode.coveredFiles) ? runNode.coveredFiles : runNode.files);
191
+ appendTestEntry(runNode.test, files);
192
+ }
193
+ const mappings = asArray(node.mappings);
194
+ for (const mapping of mappings) {
195
+ const mappingNode = asRecord(mapping);
196
+ if (!mappingNode || typeof mappingNode.file !== 'string' || !Array.isArray(mappingNode.tests)) {
197
+ continue;
198
+ }
199
+ const normalizedFile = normalizePath(mappingNode.file);
200
+ for (const test of mappingNode.tests) {
201
+ if (typeof test !== 'string') {
202
+ continue;
203
+ }
204
+ addCoverageEntry(map, test, [normalizedFile]);
205
+ }
206
+ }
207
+ const fileToTests = asRecord(node.fileToTests);
208
+ if (fileToTests) {
209
+ for (const [file, tests] of Object.entries(fileToTests)) {
210
+ if (!Array.isArray(tests)) {
211
+ continue;
212
+ }
213
+ for (const test of tests) {
214
+ if (typeof test !== 'string') {
215
+ continue;
216
+ }
217
+ addCoverageEntry(map, test, [normalizePath(file)]);
218
+ }
219
+ }
220
+ }
221
+ return map;
222
+ }
223
+ function coverageForSpec(specPath, coverageMap) {
224
+ const normalizedSpec = normalizePath(specPath);
225
+ const files = new Set();
226
+ const direct = coverageMap.get(normalizedSpec);
227
+ if (direct) {
228
+ for (const file of direct) {
229
+ files.add(file);
230
+ }
231
+ }
232
+ const prefix = `${normalizedSpec}#`;
233
+ for (const [key, value] of coverageMap.entries()) {
234
+ if (key.startsWith(prefix)) {
235
+ for (const file of value) {
236
+ files.add(file);
237
+ }
238
+ }
239
+ }
240
+ return Array.from(files).sort();
241
+ }
242
+ export function captureTraceabilityInput(options) {
243
+ const warnings = [];
244
+ const reportPath = resolveFilePath(process.cwd(), options.reportPath);
245
+ if (!existsSync(reportPath)) {
246
+ throw new Error(`Traceability report not found: ${reportPath}`);
247
+ }
248
+ const roots = [options.testsRoot, options.appPath].map((root) => resolve(root));
249
+ const executedSpecs = loadPlaywrightExecutedSpecs(reportPath, roots);
250
+ if (executedSpecs.length === 0) {
251
+ warnings.push('No executed tests found in Playwright report.');
252
+ }
253
+ let changedFiles = [];
254
+ if (options.changedFilesPath) {
255
+ const changedPath = resolveFilePath(process.cwd(), options.changedFilesPath);
256
+ if (existsSync(changedPath)) {
257
+ changedFiles = loadChangedFilesFromPath(changedPath);
258
+ }
259
+ else {
260
+ warnings.push(`Changed files path not found: ${changedPath}`);
261
+ }
262
+ }
263
+ else {
264
+ const diff = getChangedFiles(options.appPath, options.sinceRef, { includeUncommitted: false });
265
+ if (diff.error) {
266
+ warnings.push(`Git diff failed while building traceability input: ${diff.error}`);
267
+ }
268
+ changedFiles = diff.files;
269
+ }
270
+ changedFiles = normalizeList(changedFiles);
271
+ let coverageMap = new Map();
272
+ if (options.coverageMapPath) {
273
+ const coveragePath = resolveFilePath(process.cwd(), options.coverageMapPath);
274
+ if (existsSync(coveragePath)) {
275
+ coverageMap = loadCoverageMap(coveragePath);
276
+ }
277
+ else {
278
+ warnings.push(`Coverage map path not found: ${coveragePath}`);
279
+ }
280
+ }
281
+ const runs = executedSpecs.map((spec) => {
282
+ const mappedFiles = coverageForSpec(spec, coverageMap);
283
+ const touchedFiles = mappedFiles.length > 0 ? mappedFiles : changedFiles;
284
+ return {
285
+ test: spec,
286
+ touchedFiles,
287
+ timestamp: new Date().toISOString(),
288
+ };
289
+ }).filter((entry) => entry.touchedFiles.length > 0);
290
+ if (runs.length < executedSpecs.length && changedFiles.length === 0) {
291
+ warnings.push('Some executed tests had no coverage-map entries and no changed-files fallback.');
292
+ }
293
+ const outputPath = options.outputPath
294
+ ? resolveFilePath(process.cwd(), options.outputPath)
295
+ : resolve(options.testsRoot, '.e2e-ai-agents', 'traceability-input.json');
296
+ mkdirSync(dirname(outputPath), { recursive: true });
297
+ writeFileSync(outputPath, JSON.stringify({
298
+ schemaVersion: '1.0.0',
299
+ source: 'traceability-capture',
300
+ generatedAt: new Date().toISOString(),
301
+ runs,
302
+ }, null, 2), 'utf-8');
303
+ return {
304
+ outputPath,
305
+ testsSeen: executedSpecs.length,
306
+ runsGenerated: runs.length,
307
+ changedFilesUsed: changedFiles.length,
308
+ warnings,
309
+ };
310
+ }