@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.
- package/LICENSE +168 -0
- package/README.md +620 -0
- package/dist/agent/analysis.d.ts +62 -0
- package/dist/agent/analysis.d.ts.map +1 -0
- package/dist/agent/analysis.js +292 -0
- package/dist/agent/blast_radius.d.ts +4 -0
- package/dist/agent/blast_radius.d.ts.map +1 -0
- package/dist/agent/blast_radius.js +37 -0
- package/dist/agent/cache_utils.d.ts +38 -0
- package/dist/agent/cache_utils.d.ts.map +1 -0
- package/dist/agent/cache_utils.js +67 -0
- package/dist/agent/config.d.ts +148 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +640 -0
- package/dist/agent/dependency_graph.d.ts +14 -0
- package/dist/agent/dependency_graph.d.ts.map +1 -0
- package/dist/agent/dependency_graph.js +227 -0
- package/dist/agent/feedback.d.ts +55 -0
- package/dist/agent/feedback.d.ts.map +1 -0
- package/dist/agent/feedback.js +257 -0
- package/dist/agent/flags.d.ts +23 -0
- package/dist/agent/flags.d.ts.map +1 -0
- package/dist/agent/flags.js +171 -0
- package/dist/agent/flow_catalog.d.ts +25 -0
- package/dist/agent/flow_catalog.d.ts.map +1 -0
- package/dist/agent/flow_catalog.js +106 -0
- package/dist/agent/flow_mapping.d.ts +10 -0
- package/dist/agent/flow_mapping.d.ts.map +1 -0
- package/dist/agent/flow_mapping.js +84 -0
- package/dist/agent/framework.d.ts +13 -0
- package/dist/agent/framework.d.ts.map +1 -0
- package/dist/agent/framework.js +149 -0
- package/dist/agent/gap_suggestions.d.ts +14 -0
- package/dist/agent/gap_suggestions.d.ts.map +1 -0
- package/dist/agent/gap_suggestions.js +101 -0
- package/dist/agent/generator.d.ts +10 -0
- package/dist/agent/generator.d.ts.map +1 -0
- package/dist/agent/generator.js +115 -0
- package/dist/agent/git.d.ts +11 -0
- package/dist/agent/git.d.ts.map +1 -0
- package/dist/agent/git.js +90 -0
- package/dist/agent/handoff.d.ts +22 -0
- package/dist/agent/handoff.d.ts.map +1 -0
- package/dist/agent/handoff.js +180 -0
- package/dist/agent/impact-analyzer.d.ts +114 -0
- package/dist/agent/impact-analyzer.d.ts.map +1 -0
- package/dist/agent/impact-analyzer.js +557 -0
- package/dist/agent/index.d.ts +21 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +38 -0
- package/dist/agent/model-router.d.ts +57 -0
- package/dist/agent/model-router.d.ts.map +1 -0
- package/dist/agent/model-router.js +154 -0
- package/dist/agent/operational_insights.d.ts +41 -0
- package/dist/agent/operational_insights.d.ts.map +1 -0
- package/dist/agent/operational_insights.js +126 -0
- package/dist/agent/pipeline.d.ts +23 -0
- package/dist/agent/pipeline.d.ts.map +1 -0
- package/dist/agent/pipeline.js +609 -0
- package/dist/agent/plan.d.ts +91 -0
- package/dist/agent/plan.d.ts.map +1 -0
- package/dist/agent/plan.js +331 -0
- package/dist/agent/playwright_report.d.ts +8 -0
- package/dist/agent/playwright_report.d.ts.map +1 -0
- package/dist/agent/playwright_report.js +126 -0
- package/dist/agent/report-generator.d.ts +24 -0
- package/dist/agent/report-generator.d.ts.map +1 -0
- package/dist/agent/report-generator.js +250 -0
- package/dist/agent/report.d.ts +81 -0
- package/dist/agent/report.d.ts.map +1 -0
- package/dist/agent/report.js +147 -0
- package/dist/agent/runner.d.ts +7 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +576 -0
- package/dist/agent/selectors.d.ts +10 -0
- package/dist/agent/selectors.d.ts.map +1 -0
- package/dist/agent/selectors.js +75 -0
- package/dist/agent/spec-bridge.d.ts +101 -0
- package/dist/agent/spec-bridge.d.ts.map +1 -0
- package/dist/agent/spec-bridge.js +273 -0
- package/dist/agent/spec-builder.d.ts +102 -0
- package/dist/agent/spec-builder.d.ts.map +1 -0
- package/dist/agent/spec-builder.js +273 -0
- package/dist/agent/subsystem_risk.d.ts +23 -0
- package/dist/agent/subsystem_risk.d.ts.map +1 -0
- package/dist/agent/subsystem_risk.js +207 -0
- package/dist/agent/telemetry.d.ts +84 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +220 -0
- package/dist/agent/test_path.d.ts +2 -0
- package/dist/agent/test_path.d.ts.map +1 -0
- package/dist/agent/test_path.js +23 -0
- package/dist/agent/tests.d.ts +18 -0
- package/dist/agent/tests.d.ts.map +1 -0
- package/dist/agent/tests.js +106 -0
- package/dist/agent/traceability.d.ts +22 -0
- package/dist/agent/traceability.d.ts.map +1 -0
- package/dist/agent/traceability.js +183 -0
- package/dist/agent/traceability_capture.d.ts +18 -0
- package/dist/agent/traceability_capture.d.ts.map +1 -0
- package/dist/agent/traceability_capture.js +313 -0
- package/dist/agent/traceability_ingest.d.ts +21 -0
- package/dist/agent/traceability_ingest.d.ts.map +1 -0
- package/dist/agent/traceability_ingest.js +237 -0
- package/dist/agent/utils.d.ts +13 -0
- package/dist/agent/utils.d.ts.map +1 -0
- package/dist/agent/utils.js +152 -0
- package/dist/agent/validators/selector-validator.d.ts +74 -0
- package/dist/agent/validators/selector-validator.d.ts.map +1 -0
- package/dist/agent/validators/selector-validator.js +165 -0
- package/dist/anthropic_provider.d.ts +65 -0
- package/dist/anthropic_provider.d.ts.map +1 -0
- package/dist/anthropic_provider.js +332 -0
- package/dist/api.d.ts +48 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +113 -0
- package/dist/base_provider.d.ts +53 -0
- package/dist/base_provider.d.ts.map +1 -0
- package/dist/base_provider.js +81 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +843 -0
- package/dist/custom_provider.d.ts +20 -0
- package/dist/custom_provider.d.ts.map +1 -0
- package/dist/custom_provider.js +276 -0
- package/dist/e2e-test-gen/index.d.ts +51 -0
- package/dist/e2e-test-gen/index.d.ts.map +1 -0
- package/dist/e2e-test-gen/index.js +57 -0
- package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
- package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
- package/dist/e2e-test-gen/spec_parser.js +786 -0
- package/dist/e2e-test-gen/types.d.ts +185 -0
- package/dist/e2e-test-gen/types.d.ts.map +1 -0
- package/dist/e2e-test-gen/types.js +4 -0
- package/dist/esm/agent/analysis.js +287 -0
- package/dist/esm/agent/blast_radius.js +34 -0
- package/dist/esm/agent/cache_utils.js +63 -0
- package/dist/esm/agent/config.js +637 -0
- package/dist/esm/agent/dependency_graph.js +224 -0
- package/dist/esm/agent/feedback.js +253 -0
- package/dist/esm/agent/flags.js +160 -0
- package/dist/esm/agent/flow_catalog.js +103 -0
- package/dist/esm/agent/flow_mapping.js +81 -0
- package/dist/esm/agent/framework.js +145 -0
- package/dist/esm/agent/gap_suggestions.js +98 -0
- package/dist/esm/agent/generator.js +112 -0
- package/dist/esm/agent/git.js +87 -0
- package/dist/esm/agent/handoff.js +177 -0
- package/dist/esm/agent/impact-analyzer.js +548 -0
- package/dist/esm/agent/index.js +22 -0
- package/dist/esm/agent/model-router.js +150 -0
- package/dist/esm/agent/operational_insights.js +123 -0
- package/dist/esm/agent/pipeline.js +605 -0
- package/dist/esm/agent/plan.js +324 -0
- package/dist/esm/agent/playwright_report.js +123 -0
- package/dist/esm/agent/report-generator.js +247 -0
- package/dist/esm/agent/report.js +144 -0
- package/dist/esm/agent/runner.js +572 -0
- package/dist/esm/agent/selectors.js +71 -0
- package/dist/esm/agent/spec-bridge.js +267 -0
- package/dist/esm/agent/spec-builder.js +267 -0
- package/dist/esm/agent/subsystem_risk.js +204 -0
- package/dist/esm/agent/telemetry.js +216 -0
- package/dist/esm/agent/test_path.js +20 -0
- package/dist/esm/agent/tests.js +101 -0
- package/dist/esm/agent/traceability.js +180 -0
- package/dist/esm/agent/traceability_capture.js +310 -0
- package/dist/esm/agent/traceability_ingest.js +234 -0
- package/dist/esm/agent/utils.js +138 -0
- package/dist/esm/agent/validators/selector-validator.js +160 -0
- package/dist/esm/anthropic_provider.js +324 -0
- package/dist/esm/api.js +105 -0
- package/dist/esm/base_provider.js +77 -0
- package/dist/esm/cli.js +841 -0
- package/dist/esm/custom_provider.js +272 -0
- package/dist/esm/e2e-test-gen/index.js +50 -0
- package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
- package/dist/esm/e2e-test-gen/types.js +3 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/logger.js +89 -0
- package/dist/esm/mcp-server.js +465 -0
- package/dist/esm/ollama_provider.js +300 -0
- package/dist/esm/openai_provider.js +242 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/plan-and-test-constants.js +126 -0
- package/dist/esm/provider_factory.js +336 -0
- package/dist/esm/provider_interface.js +23 -0
- package/dist/esm/provider_utils.js +96 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/logger.d.ts +23 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +93 -0
- package/dist/mcp-server.d.ts +35 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +469 -0
- package/dist/ollama_provider.d.ts +65 -0
- package/dist/ollama_provider.d.ts.map +1 -0
- package/dist/ollama_provider.js +308 -0
- package/dist/openai_provider.d.ts +23 -0
- package/dist/openai_provider.d.ts.map +1 -0
- package/dist/openai_provider.js +250 -0
- package/dist/plan-and-test-constants.d.ts +110 -0
- package/dist/plan-and-test-constants.d.ts.map +1 -0
- package/dist/plan-and-test-constants.js +132 -0
- package/dist/provider_factory.d.ts +99 -0
- package/dist/provider_factory.d.ts.map +1 -0
- package/dist/provider_factory.js +341 -0
- package/dist/provider_interface.d.ts +358 -0
- package/dist/provider_interface.d.ts.map +1 -0
- package/dist/provider_interface.js +28 -0
- package/dist/provider_utils.d.ts +39 -0
- package/dist/provider_utils.d.ts.map +1 -0
- package/dist/provider_utils.js +103 -0
- package/package.json +101 -0
- package/schemas/gap.schema.json +18 -0
- package/schemas/impact.schema.json +418 -0
- package/schemas/plan.schema.json +285 -0
- package/schemas/subsystem-risk-map.schema.json +62 -0
- package/schemas/traceability-input.schema.json +122 -0
|
@@ -0,0 +1,313 @@
|
|
|
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.captureTraceabilityInput = captureTraceabilityInput;
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const git_js_1 = require("./git.js");
|
|
9
|
+
const utils_js_1 = require("./utils.js");
|
|
10
|
+
function asRecord(value) {
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function asArray(value) {
|
|
17
|
+
return Array.isArray(value) ? value : [];
|
|
18
|
+
}
|
|
19
|
+
function resolveFilePath(cwd, value) {
|
|
20
|
+
if ((0, path_1.isAbsolute)(value)) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return (0, path_1.resolve)(cwd, value);
|
|
24
|
+
}
|
|
25
|
+
function normalizeList(values) {
|
|
26
|
+
return Array.from(new Set(values
|
|
27
|
+
.map((value) => (0, utils_js_1.normalizePath)(value))
|
|
28
|
+
.filter(Boolean)));
|
|
29
|
+
}
|
|
30
|
+
function parseStringArray(value) {
|
|
31
|
+
return normalizeList(asArray(value)
|
|
32
|
+
.filter((item) => typeof item === 'string')
|
|
33
|
+
.map((item) => item));
|
|
34
|
+
}
|
|
35
|
+
function isExecutedStatus(status) {
|
|
36
|
+
return status !== 'skipped';
|
|
37
|
+
}
|
|
38
|
+
function specExecuted(spec) {
|
|
39
|
+
const tests = asArray(spec.tests);
|
|
40
|
+
if (tests.length === 0) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
for (const testValue of tests) {
|
|
44
|
+
const testNode = asRecord(testValue);
|
|
45
|
+
if (!testNode) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const testStatus = typeof testNode.status === 'string' ? testNode.status : undefined;
|
|
49
|
+
if (testStatus && isExecutedStatus(testStatus)) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
const outcome = typeof testNode.outcome === 'string' ? testNode.outcome : undefined;
|
|
53
|
+
if (outcome && isExecutedStatus(outcome)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const results = asArray(testNode.results);
|
|
57
|
+
for (const resultValue of results) {
|
|
58
|
+
const resultNode = asRecord(resultValue);
|
|
59
|
+
if (!resultNode) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const status = typeof resultNode.status === 'string' ? resultNode.status : undefined;
|
|
63
|
+
if (status && isExecutedStatus(status)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function relativizePath(path, roots) {
|
|
71
|
+
const normalized = (0, utils_js_1.normalizePath)(path);
|
|
72
|
+
for (const root of roots) {
|
|
73
|
+
const normalizedRoot = (0, utils_js_1.normalizePath)((0, path_1.resolve)(root));
|
|
74
|
+
if (normalized === normalizedRoot) {
|
|
75
|
+
return '.';
|
|
76
|
+
}
|
|
77
|
+
if (normalized.startsWith(`${normalizedRoot}/`)) {
|
|
78
|
+
return normalized.slice(normalizedRoot.length + 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
function collectExecutedSpecs(value, roots, output) {
|
|
84
|
+
const node = asRecord(value);
|
|
85
|
+
if (!node) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const specs = asArray(node.specs);
|
|
89
|
+
for (const specValue of specs) {
|
|
90
|
+
const specNode = asRecord(specValue);
|
|
91
|
+
if (!specNode) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const file = typeof specNode.file === 'string' ? specNode.file : '';
|
|
95
|
+
if (!file) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!specExecuted(specNode)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
output.add(relativizePath(file, roots));
|
|
102
|
+
}
|
|
103
|
+
const suites = asArray(node.suites);
|
|
104
|
+
for (const suite of suites) {
|
|
105
|
+
collectExecutedSpecs(suite, roots, output);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function loadPlaywrightExecutedSpecs(reportPath, roots) {
|
|
109
|
+
const raw = JSON.parse((0, fs_1.readFileSync)(reportPath, 'utf-8'));
|
|
110
|
+
const specs = new Set();
|
|
111
|
+
collectExecutedSpecs(raw, roots, specs);
|
|
112
|
+
return Array.from(specs).sort();
|
|
113
|
+
}
|
|
114
|
+
function loadChangedFilesFromPath(filePath) {
|
|
115
|
+
const rawText = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
116
|
+
const trimmed = rawText.trim();
|
|
117
|
+
if (!trimmed) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
121
|
+
const parsed = JSON.parse(trimmed);
|
|
122
|
+
if (Array.isArray(parsed)) {
|
|
123
|
+
return normalizeList(parsed.filter((value) => typeof value === 'string'));
|
|
124
|
+
}
|
|
125
|
+
const node = asRecord(parsed);
|
|
126
|
+
if (!node) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(node.files)) {
|
|
130
|
+
return normalizeList(node.files.filter((value) => typeof value === 'string'));
|
|
131
|
+
}
|
|
132
|
+
if (Array.isArray(node.changedFiles)) {
|
|
133
|
+
return normalizeList(node.changedFiles.filter((value) => typeof value === 'string'));
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
return normalizeList(rawText
|
|
138
|
+
.split('\n')
|
|
139
|
+
.map((line) => line.trim())
|
|
140
|
+
.filter(Boolean));
|
|
141
|
+
}
|
|
142
|
+
function addCoverageEntry(map, test, files) {
|
|
143
|
+
if (!test || files.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const normalizedTest = (0, utils_js_1.normalizePath)(test);
|
|
147
|
+
if (!map.has(normalizedTest)) {
|
|
148
|
+
map.set(normalizedTest, new Set());
|
|
149
|
+
}
|
|
150
|
+
const bucket = map.get(normalizedTest);
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
bucket?.add((0, utils_js_1.normalizePath)(file));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function loadCoverageMap(path) {
|
|
156
|
+
const raw = JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
|
|
157
|
+
const map = new Map();
|
|
158
|
+
const appendTestEntry = (testValue, filesValue) => {
|
|
159
|
+
if (typeof testValue !== 'string') {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const files = parseStringArray(filesValue);
|
|
163
|
+
addCoverageEntry(map, testValue, files);
|
|
164
|
+
};
|
|
165
|
+
if (Array.isArray(raw)) {
|
|
166
|
+
for (const entry of raw) {
|
|
167
|
+
const node = asRecord(entry);
|
|
168
|
+
if (!node) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
appendTestEntry(node.test, node.touchedFiles);
|
|
172
|
+
}
|
|
173
|
+
return map;
|
|
174
|
+
}
|
|
175
|
+
const node = asRecord(raw);
|
|
176
|
+
if (!node) {
|
|
177
|
+
return map;
|
|
178
|
+
}
|
|
179
|
+
for (const entry of asArray(node.tests)) {
|
|
180
|
+
const testNode = asRecord(entry);
|
|
181
|
+
if (!testNode) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
appendTestEntry(testNode.test, Array.isArray(testNode.touchedFiles) ? testNode.touchedFiles : testNode.files);
|
|
185
|
+
}
|
|
186
|
+
for (const entry of asArray(node.runs)) {
|
|
187
|
+
const runNode = asRecord(entry);
|
|
188
|
+
if (!runNode) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const files = Array.isArray(runNode.touchedFiles)
|
|
192
|
+
? runNode.touchedFiles
|
|
193
|
+
: (Array.isArray(runNode.coveredFiles) ? runNode.coveredFiles : runNode.files);
|
|
194
|
+
appendTestEntry(runNode.test, files);
|
|
195
|
+
}
|
|
196
|
+
const mappings = asArray(node.mappings);
|
|
197
|
+
for (const mapping of mappings) {
|
|
198
|
+
const mappingNode = asRecord(mapping);
|
|
199
|
+
if (!mappingNode || typeof mappingNode.file !== 'string' || !Array.isArray(mappingNode.tests)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const normalizedFile = (0, utils_js_1.normalizePath)(mappingNode.file);
|
|
203
|
+
for (const test of mappingNode.tests) {
|
|
204
|
+
if (typeof test !== 'string') {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
addCoverageEntry(map, test, [normalizedFile]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const fileToTests = asRecord(node.fileToTests);
|
|
211
|
+
if (fileToTests) {
|
|
212
|
+
for (const [file, tests] of Object.entries(fileToTests)) {
|
|
213
|
+
if (!Array.isArray(tests)) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
for (const test of tests) {
|
|
217
|
+
if (typeof test !== 'string') {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
addCoverageEntry(map, test, [(0, utils_js_1.normalizePath)(file)]);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return map;
|
|
225
|
+
}
|
|
226
|
+
function coverageForSpec(specPath, coverageMap) {
|
|
227
|
+
const normalizedSpec = (0, utils_js_1.normalizePath)(specPath);
|
|
228
|
+
const files = new Set();
|
|
229
|
+
const direct = coverageMap.get(normalizedSpec);
|
|
230
|
+
if (direct) {
|
|
231
|
+
for (const file of direct) {
|
|
232
|
+
files.add(file);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const prefix = `${normalizedSpec}#`;
|
|
236
|
+
for (const [key, value] of coverageMap.entries()) {
|
|
237
|
+
if (key.startsWith(prefix)) {
|
|
238
|
+
for (const file of value) {
|
|
239
|
+
files.add(file);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return Array.from(files).sort();
|
|
244
|
+
}
|
|
245
|
+
function captureTraceabilityInput(options) {
|
|
246
|
+
const warnings = [];
|
|
247
|
+
const reportPath = resolveFilePath(process.cwd(), options.reportPath);
|
|
248
|
+
if (!(0, fs_1.existsSync)(reportPath)) {
|
|
249
|
+
throw new Error(`Traceability report not found: ${reportPath}`);
|
|
250
|
+
}
|
|
251
|
+
const roots = [options.testsRoot, options.appPath].map((root) => (0, path_1.resolve)(root));
|
|
252
|
+
const executedSpecs = loadPlaywrightExecutedSpecs(reportPath, roots);
|
|
253
|
+
if (executedSpecs.length === 0) {
|
|
254
|
+
warnings.push('No executed tests found in Playwright report.');
|
|
255
|
+
}
|
|
256
|
+
let changedFiles = [];
|
|
257
|
+
if (options.changedFilesPath) {
|
|
258
|
+
const changedPath = resolveFilePath(process.cwd(), options.changedFilesPath);
|
|
259
|
+
if ((0, fs_1.existsSync)(changedPath)) {
|
|
260
|
+
changedFiles = loadChangedFilesFromPath(changedPath);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
warnings.push(`Changed files path not found: ${changedPath}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const diff = (0, git_js_1.getChangedFiles)(options.appPath, options.sinceRef, { includeUncommitted: false });
|
|
268
|
+
if (diff.error) {
|
|
269
|
+
warnings.push(`Git diff failed while building traceability input: ${diff.error}`);
|
|
270
|
+
}
|
|
271
|
+
changedFiles = diff.files;
|
|
272
|
+
}
|
|
273
|
+
changedFiles = normalizeList(changedFiles);
|
|
274
|
+
let coverageMap = new Map();
|
|
275
|
+
if (options.coverageMapPath) {
|
|
276
|
+
const coveragePath = resolveFilePath(process.cwd(), options.coverageMapPath);
|
|
277
|
+
if ((0, fs_1.existsSync)(coveragePath)) {
|
|
278
|
+
coverageMap = loadCoverageMap(coveragePath);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
warnings.push(`Coverage map path not found: ${coveragePath}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const runs = executedSpecs.map((spec) => {
|
|
285
|
+
const mappedFiles = coverageForSpec(spec, coverageMap);
|
|
286
|
+
const touchedFiles = mappedFiles.length > 0 ? mappedFiles : changedFiles;
|
|
287
|
+
return {
|
|
288
|
+
test: spec,
|
|
289
|
+
touchedFiles,
|
|
290
|
+
timestamp: new Date().toISOString(),
|
|
291
|
+
};
|
|
292
|
+
}).filter((entry) => entry.touchedFiles.length > 0);
|
|
293
|
+
if (runs.length < executedSpecs.length && changedFiles.length === 0) {
|
|
294
|
+
warnings.push('Some executed tests had no coverage-map entries and no changed-files fallback.');
|
|
295
|
+
}
|
|
296
|
+
const outputPath = options.outputPath
|
|
297
|
+
? resolveFilePath(process.cwd(), options.outputPath)
|
|
298
|
+
: (0, path_1.resolve)(options.testsRoot, '.e2e-ai-agents', 'traceability-input.json');
|
|
299
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(outputPath), { recursive: true });
|
|
300
|
+
(0, fs_1.writeFileSync)(outputPath, JSON.stringify({
|
|
301
|
+
schemaVersion: '1.0.0',
|
|
302
|
+
source: 'traceability-capture',
|
|
303
|
+
generatedAt: new Date().toISOString(),
|
|
304
|
+
runs,
|
|
305
|
+
}, null, 2), 'utf-8');
|
|
306
|
+
return {
|
|
307
|
+
outputPath,
|
|
308
|
+
testsSeen: executedSpecs.length,
|
|
309
|
+
runsGenerated: runs.length,
|
|
310
|
+
changedFilesUsed: changedFiles.length,
|
|
311
|
+
warnings,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TraceabilityImpactConfig } from './config.js';
|
|
2
|
+
export interface TraceabilityIngestEntry {
|
|
3
|
+
test: string;
|
|
4
|
+
touchedFiles: string[];
|
|
5
|
+
timestamp?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TraceabilityIngestOptions {
|
|
8
|
+
minHits?: number;
|
|
9
|
+
maxFilesPerTest?: number;
|
|
10
|
+
maxAgeDays?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TraceabilityIngestResult {
|
|
13
|
+
manifestPath: string;
|
|
14
|
+
statePath: string;
|
|
15
|
+
entriesIngested: number;
|
|
16
|
+
testsTracked: number;
|
|
17
|
+
edgesTracked: number;
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function ingestTraceabilityInput(rootPath: string, traceabilityConfig: TraceabilityImpactConfig, inputPayload: unknown, options?: TraceabilityIngestOptions): TraceabilityIngestResult;
|
|
21
|
+
//# sourceMappingURL=traceability_ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traceability_ingest.d.ts","sourceRoot":"","sources":["../../src/agent/traceability_ingest.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,aAAa,CAAC;AAG1D,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAgCD,MAAM,WAAW,wBAAwB;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AA4MD,wBAAgB,uBAAuB,CACnC,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,wBAAwB,EAC5C,YAAY,EAAE,OAAO,EACrB,OAAO,CAAC,EAAE,yBAAyB,GACpC,wBAAwB,CAoE1B"}
|
|
@@ -0,0 +1,237 @@
|
|
|
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.ingestTraceabilityInput = ingestTraceabilityInput;
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const utils_js_1 = require("./utils.js");
|
|
9
|
+
const DEFAULT_OPTIONS = {
|
|
10
|
+
minHits: 1,
|
|
11
|
+
maxFilesPerTest: 200,
|
|
12
|
+
maxAgeDays: 120,
|
|
13
|
+
};
|
|
14
|
+
function resolvePath(root, value) {
|
|
15
|
+
if ((0, path_1.isAbsolute)(value)) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
return (0, path_1.join)(root, value);
|
|
19
|
+
}
|
|
20
|
+
function parseDate(value) {
|
|
21
|
+
const parsed = Date.parse(value);
|
|
22
|
+
if (Number.isNaN(parsed)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
function safeReadJson(path) {
|
|
28
|
+
if (!(0, fs_1.existsSync)(path)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function normalizeFiles(value) {
|
|
39
|
+
if (!Array.isArray(value)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
return Array.from(new Set(value
|
|
43
|
+
.filter((entry) => typeof entry === 'string')
|
|
44
|
+
.map((entry) => (0, utils_js_1.normalizePath)(entry))));
|
|
45
|
+
}
|
|
46
|
+
function normalizeTest(value) {
|
|
47
|
+
if (typeof value !== 'string') {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const normalized = (0, utils_js_1.normalizePath)(value);
|
|
51
|
+
return normalized ? normalized : null;
|
|
52
|
+
}
|
|
53
|
+
function buildEntriesFromInput(payload) {
|
|
54
|
+
const warnings = [];
|
|
55
|
+
const entries = [];
|
|
56
|
+
const pushEntry = (testValue, filesValue, timestampValue) => {
|
|
57
|
+
const test = normalizeTest(testValue);
|
|
58
|
+
const files = normalizeFiles(filesValue);
|
|
59
|
+
if (!test || files.length === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
entries.push({
|
|
63
|
+
test,
|
|
64
|
+
touchedFiles: files,
|
|
65
|
+
timestamp: typeof timestampValue === 'string' ? timestampValue : undefined,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
if (Array.isArray(payload)) {
|
|
69
|
+
for (const item of payload) {
|
|
70
|
+
if (!item || typeof item !== 'object') {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const entry = item;
|
|
74
|
+
pushEntry(entry.test, entry.touchedFiles, entry.timestamp);
|
|
75
|
+
}
|
|
76
|
+
if (entries.length === 0) {
|
|
77
|
+
warnings.push('Traceability input array had no valid entries.');
|
|
78
|
+
}
|
|
79
|
+
return { entries, warnings };
|
|
80
|
+
}
|
|
81
|
+
if (!payload || typeof payload !== 'object') {
|
|
82
|
+
warnings.push('Traceability input must be an object or array.');
|
|
83
|
+
return { entries, warnings };
|
|
84
|
+
}
|
|
85
|
+
const input = payload;
|
|
86
|
+
if (Array.isArray(input.tests)) {
|
|
87
|
+
for (const item of input.tests) {
|
|
88
|
+
pushEntry(item?.test, item?.touchedFiles, item?.timestamp);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(input.runs)) {
|
|
92
|
+
for (const item of input.runs) {
|
|
93
|
+
const files = Array.isArray(item?.touchedFiles) ? item?.touchedFiles : (Array.isArray(item?.coveredFiles) ? item?.coveredFiles : item?.files);
|
|
94
|
+
pushEntry(item?.test, files, item?.timestamp);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (input.fileToTests && typeof input.fileToTests === 'object') {
|
|
98
|
+
for (const [file, tests] of Object.entries(input.fileToTests)) {
|
|
99
|
+
if (!Array.isArray(tests)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const normalizedFile = (0, utils_js_1.normalizePath)(file);
|
|
103
|
+
for (const test of tests) {
|
|
104
|
+
pushEntry(test, [normalizedFile]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(input.mappings)) {
|
|
109
|
+
for (const mapping of input.mappings) {
|
|
110
|
+
const file = typeof mapping?.file === 'string' ? (0, utils_js_1.normalizePath)(mapping.file) : null;
|
|
111
|
+
if (!file || !Array.isArray(mapping.tests)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const test of mapping.tests) {
|
|
115
|
+
pushEntry(test, [file]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (entries.length === 0) {
|
|
120
|
+
warnings.push('Traceability input had no valid test<->file entries.');
|
|
121
|
+
}
|
|
122
|
+
return { entries, warnings };
|
|
123
|
+
}
|
|
124
|
+
function defaultState() {
|
|
125
|
+
return {
|
|
126
|
+
schemaVersion: '1.0.0',
|
|
127
|
+
updatedAt: new Date().toISOString(),
|
|
128
|
+
tests: {},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function loadState(path) {
|
|
132
|
+
const existing = safeReadJson(path);
|
|
133
|
+
if (!existing || typeof existing !== 'object' || !existing.tests) {
|
|
134
|
+
return defaultState();
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
schemaVersion: '1.0.0',
|
|
138
|
+
updatedAt: existing.updatedAt || new Date().toISOString(),
|
|
139
|
+
tests: existing.tests,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function pruneByAge(state, maxAgeDays) {
|
|
143
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
144
|
+
for (const [test, entry] of Object.entries(state.tests)) {
|
|
145
|
+
const lastSeen = parseDate(entry.lastSeen);
|
|
146
|
+
if (lastSeen === null) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (lastSeen < cutoff) {
|
|
150
|
+
delete state.tests[test];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function buildManifest(state, minHits, maxFilesPerTest) {
|
|
155
|
+
const tests = Object.entries(state.tests)
|
|
156
|
+
.map(([test, entry]) => {
|
|
157
|
+
const touchedFiles = Object.entries(entry.files)
|
|
158
|
+
.filter(([, hits]) => hits >= minHits)
|
|
159
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
160
|
+
.slice(0, maxFilesPerTest)
|
|
161
|
+
.map(([file]) => file);
|
|
162
|
+
const signalCount = Object.values(entry.files).reduce((acc, value) => acc + value, 0);
|
|
163
|
+
return {
|
|
164
|
+
test,
|
|
165
|
+
touchedFiles,
|
|
166
|
+
signalCount,
|
|
167
|
+
lastSeen: entry.lastSeen,
|
|
168
|
+
};
|
|
169
|
+
})
|
|
170
|
+
.filter((entry) => entry.touchedFiles.length > 0)
|
|
171
|
+
.sort((a, b) => a.test.localeCompare(b.test));
|
|
172
|
+
return {
|
|
173
|
+
schemaVersion: '1.0.0',
|
|
174
|
+
generatedAt: new Date().toISOString(),
|
|
175
|
+
tests,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function ensureParent(path) {
|
|
179
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(path), { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
function ingestTraceabilityInput(rootPath, traceabilityConfig, inputPayload, options) {
|
|
182
|
+
const resolvedOptions = {
|
|
183
|
+
minHits: options?.minHits ?? DEFAULT_OPTIONS.minHits,
|
|
184
|
+
maxFilesPerTest: options?.maxFilesPerTest ?? DEFAULT_OPTIONS.maxFilesPerTest,
|
|
185
|
+
maxAgeDays: options?.maxAgeDays ?? DEFAULT_OPTIONS.maxAgeDays,
|
|
186
|
+
};
|
|
187
|
+
const warnings = [];
|
|
188
|
+
const manifestPath = resolvePath(rootPath, traceabilityConfig.manifestPath);
|
|
189
|
+
const statePath = (0, path_1.join)((0, path_1.dirname)(manifestPath), 'traceability-state.json');
|
|
190
|
+
if (!traceabilityConfig.enabled) {
|
|
191
|
+
warnings.push('Traceability is disabled in config. Input was not ingested.');
|
|
192
|
+
return {
|
|
193
|
+
manifestPath,
|
|
194
|
+
statePath,
|
|
195
|
+
entriesIngested: 0,
|
|
196
|
+
testsTracked: 0,
|
|
197
|
+
edgesTracked: 0,
|
|
198
|
+
warnings,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const parsed = buildEntriesFromInput(inputPayload);
|
|
202
|
+
warnings.push(...parsed.warnings);
|
|
203
|
+
const state = loadState(statePath);
|
|
204
|
+
const now = new Date().toISOString();
|
|
205
|
+
for (const entry of parsed.entries) {
|
|
206
|
+
const bucket = state.tests[entry.test] || {
|
|
207
|
+
files: {},
|
|
208
|
+
seenCount: 0,
|
|
209
|
+
lastSeen: now,
|
|
210
|
+
};
|
|
211
|
+
bucket.seenCount += 1;
|
|
212
|
+
bucket.lastSeen = entry.timestamp || now;
|
|
213
|
+
for (const file of entry.touchedFiles) {
|
|
214
|
+
bucket.files[file] = (bucket.files[file] || 0) + 1;
|
|
215
|
+
}
|
|
216
|
+
state.tests[entry.test] = bucket;
|
|
217
|
+
}
|
|
218
|
+
pruneByAge(state, Math.max(1, resolvedOptions.maxAgeDays));
|
|
219
|
+
state.updatedAt = now;
|
|
220
|
+
const manifest = buildManifest(state, Math.max(1, resolvedOptions.minHits), Math.max(1, resolvedOptions.maxFilesPerTest));
|
|
221
|
+
let edgesTracked = 0;
|
|
222
|
+
for (const entry of manifest.tests) {
|
|
223
|
+
edgesTracked += entry.touchedFiles.length;
|
|
224
|
+
}
|
|
225
|
+
ensureParent(statePath);
|
|
226
|
+
ensureParent(manifestPath);
|
|
227
|
+
(0, fs_1.writeFileSync)(statePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
228
|
+
(0, fs_1.writeFileSync)(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
229
|
+
return {
|
|
230
|
+
manifestPath,
|
|
231
|
+
statePath,
|
|
232
|
+
entriesIngested: parsed.entries.length,
|
|
233
|
+
testsTracked: manifest.tests.length,
|
|
234
|
+
edgesTracked,
|
|
235
|
+
warnings,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function hasGlobChars(value: string): boolean;
|
|
2
|
+
export declare function globToRegExp(pattern: string): RegExp;
|
|
3
|
+
export declare function matchGlob(pathValue: string, pattern: string): boolean;
|
|
4
|
+
export declare function safeReadTextFile(path: string): string | null;
|
|
5
|
+
export declare function normalizePath(pathValue: string): string;
|
|
6
|
+
export declare function toRelativePosix(root: string, filePath: string): string;
|
|
7
|
+
export declare function isPathWithinRoot(root: string, target: string): boolean;
|
|
8
|
+
export declare function fileExtension(pathValue: string): string;
|
|
9
|
+
export declare function baseNameWithoutExt(pathValue: string): string;
|
|
10
|
+
export declare function tokenize(value: string): string[];
|
|
11
|
+
export declare function uniqueTokens(tokens: string[]): string[];
|
|
12
|
+
export declare function titleCase(value: string): string;
|
|
13
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/agent/utils.ts"],"names":[],"mappings":"AAuCA,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+BpD;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAWrE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU5D;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGtE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAKtE;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAI5D;AAMD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAUhD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAEvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C"}
|