@yasserkhanorg/e2e-agents 1.3.2 → 1.4.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 (44) hide show
  1. package/README.md +40 -9
  2. package/dist/cli/commands/train.d.ts +3 -0
  3. package/dist/cli/commands/train.d.ts.map +1 -0
  4. package/dist/cli/commands/train.js +307 -0
  5. package/dist/cli/parse_args.d.ts.map +1 -1
  6. package/dist/cli/parse_args.js +7 -1
  7. package/dist/cli/types.d.ts +6 -1
  8. package/dist/cli/types.d.ts.map +1 -1
  9. package/dist/cli/usage.d.ts.map +1 -1
  10. package/dist/cli/usage.js +7 -1
  11. package/dist/cli.js +5 -0
  12. package/dist/esm/cli/commands/train.js +271 -0
  13. package/dist/esm/cli/parse_args.js +7 -1
  14. package/dist/esm/cli/usage.js +7 -1
  15. package/dist/esm/cli.js +5 -0
  16. package/dist/esm/index.js +5 -0
  17. package/dist/esm/knowledge/route_families.js +2 -2
  18. package/dist/esm/training/enricher.js +273 -0
  19. package/dist/esm/training/merger.js +137 -0
  20. package/dist/esm/training/scanner.js +386 -0
  21. package/dist/esm/training/types.js +6 -0
  22. package/dist/esm/training/validator.js +153 -0
  23. package/dist/index.d.ts +5 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +15 -1
  26. package/dist/knowledge/route_families.d.ts +2 -0
  27. package/dist/knowledge/route_families.d.ts.map +1 -1
  28. package/dist/knowledge/route_families.js +2 -0
  29. package/dist/training/enricher.d.ts +15 -0
  30. package/dist/training/enricher.d.ts.map +1 -0
  31. package/dist/training/enricher.js +278 -0
  32. package/dist/training/merger.d.ts +5 -0
  33. package/dist/training/merger.d.ts.map +1 -0
  34. package/dist/training/merger.js +141 -0
  35. package/dist/training/scanner.d.ts +5 -0
  36. package/dist/training/scanner.d.ts.map +1 -0
  37. package/dist/training/scanner.js +391 -0
  38. package/dist/training/types.d.ts +109 -0
  39. package/dist/training/types.d.ts.map +1 -0
  40. package/dist/training/types.js +9 -0
  41. package/dist/training/validator.d.ts +16 -0
  42. package/dist/training/validator.d.ts.map +1 -0
  43. package/dist/training/validator.js +160 -0
  44. package/package.json +1 -1
@@ -0,0 +1,391 @@
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.discoverSourceDirs = discoverSourceDirs;
6
+ exports.discoverTestDirs = discoverTestDirs;
7
+ exports.scanProject = scanProject;
8
+ const fs_1 = require("fs");
9
+ const path_1 = require("path");
10
+ const SOURCE_MAX_DEPTH = 3;
11
+ // One deeper than source to account for test framework wrapper dirs (e2e/, integration/)
12
+ const TEST_MAX_DEPTH = 5;
13
+ const SPEC_FILES_MAX_DEPTH = 10;
14
+ const SOURCE_ROOTS = ['src', 'app', 'pages', 'components', 'features', 'modules'];
15
+ const SERVER_ROOTS = ['server', 'api', 'cmd', 'model', 'services'];
16
+ const SKIP_DIRS = new Set([
17
+ 'node_modules', '.git', '.next', '.nuxt', 'dist', 'build',
18
+ 'coverage', '__pycache__', '.e2e-ai-agents', '.cache',
19
+ 'vendor', 'third_party',
20
+ ]);
21
+ const TEST_EXTENSIONS = ['.spec.ts', '.test.ts', '.spec.js', '.test.js', '.spec.tsx', '.test.tsx'];
22
+ const GO_TEST_SUFFIX = '_test.go';
23
+ /** Type-safe includes check for readonly arrays */
24
+ const includes = (arr, v) => arr.includes(v);
25
+ function isSkipped(name) {
26
+ return name.startsWith('.') || SKIP_DIRS.has(name);
27
+ }
28
+ function normalizeId(name) {
29
+ return name
30
+ .replace(/[A-Z]/g, (c, idx) => (idx > 0 ? `_${c.toLowerCase()}` : c.toLowerCase()))
31
+ .replace(/[^a-z0-9_]/g, '_')
32
+ .replace(/_+/g, '_')
33
+ .replace(/^_|_$/g, '');
34
+ }
35
+ function extractFamilyHint(dirPath, projectRoot) {
36
+ const rel = (0, path_1.relative)(projectRoot, dirPath).replace(/\\/g, '/');
37
+ const parts = rel.split('/').filter(Boolean);
38
+ // Skip the root category dir (src/, server/, tests/, etc.)
39
+ // Return the first meaningful subdirectory name
40
+ for (let i = 1; i < parts.length; i++) {
41
+ const part = parts[i];
42
+ if (!isSkipped(part) && part !== 'e2e' && part !== 'integration' && part !== 'functional') {
43
+ return normalizeId(part);
44
+ }
45
+ }
46
+ return normalizeId(parts[parts.length - 1] || (0, path_1.basename)(dirPath));
47
+ }
48
+ function walkDirs(root, projectRoot, category, maxDepth, results, depth = 0) {
49
+ if (depth > maxDepth || !(0, fs_1.existsSync)(root)) {
50
+ return;
51
+ }
52
+ let entries;
53
+ try {
54
+ entries = (0, fs_1.readdirSync)(root);
55
+ }
56
+ catch {
57
+ // ENOENT or EACCES — skip inaccessible entries
58
+ return;
59
+ }
60
+ const hasSourceFiles = entries.some((e) => {
61
+ const ext = e.slice(e.lastIndexOf('.'));
62
+ return ['.ts', '.tsx', '.js', '.jsx', '.go', '.py', '.rs'].includes(ext);
63
+ });
64
+ const subdirs = entries.filter((e) => {
65
+ if (isSkipped(e))
66
+ return false;
67
+ try {
68
+ const stat = (0, fs_1.lstatSync)((0, path_1.join)(root, e));
69
+ if (stat.isSymbolicLink())
70
+ return false;
71
+ return stat.isDirectory();
72
+ }
73
+ catch {
74
+ // ENOENT or EACCES — skip inaccessible entries
75
+ return false;
76
+ }
77
+ });
78
+ if (hasSourceFiles && depth >= 1) {
79
+ results.push({
80
+ path: (0, path_1.resolve)(root),
81
+ relativePath: (0, path_1.relative)(projectRoot, root).replace(/\\/g, '/'),
82
+ category,
83
+ familyHint: extractFamilyHint(root, projectRoot),
84
+ });
85
+ }
86
+ for (const sub of subdirs) {
87
+ walkDirs((0, path_1.join)(root, sub), projectRoot, category, maxDepth, results, depth + 1);
88
+ }
89
+ }
90
+ function discoverSourceDirs(projectRoot) {
91
+ const results = [];
92
+ const resolved = (0, path_1.resolve)(projectRoot);
93
+ let entries;
94
+ try {
95
+ entries = (0, fs_1.readdirSync)(resolved);
96
+ }
97
+ catch {
98
+ // ENOENT or EACCES — skip inaccessible entries
99
+ return results;
100
+ }
101
+ for (const entry of entries) {
102
+ if (isSkipped(entry))
103
+ continue;
104
+ const fullPath = (0, path_1.join)(resolved, entry);
105
+ try {
106
+ const stat = (0, fs_1.lstatSync)(fullPath);
107
+ if (stat.isSymbolicLink() || !stat.isDirectory())
108
+ continue;
109
+ }
110
+ catch {
111
+ // ENOENT or EACCES — skip inaccessible entries
112
+ continue;
113
+ }
114
+ if (includes(SOURCE_ROOTS, entry)) {
115
+ walkDirs(fullPath, resolved, 'webapp', SOURCE_MAX_DEPTH, results);
116
+ }
117
+ else if (includes(SERVER_ROOTS, entry)) {
118
+ walkDirs(fullPath, resolved, 'server', SOURCE_MAX_DEPTH, results);
119
+ }
120
+ }
121
+ return results;
122
+ }
123
+ function discoverTestDirs(projectRoot) {
124
+ const results = [];
125
+ const resolved = (0, path_1.resolve)(projectRoot);
126
+ function walk(dir, category, depth) {
127
+ if (depth > TEST_MAX_DEPTH || !(0, fs_1.existsSync)(dir))
128
+ return;
129
+ let entries;
130
+ try {
131
+ entries = (0, fs_1.readdirSync)(dir);
132
+ }
133
+ catch {
134
+ // ENOENT or EACCES — skip inaccessible entries
135
+ return;
136
+ }
137
+ const hasTests = entries.some((e) => {
138
+ return TEST_EXTENSIONS.some((ext) => e.endsWith(ext)) || e.endsWith(GO_TEST_SUFFIX);
139
+ });
140
+ if (hasTests) {
141
+ results.push({
142
+ path: (0, path_1.resolve)(dir),
143
+ relativePath: (0, path_1.relative)(resolved, dir).replace(/\\/g, '/'),
144
+ category,
145
+ familyHint: extractFamilyHint(dir, resolved),
146
+ });
147
+ }
148
+ for (const entry of entries) {
149
+ if (isSkipped(entry))
150
+ continue;
151
+ const full = (0, path_1.join)(dir, entry);
152
+ try {
153
+ const stat = (0, fs_1.lstatSync)(full);
154
+ if (stat.isSymbolicLink())
155
+ continue;
156
+ if (stat.isDirectory()) {
157
+ walk(full, category, depth + 1);
158
+ }
159
+ }
160
+ catch {
161
+ // ENOENT or EACCES — skip inaccessible entries
162
+ }
163
+ }
164
+ }
165
+ const testRoots = ['tests', 'test', 'e2e-tests', 'e2e', 'specs', 'spec'];
166
+ const cypressRoots = ['cypress/e2e', 'cypress/integration'];
167
+ for (const root of testRoots) {
168
+ walk((0, path_1.join)(resolved, root), 'test', 0);
169
+ }
170
+ for (const root of cypressRoots) {
171
+ walk((0, path_1.join)(resolved, root), 'cypress', 0);
172
+ }
173
+ // Also scan server dirs for Go test files
174
+ for (const root of SERVER_ROOTS) {
175
+ const serverPath = (0, path_1.join)(resolved, root);
176
+ if ((0, fs_1.existsSync)(serverPath)) {
177
+ walk(serverPath, 'test', 0);
178
+ }
179
+ }
180
+ return results;
181
+ }
182
+ function extractTags(specFiles) {
183
+ const tags = new Set();
184
+ for (const file of specFiles) {
185
+ try {
186
+ const content = (0, fs_1.readFileSync)(file, 'utf-8');
187
+ const matches = content.match(/@[a-zA-Z][a-zA-Z0-9_-]*/g);
188
+ if (matches) {
189
+ for (const m of matches) {
190
+ if (!m.startsWith('@playwright') && !m.startsWith('@param') && !m.startsWith('@returns')) {
191
+ tags.add(m);
192
+ }
193
+ }
194
+ }
195
+ }
196
+ catch {
197
+ // ENOENT or EACCES — skip unreadable files
198
+ }
199
+ }
200
+ return Array.from(tags);
201
+ }
202
+ function getSpecFiles(dir, depth = 0) {
203
+ if (depth > SPEC_FILES_MAX_DEPTH)
204
+ return [];
205
+ const files = [];
206
+ try {
207
+ for (const entry of (0, fs_1.readdirSync)(dir)) {
208
+ const full = (0, path_1.join)(dir, entry);
209
+ try {
210
+ const stat = (0, fs_1.lstatSync)(full);
211
+ if (stat.isSymbolicLink())
212
+ continue;
213
+ if (stat.isDirectory()) {
214
+ files.push(...getSpecFiles(full, depth + 1));
215
+ }
216
+ else if (TEST_EXTENSIONS.some((ext) => entry.endsWith(ext))) {
217
+ files.push(full);
218
+ }
219
+ }
220
+ catch {
221
+ // ENOENT or EACCES — skip inaccessible entries
222
+ }
223
+ }
224
+ }
225
+ catch {
226
+ // ENOENT or EACCES — skip inaccessible directories
227
+ }
228
+ return files;
229
+ }
230
+ function buildGlobPattern(relativePath) {
231
+ const normalized = relativePath.replace(/\\/g, '/');
232
+ return `${normalized}/*`;
233
+ }
234
+ function groupByFamily(dirs) {
235
+ const groups = new Map();
236
+ for (const dir of dirs) {
237
+ const key = normalizeId(dir.familyHint);
238
+ if (!groups.has(key)) {
239
+ groups.set(key, { webapp: [], server: [], test: [], cypress: [] });
240
+ }
241
+ const group = groups.get(key);
242
+ if (dir.category === 'webapp')
243
+ group.webapp.push(dir);
244
+ else if (dir.category === 'server')
245
+ group.server.push(dir);
246
+ else if (dir.category === 'cypress')
247
+ group.cypress.push(dir);
248
+ else
249
+ group.test.push(dir);
250
+ }
251
+ return groups;
252
+ }
253
+ function detectFeatures(familyId, group, projectRoot) {
254
+ const features = [];
255
+ const webappSubdirs = new Map();
256
+ for (const dir of group.webapp) {
257
+ try {
258
+ for (const entry of (0, fs_1.readdirSync)(dir.path)) {
259
+ if (isSkipped(entry))
260
+ continue;
261
+ const full = (0, path_1.join)(dir.path, entry);
262
+ try {
263
+ const stat = (0, fs_1.lstatSync)(full);
264
+ if (stat.isSymbolicLink())
265
+ continue;
266
+ if (stat.isDirectory()) {
267
+ const hint = normalizeId(entry);
268
+ if (!webappSubdirs.has(hint))
269
+ webappSubdirs.set(hint, []);
270
+ webappSubdirs.get(hint).push({
271
+ path: full,
272
+ relativePath: (0, path_1.relative)(projectRoot, full).replace(/\\/g, '/'),
273
+ category: 'webapp',
274
+ familyHint: entry,
275
+ });
276
+ }
277
+ }
278
+ catch {
279
+ // ENOENT or EACCES — skip inaccessible entries
280
+ }
281
+ }
282
+ }
283
+ catch {
284
+ // ENOENT or EACCES — skip inaccessible directories
285
+ }
286
+ }
287
+ for (const testDir of group.test) {
288
+ try {
289
+ for (const entry of (0, fs_1.readdirSync)(testDir.path)) {
290
+ if (isSkipped(entry))
291
+ continue;
292
+ const full = (0, path_1.join)(testDir.path, entry);
293
+ try {
294
+ const stat = (0, fs_1.lstatSync)(full);
295
+ if (stat.isSymbolicLink())
296
+ continue;
297
+ if (!stat.isDirectory())
298
+ continue;
299
+ }
300
+ catch {
301
+ // ENOENT or EACCES — skip inaccessible entries
302
+ continue;
303
+ }
304
+ const hint = normalizeId(entry);
305
+ if (webappSubdirs.has(hint)) {
306
+ const webDirs = webappSubdirs.get(hint);
307
+ features.push({
308
+ id: `${familyId}/${hint}`,
309
+ webappPaths: webDirs.map((d) => buildGlobPattern(d.relativePath)),
310
+ serverPaths: [],
311
+ specDirs: [(0, path_1.relative)(projectRoot, full).replace(/\\/g, '/') + '/'],
312
+ });
313
+ }
314
+ }
315
+ }
316
+ catch {
317
+ // ENOENT or EACCES — skip inaccessible directories
318
+ }
319
+ }
320
+ return features;
321
+ }
322
+ function scanProject(projectRoot) {
323
+ const resolved = (0, path_1.resolve)(projectRoot);
324
+ const sourceDirs = discoverSourceDirs(resolved);
325
+ const testDirs = discoverTestDirs(resolved);
326
+ const allDirs = [...sourceDirs, ...testDirs];
327
+ const groups = groupByFamily(allDirs);
328
+ const families = [];
329
+ for (const [familyId, group] of groups) {
330
+ const hasSrc = group.webapp.length > 0 || group.server.length > 0;
331
+ const hasTests = group.test.length > 0 || group.cypress.length > 0;
332
+ if (!hasSrc && !hasTests)
333
+ continue;
334
+ const allSpecFiles = [];
335
+ for (const td of [...group.test, ...group.cypress]) {
336
+ allSpecFiles.push(...getSpecFiles(td.path));
337
+ }
338
+ const features = detectFeatures(familyId, group, resolved);
339
+ families.push({
340
+ id: familyId,
341
+ routes: [`/${familyId}`],
342
+ webappPaths: group.webapp.map((d) => buildGlobPattern(d.relativePath)),
343
+ serverPaths: group.server.map((d) => buildGlobPattern(d.relativePath)),
344
+ specDirs: group.test.map((d) => d.relativePath + '/'),
345
+ cypressSpecDirs: group.cypress.map((d) => d.relativePath + '/'),
346
+ tags: extractTags(allSpecFiles),
347
+ features,
348
+ routesGuessed: true,
349
+ });
350
+ }
351
+ const familyIds = new Set(families.map((f) => f.id));
352
+ const unmatchedSourceDirs = sourceDirs.filter((d) => !familyIds.has(normalizeId(d.familyHint)));
353
+ const unmatchedTestDirs = testDirs.filter((d) => !familyIds.has(normalizeId(d.familyHint)));
354
+ let totalSourceFiles = 0;
355
+ let totalTestFiles = 0;
356
+ for (const dir of sourceDirs) {
357
+ try {
358
+ totalSourceFiles += (0, fs_1.readdirSync)(dir.path).filter((e) => {
359
+ try {
360
+ const stat = (0, fs_1.lstatSync)((0, path_1.join)(dir.path, e));
361
+ return !stat.isSymbolicLink() && !stat.isDirectory();
362
+ }
363
+ catch {
364
+ // ENOENT or EACCES — skip inaccessible entries
365
+ return false;
366
+ }
367
+ }).length;
368
+ }
369
+ catch {
370
+ // ENOENT or EACCES — skip inaccessible directories
371
+ }
372
+ }
373
+ for (const dir of testDirs) {
374
+ try {
375
+ totalTestFiles += getSpecFiles(dir.path).length;
376
+ }
377
+ catch {
378
+ // ENOENT or EACCES — skip inaccessible directories
379
+ }
380
+ }
381
+ return {
382
+ families,
383
+ unmatchedSourceDirs,
384
+ unmatchedTestDirs,
385
+ stats: {
386
+ totalSourceFiles,
387
+ totalTestFiles,
388
+ familyCount: families.length,
389
+ },
390
+ };
391
+ }
@@ -0,0 +1,109 @@
1
+ import type { RouteFamily, RouteFamilyManifest } from '../knowledge/route_families.js';
2
+ /** A source directory discovered by the scanner */
3
+ export interface DiscoveredDir {
4
+ /** Absolute path to the directory */
5
+ path: string;
6
+ /** Relative path from project root */
7
+ relativePath: string;
8
+ /** Category: frontend source, backend source, or test */
9
+ category: 'webapp' | 'server' | 'test' | 'cypress';
10
+ /** Deepest meaningful directory name (e.g., 'channels' from 'src/channels/') */
11
+ familyHint: string;
12
+ }
13
+ /** A family proposed by the deterministic scanner */
14
+ export interface ScannedFamily {
15
+ id: string;
16
+ routes: string[];
17
+ webappPaths: string[];
18
+ serverPaths: string[];
19
+ specDirs: string[];
20
+ cypressSpecDirs: string[];
21
+ tags: string[];
22
+ features: ScannedFeature[];
23
+ /** True if routes are guesses (directory-name-based) */
24
+ routesGuessed: boolean;
25
+ }
26
+ /** A nested feature proposed by the scanner */
27
+ export interface ScannedFeature {
28
+ id: string;
29
+ webappPaths: string[];
30
+ serverPaths: string[];
31
+ specDirs: string[];
32
+ }
33
+ /** Output of the deterministic scanner */
34
+ export interface ScanResult {
35
+ families: ScannedFamily[];
36
+ unmatchedSourceDirs: DiscoveredDir[];
37
+ unmatchedTestDirs: DiscoveredDir[];
38
+ stats: {
39
+ totalSourceFiles: number;
40
+ totalTestFiles: number;
41
+ familyCount: number;
42
+ };
43
+ }
44
+ /** Output of LLM enrichment */
45
+ export interface EnrichmentResult {
46
+ enrichedFamilies: RouteFamily[];
47
+ tokensUsed: number;
48
+ costUSD: number;
49
+ skippedFamilies: string[];
50
+ }
51
+ /** A single commit's validation result */
52
+ export interface CommitValidation {
53
+ hash: string;
54
+ message: string;
55
+ changedFiles: string[];
56
+ boundFiles: number;
57
+ unboundFiles: string[];
58
+ familiesHit: string[];
59
+ }
60
+ /** Output of validation mode */
61
+ export interface ValidationReport {
62
+ totalCommits: number;
63
+ totalFiles: number;
64
+ boundFiles: number;
65
+ unboundFiles: number;
66
+ coveragePercent: number;
67
+ commits: CommitValidation[];
68
+ familyHits: Record<string, number>;
69
+ neverHitFamilies: string[];
70
+ unboundFileClusters: Array<{
71
+ pattern: string;
72
+ count: number;
73
+ suggestedFamily: string;
74
+ }>;
75
+ }
76
+ /** Output of smart merge */
77
+ export interface MergeResult {
78
+ manifest: RouteFamilyManifest;
79
+ newFamilies: string[];
80
+ updatedFamilies: string[];
81
+ staleFamilies: string[];
82
+ summary: string;
83
+ }
84
+ /** Options for the train command */
85
+ export interface TrainOptions {
86
+ /** Path to the application root */
87
+ appPath: string;
88
+ /** Path to tests root (may differ from appPath) */
89
+ testsRoot: string;
90
+ /** Enable LLM enrichment (default: true) */
91
+ enrich: boolean;
92
+ /** Run validation against git history */
93
+ validate: boolean;
94
+ /** Git ref for validation (e.g., 'HEAD~20') */
95
+ since: string;
96
+ /** GitHub PR number for validation */
97
+ pr?: number;
98
+ /** Output path for route-families.json */
99
+ outputPath: string;
100
+ /** Dry run — print without writing */
101
+ dryRun: boolean;
102
+ /** Non-interactive mode */
103
+ yes: boolean;
104
+ /** Max LLM spend in USD */
105
+ budgetUSD: number;
106
+ }
107
+ /** Routes that look like bare "/<id>" are scanner-generated guesses */
108
+ export declare function isGuessedRoute(routes: string[]): boolean;
109
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/training/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
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.isGuessedRoute = isGuessedRoute;
6
+ /** Routes that look like bare "/<id>" are scanner-generated guesses */
7
+ function isGuessedRoute(routes) {
8
+ return routes.every((r) => /^\/[a-z][a-z0-9_]*$/.test(r));
9
+ }
@@ -0,0 +1,16 @@
1
+ import type { RouteFamilyManifest } from '../knowledge/route_families.js';
2
+ import type { CommitValidation, ValidationReport } from './types.js';
3
+ export declare function parseGitLog(log: string): Array<{
4
+ hash: string;
5
+ message: string;
6
+ files: string[];
7
+ }>;
8
+ export declare function getCommitFiles(projectRoot: string, since: string): Array<{
9
+ hash: string;
10
+ message: string;
11
+ files: string[];
12
+ }>;
13
+ export declare function validateCommit(manifest: RouteFamilyManifest, files: string[], hash: string, message: string): CommitValidation;
14
+ export declare function buildValidationReport(commits: CommitValidation[], manifest: RouteFamilyManifest): ValidationReport;
15
+ export declare function formatValidationReport(report: ValidationReport): string;
16
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/training/validator.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AAExE,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,YAAY,CAAC;AAEnE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC,CA6BhG;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC,CAgB1H;AAED,wBAAgB,cAAc,CAC1B,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAChB,gBAAgB,CA6BlB;AAED,wBAAgB,qBAAqB,CACjC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,mBAAmB,GAC9B,gBAAgB,CAkDlB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAgCvE"}