@yasserkhanorg/e2e-agents 1.7.0 → 1.7.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/dist/cli/commands/train.d.ts.map +1 -1
- package/dist/cli/commands/train.js +16 -2
- package/dist/esm/cli/commands/train.js +16 -2
- package/dist/esm/knowledge/route_families.js +3 -0
- package/dist/esm/training/scanner.js +33 -10
- package/dist/esm/training/validator.js +47 -3
- package/dist/knowledge/route_families.d.ts.map +1 -1
- package/dist/knowledge/route_families.js +3 -0
- package/dist/training/scanner.d.ts +1 -1
- package/dist/training/scanner.d.ts.map +1 -1
- package/dist/training/scanner.js +33 -10
- package/dist/training/validator.d.ts +1 -1
- package/dist/training/validator.d.ts.map +1 -1
- package/dist/training/validator.js +47 -3
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"train.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/train.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAqJ5C,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"train.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/train.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAqJ5C,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsP1F"}
|
|
@@ -302,6 +302,20 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
302
302
|
let validationReport;
|
|
303
303
|
if (opts.validate) {
|
|
304
304
|
const validateTimer = logger_js_1.logger.timer('validate');
|
|
305
|
+
// Build path prefixes for monorepo-aware path normalization.
|
|
306
|
+
// Git log returns repo-root-relative paths, but manifest globs are
|
|
307
|
+
// relative to appPath, testsRoot, or serverRoot.
|
|
308
|
+
const pathPrefixes = [];
|
|
309
|
+
if (opts.gitRepoRoot) {
|
|
310
|
+
const { relative: relPath } = await import('path');
|
|
311
|
+
for (const root of [opts.appPath, opts.testsRoot, opts.serverRoot].filter(Boolean)) {
|
|
312
|
+
const rel = relPath(opts.gitRepoRoot, root).replace(/\\/g, '/');
|
|
313
|
+
if (rel && !rel.startsWith('..') && rel !== '.') {
|
|
314
|
+
pathPrefixes.push(rel.endsWith('/') ? rel : rel + '/');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
logger_js_1.logger.debug('Validation path prefixes', { prefixes: pathPrefixes });
|
|
305
319
|
if (opts.pr) {
|
|
306
320
|
logger_js_1.logger.info(`Validating against PR #${opts.pr}...`);
|
|
307
321
|
// Check for gh CLI
|
|
@@ -329,7 +343,7 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
329
343
|
logger_js_1.logger.info('No files found in PR.');
|
|
330
344
|
}
|
|
331
345
|
else {
|
|
332
|
-
const validation = (0, validator_js_1.validateCommit)(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}
|
|
346
|
+
const validation = (0, validator_js_1.validateCommit)(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}`, pathPrefixes);
|
|
333
347
|
validationReport = (0, validator_js_1.buildValidationReport)([validation], mergeResult.manifest);
|
|
334
348
|
logger_js_1.logger.info((0, validator_js_1.formatValidationReport)(validationReport));
|
|
335
349
|
}
|
|
@@ -341,7 +355,7 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
341
355
|
logger_js_1.logger.info('No commits found in range.');
|
|
342
356
|
}
|
|
343
357
|
else {
|
|
344
|
-
const validations = commits.map((c) => (0, validator_js_1.validateCommit)(mergeResult.manifest, c.files, c.hash, c.message));
|
|
358
|
+
const validations = commits.map((c) => (0, validator_js_1.validateCommit)(mergeResult.manifest, c.files, c.hash, c.message, pathPrefixes));
|
|
345
359
|
validationReport = (0, validator_js_1.buildValidationReport)(validations, mergeResult.manifest);
|
|
346
360
|
logger_js_1.logger.info((0, validator_js_1.formatValidationReport)(validationReport));
|
|
347
361
|
}
|
|
@@ -266,6 +266,20 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
266
266
|
let validationReport;
|
|
267
267
|
if (opts.validate) {
|
|
268
268
|
const validateTimer = logger.timer('validate');
|
|
269
|
+
// Build path prefixes for monorepo-aware path normalization.
|
|
270
|
+
// Git log returns repo-root-relative paths, but manifest globs are
|
|
271
|
+
// relative to appPath, testsRoot, or serverRoot.
|
|
272
|
+
const pathPrefixes = [];
|
|
273
|
+
if (opts.gitRepoRoot) {
|
|
274
|
+
const { relative: relPath } = await import('path');
|
|
275
|
+
for (const root of [opts.appPath, opts.testsRoot, opts.serverRoot].filter(Boolean)) {
|
|
276
|
+
const rel = relPath(opts.gitRepoRoot, root).replace(/\\/g, '/');
|
|
277
|
+
if (rel && !rel.startsWith('..') && rel !== '.') {
|
|
278
|
+
pathPrefixes.push(rel.endsWith('/') ? rel : rel + '/');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
logger.debug('Validation path prefixes', { prefixes: pathPrefixes });
|
|
269
283
|
if (opts.pr) {
|
|
270
284
|
logger.info(`Validating against PR #${opts.pr}...`);
|
|
271
285
|
// Check for gh CLI
|
|
@@ -293,7 +307,7 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
293
307
|
logger.info('No files found in PR.');
|
|
294
308
|
}
|
|
295
309
|
else {
|
|
296
|
-
const validation = validateCommit(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}
|
|
310
|
+
const validation = validateCommit(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}`, pathPrefixes);
|
|
297
311
|
validationReport = buildValidationReport([validation], mergeResult.manifest);
|
|
298
312
|
logger.info(formatValidationReport(validationReport));
|
|
299
313
|
}
|
|
@@ -305,7 +319,7 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
305
319
|
logger.info('No commits found in range.');
|
|
306
320
|
}
|
|
307
321
|
else {
|
|
308
|
-
const validations = commits.map((c) => validateCommit(mergeResult.manifest, c.files, c.hash, c.message));
|
|
322
|
+
const validations = commits.map((c) => validateCommit(mergeResult.manifest, c.files, c.hash, c.message, pathPrefixes));
|
|
309
323
|
validationReport = buildValidationReport(validations, mergeResult.manifest);
|
|
310
324
|
logger.info(formatValidationReport(validationReport));
|
|
311
325
|
}
|
|
@@ -171,6 +171,7 @@ export function bindFilesToFamilies(changedFiles, manifest) {
|
|
|
171
171
|
const featurePatterns = [
|
|
172
172
|
...(feature.webappPaths || []),
|
|
173
173
|
...(feature.serverPaths || []),
|
|
174
|
+
...(feature.specDirs || []),
|
|
174
175
|
];
|
|
175
176
|
if (featurePatterns.length > 0 && matchesAnyPattern(normalized, featurePatterns)) {
|
|
176
177
|
featureBindings.push({ family: family.id, feature: feature.id });
|
|
@@ -185,6 +186,8 @@ export function bindFilesToFamilies(changedFiles, manifest) {
|
|
|
185
186
|
const familyPatterns = [
|
|
186
187
|
...(family.webappPaths || []),
|
|
187
188
|
...(family.serverPaths || []),
|
|
189
|
+
...(family.specDirs || []),
|
|
190
|
+
...(family.cypressSpecDirs || []),
|
|
188
191
|
];
|
|
189
192
|
if (familyPatterns.length > 0 && matchesAnyPattern(normalized, familyPatterns)) {
|
|
190
193
|
bindings.push({ family: family.id });
|
|
@@ -628,7 +628,7 @@ export function discoverTestDerivedFamilies(testsRoot) {
|
|
|
628
628
|
}
|
|
629
629
|
/**
|
|
630
630
|
* Discover test library paths (page objects, helpers) organized by feature.
|
|
631
|
-
* Walks well-known test lib directories and maps subdirectories to family IDs.
|
|
631
|
+
* Walks well-known test lib directories and maps subdirectories and files to family IDs.
|
|
632
632
|
*/
|
|
633
633
|
export function discoverTestLibPaths(testsRoot) {
|
|
634
634
|
const resolved = resolve(testsRoot);
|
|
@@ -655,18 +655,34 @@ export function discoverTestLibPaths(testsRoot) {
|
|
|
655
655
|
const fullPath = join(fullDir, entry);
|
|
656
656
|
try {
|
|
657
657
|
const stat = lstatSync(fullPath);
|
|
658
|
-
if (stat.isSymbolicLink()
|
|
658
|
+
if (stat.isSymbolicLink())
|
|
659
659
|
continue;
|
|
660
|
+
if (stat.isDirectory()) {
|
|
661
|
+
// Subdirectory → family ID from dir name
|
|
662
|
+
const familyId = normalizeId(entry);
|
|
663
|
+
const relPath = relative(resolved, fullPath).replace(/\\/g, '/');
|
|
664
|
+
if (!result.has(familyId))
|
|
665
|
+
result.set(familyId, []);
|
|
666
|
+
result.get(familyId).push(`${relPath}/*`);
|
|
667
|
+
}
|
|
668
|
+
else if (stat.isFile()) {
|
|
669
|
+
// File → family ID from basename (e.g., channel.ts → channel)
|
|
670
|
+
const ext = entry.slice(entry.lastIndexOf('.'));
|
|
671
|
+
if (!['.ts', '.tsx', '.js', '.jsx'].includes(ext))
|
|
672
|
+
continue;
|
|
673
|
+
const baseName = entry.slice(0, entry.lastIndexOf('.'));
|
|
674
|
+
const familyId = normalizeId(baseName);
|
|
675
|
+
if (familyId.length < 3)
|
|
676
|
+
continue;
|
|
677
|
+
const relPath = relative(resolved, fullPath).replace(/\\/g, '/');
|
|
678
|
+
if (!result.has(familyId))
|
|
679
|
+
result.set(familyId, []);
|
|
680
|
+
result.get(familyId).push(relPath);
|
|
681
|
+
}
|
|
660
682
|
}
|
|
661
683
|
catch {
|
|
662
684
|
continue;
|
|
663
685
|
}
|
|
664
|
-
const familyId = normalizeId(entry);
|
|
665
|
-
const relPath = relative(resolved, fullPath).replace(/\\/g, '/');
|
|
666
|
-
const pattern = `${relPath}/*`;
|
|
667
|
-
if (!result.has(familyId))
|
|
668
|
-
result.set(familyId, []);
|
|
669
|
-
result.get(familyId).push(pattern);
|
|
670
686
|
}
|
|
671
687
|
}
|
|
672
688
|
return result;
|
|
@@ -682,7 +698,7 @@ export function discoverNameMatchedPaths(appPath, gitRepoRoot) {
|
|
|
682
698
|
{ root: join(resolvedApp, 'src/utils'), base: resolvedApp },
|
|
683
699
|
{ root: join(resolvedApp, 'src/types'), base: resolvedApp },
|
|
684
700
|
];
|
|
685
|
-
// Monorepo-aware: scan platform types
|
|
701
|
+
// Monorepo-aware: scan platform types and server model directories
|
|
686
702
|
if (gitRepoRoot) {
|
|
687
703
|
const resolvedGitRoot = resolve(gitRepoRoot);
|
|
688
704
|
const platformTypes = join(resolvedGitRoot, 'webapp/platform/types/src');
|
|
@@ -693,6 +709,10 @@ export function discoverNameMatchedPaths(appPath, gitRepoRoot) {
|
|
|
693
709
|
if (existsSync(platformClient)) {
|
|
694
710
|
scanRoots.push({ root: platformClient, base: resolvedGitRoot });
|
|
695
711
|
}
|
|
712
|
+
const serverModel = join(resolvedGitRoot, 'server/public/model');
|
|
713
|
+
if (existsSync(serverModel)) {
|
|
714
|
+
scanRoots.push({ root: serverModel, base: resolvedGitRoot });
|
|
715
|
+
}
|
|
696
716
|
}
|
|
697
717
|
for (const { root, base } of scanRoots) {
|
|
698
718
|
if (!existsSync(root))
|
|
@@ -708,7 +728,10 @@ export function discoverNameMatchedPaths(appPath, gitRepoRoot) {
|
|
|
708
728
|
if (entry.startsWith('.'))
|
|
709
729
|
continue;
|
|
710
730
|
const ext = entry.slice(entry.lastIndexOf('.'));
|
|
711
|
-
if (!['.ts', '.tsx', '.js', '.jsx'].includes(ext))
|
|
731
|
+
if (!['.ts', '.tsx', '.js', '.jsx', '.go'].includes(ext))
|
|
732
|
+
continue;
|
|
733
|
+
// Skip Go test files
|
|
734
|
+
if (entry.endsWith('_test.go'))
|
|
712
735
|
continue;
|
|
713
736
|
const fullPath = join(root, entry);
|
|
714
737
|
try {
|
|
@@ -12,9 +12,13 @@ const INFRA_GLOBS = [
|
|
|
12
12
|
'*.lock',
|
|
13
13
|
'**/mocks/*', '**/storetest/*', '**/testlib/*',
|
|
14
14
|
'**/i18n/*',
|
|
15
|
-
'**/.github/*', '**/scripts/*',
|
|
15
|
+
'**/.github/*', '**/.ci/*', '**/scripts/*',
|
|
16
16
|
'**/docker-compose*',
|
|
17
17
|
'**/__fixtures__/*', '**/test_templates/*',
|
|
18
|
+
'**/__snapshots__/*', '**/retrylayer/*', '**/timerlayer/*',
|
|
19
|
+
'**/cmd/mmctl/*',
|
|
20
|
+
'**/mattermost-redux/src/reducers/*',
|
|
21
|
+
'playwright.config.ts', 'global-setup.ts',
|
|
18
22
|
];
|
|
19
23
|
/**
|
|
20
24
|
* Check if a file path matches any infrastructure glob pattern.
|
|
@@ -104,7 +108,45 @@ export function getCommitFiles(projectRoot, since) {
|
|
|
104
108
|
}
|
|
105
109
|
return parseGitLog(log);
|
|
106
110
|
}
|
|
107
|
-
|
|
111
|
+
/**
|
|
112
|
+
* For each file, try matching both the original path and any prefix-stripped
|
|
113
|
+
* variant against the manifest. Returns one FileBinding per original file.
|
|
114
|
+
*/
|
|
115
|
+
function bindWithPrefixes(files, manifest, prefixes) {
|
|
116
|
+
if (prefixes.length === 0) {
|
|
117
|
+
return bindFilesToFamilies(files, manifest);
|
|
118
|
+
}
|
|
119
|
+
// Build candidate variants for each file
|
|
120
|
+
const variants = files.map((f) => {
|
|
121
|
+
const normalized = f.replace(/\\/g, '/');
|
|
122
|
+
const candidates = [normalized];
|
|
123
|
+
for (const prefix of prefixes) {
|
|
124
|
+
if (normalized.startsWith(prefix)) {
|
|
125
|
+
candidates.push(normalized.slice(prefix.length));
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return candidates;
|
|
130
|
+
});
|
|
131
|
+
// Bind all variants and merge results per original file
|
|
132
|
+
return files.map((f, i) => {
|
|
133
|
+
const normalized = f.replace(/\\/g, '/');
|
|
134
|
+
const allBindings = [];
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
for (const variant of variants[i]) {
|
|
137
|
+
const [result] = bindFilesToFamilies([variant], manifest);
|
|
138
|
+
for (const b of result.bindings) {
|
|
139
|
+
const key = `${b.family}:${b.feature || ''}`;
|
|
140
|
+
if (!seen.has(key)) {
|
|
141
|
+
seen.add(key);
|
|
142
|
+
allBindings.push(b);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { file: normalized, bindings: allBindings };
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
export function validateCommit(manifest, files, hash, message, pathPrefixes) {
|
|
108
150
|
// Filter out non-source files and infrastructure files
|
|
109
151
|
const sourceFiles = files.filter((f) => {
|
|
110
152
|
return !f.endsWith('.md') && !f.endsWith('.json') && !f.endsWith('.yml') && !f.endsWith('.yaml') &&
|
|
@@ -113,7 +155,9 @@ export function validateCommit(manifest, files, hash, message) {
|
|
|
113
155
|
if (sourceFiles.length === 0) {
|
|
114
156
|
return { hash, message, changedFiles: [], boundFiles: 0, unboundFiles: [], familiesHit: [] };
|
|
115
157
|
}
|
|
116
|
-
const bindings =
|
|
158
|
+
const bindings = pathPrefixes
|
|
159
|
+
? bindWithPrefixes(sourceFiles, manifest, pathPrefixes)
|
|
160
|
+
: bindFilesToFamilies(sourceFiles, manifest);
|
|
117
161
|
const bound = bindings.filter((b) => b.bindings.length > 0);
|
|
118
162
|
const unbound = bindings.filter((b) => b.bindings.length === 0);
|
|
119
163
|
const familiesHit = new Set();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route_families.d.ts","sourceRoot":"","sources":["../../src/knowledge/route_families.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,iBAAiB;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAwBtE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAE/E;AA+FD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,IAAI,CA0CjH;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,mBAAmB,GAAG,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"route_families.d.ts","sourceRoot":"","sources":["../../src/knowledge/route_families.ts"],"names":[],"mappings":"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"}
|
|
@@ -185,6 +185,7 @@ function bindFilesToFamilies(changedFiles, manifest) {
|
|
|
185
185
|
const featurePatterns = [
|
|
186
186
|
...(feature.webappPaths || []),
|
|
187
187
|
...(feature.serverPaths || []),
|
|
188
|
+
...(feature.specDirs || []),
|
|
188
189
|
];
|
|
189
190
|
if (featurePatterns.length > 0 && matchesAnyPattern(normalized, featurePatterns)) {
|
|
190
191
|
featureBindings.push({ family: family.id, feature: feature.id });
|
|
@@ -199,6 +200,8 @@ function bindFilesToFamilies(changedFiles, manifest) {
|
|
|
199
200
|
const familyPatterns = [
|
|
200
201
|
...(family.webappPaths || []),
|
|
201
202
|
...(family.serverPaths || []),
|
|
203
|
+
...(family.specDirs || []),
|
|
204
|
+
...(family.cypressSpecDirs || []),
|
|
202
205
|
];
|
|
203
206
|
if (familyPatterns.length > 0 && matchesAnyPattern(normalized, familyPatterns)) {
|
|
204
207
|
bindings.push({ family: family.id });
|
|
@@ -19,7 +19,7 @@ export declare function discoverServerDerivedFamilies(serverRoot: string): {
|
|
|
19
19
|
export declare function discoverTestDerivedFamilies(testsRoot: string): ScannedFamily[];
|
|
20
20
|
/**
|
|
21
21
|
* Discover test library paths (page objects, helpers) organized by feature.
|
|
22
|
-
* Walks well-known test lib directories and maps subdirectories to family IDs.
|
|
22
|
+
* Walks well-known test lib directories and maps subdirectories and files to family IDs.
|
|
23
23
|
*/
|
|
24
24
|
export declare function discoverTestLibPaths(testsRoot: string): Map<string, string[]>;
|
|
25
25
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/training/scanner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAkB,UAAU,EAAC,MAAM,YAAY,CAAC;AAgJzF,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CA+BvE;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CA6DrE;AAuLD;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG;IAAC,iBAAiB,EAAE,aAAa,EAAE,CAAC;IAAC,kBAAkB,EAAE,aAAa,EAAE,CAAA;CAAC,CAgI3I;AAED,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,EAAE,CAiG9E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/training/scanner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAkB,UAAU,EAAC,MAAM,YAAY,CAAC;AAgJzF,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CA+BvE;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CA6DrE;AAuLD;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG;IAAC,iBAAiB,EAAE,aAAa,EAAE,CAAC;IAAC,kBAAkB,EAAE,aAAa,EAAE,CAAA;CAAC,CAgI3I;AAED,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,EAAE,CAiG9E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CA8C7E;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACrB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAyDvB;AAED,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CA0L1H"}
|
package/dist/training/scanner.js
CHANGED
|
@@ -637,7 +637,7 @@ function discoverTestDerivedFamilies(testsRoot) {
|
|
|
637
637
|
}
|
|
638
638
|
/**
|
|
639
639
|
* Discover test library paths (page objects, helpers) organized by feature.
|
|
640
|
-
* Walks well-known test lib directories and maps subdirectories to family IDs.
|
|
640
|
+
* Walks well-known test lib directories and maps subdirectories and files to family IDs.
|
|
641
641
|
*/
|
|
642
642
|
function discoverTestLibPaths(testsRoot) {
|
|
643
643
|
const resolved = (0, path_1.resolve)(testsRoot);
|
|
@@ -664,18 +664,34 @@ function discoverTestLibPaths(testsRoot) {
|
|
|
664
664
|
const fullPath = (0, path_1.join)(fullDir, entry);
|
|
665
665
|
try {
|
|
666
666
|
const stat = (0, fs_1.lstatSync)(fullPath);
|
|
667
|
-
if (stat.isSymbolicLink()
|
|
667
|
+
if (stat.isSymbolicLink())
|
|
668
668
|
continue;
|
|
669
|
+
if (stat.isDirectory()) {
|
|
670
|
+
// Subdirectory → family ID from dir name
|
|
671
|
+
const familyId = normalizeId(entry);
|
|
672
|
+
const relPath = (0, path_1.relative)(resolved, fullPath).replace(/\\/g, '/');
|
|
673
|
+
if (!result.has(familyId))
|
|
674
|
+
result.set(familyId, []);
|
|
675
|
+
result.get(familyId).push(`${relPath}/*`);
|
|
676
|
+
}
|
|
677
|
+
else if (stat.isFile()) {
|
|
678
|
+
// File → family ID from basename (e.g., channel.ts → channel)
|
|
679
|
+
const ext = entry.slice(entry.lastIndexOf('.'));
|
|
680
|
+
if (!['.ts', '.tsx', '.js', '.jsx'].includes(ext))
|
|
681
|
+
continue;
|
|
682
|
+
const baseName = entry.slice(0, entry.lastIndexOf('.'));
|
|
683
|
+
const familyId = normalizeId(baseName);
|
|
684
|
+
if (familyId.length < 3)
|
|
685
|
+
continue;
|
|
686
|
+
const relPath = (0, path_1.relative)(resolved, fullPath).replace(/\\/g, '/');
|
|
687
|
+
if (!result.has(familyId))
|
|
688
|
+
result.set(familyId, []);
|
|
689
|
+
result.get(familyId).push(relPath);
|
|
690
|
+
}
|
|
669
691
|
}
|
|
670
692
|
catch {
|
|
671
693
|
continue;
|
|
672
694
|
}
|
|
673
|
-
const familyId = normalizeId(entry);
|
|
674
|
-
const relPath = (0, path_1.relative)(resolved, fullPath).replace(/\\/g, '/');
|
|
675
|
-
const pattern = `${relPath}/*`;
|
|
676
|
-
if (!result.has(familyId))
|
|
677
|
-
result.set(familyId, []);
|
|
678
|
-
result.get(familyId).push(pattern);
|
|
679
695
|
}
|
|
680
696
|
}
|
|
681
697
|
return result;
|
|
@@ -691,7 +707,7 @@ function discoverNameMatchedPaths(appPath, gitRepoRoot) {
|
|
|
691
707
|
{ root: (0, path_1.join)(resolvedApp, 'src/utils'), base: resolvedApp },
|
|
692
708
|
{ root: (0, path_1.join)(resolvedApp, 'src/types'), base: resolvedApp },
|
|
693
709
|
];
|
|
694
|
-
// Monorepo-aware: scan platform types
|
|
710
|
+
// Monorepo-aware: scan platform types and server model directories
|
|
695
711
|
if (gitRepoRoot) {
|
|
696
712
|
const resolvedGitRoot = (0, path_1.resolve)(gitRepoRoot);
|
|
697
713
|
const platformTypes = (0, path_1.join)(resolvedGitRoot, 'webapp/platform/types/src');
|
|
@@ -702,6 +718,10 @@ function discoverNameMatchedPaths(appPath, gitRepoRoot) {
|
|
|
702
718
|
if ((0, fs_1.existsSync)(platformClient)) {
|
|
703
719
|
scanRoots.push({ root: platformClient, base: resolvedGitRoot });
|
|
704
720
|
}
|
|
721
|
+
const serverModel = (0, path_1.join)(resolvedGitRoot, 'server/public/model');
|
|
722
|
+
if ((0, fs_1.existsSync)(serverModel)) {
|
|
723
|
+
scanRoots.push({ root: serverModel, base: resolvedGitRoot });
|
|
724
|
+
}
|
|
705
725
|
}
|
|
706
726
|
for (const { root, base } of scanRoots) {
|
|
707
727
|
if (!(0, fs_1.existsSync)(root))
|
|
@@ -717,7 +737,10 @@ function discoverNameMatchedPaths(appPath, gitRepoRoot) {
|
|
|
717
737
|
if (entry.startsWith('.'))
|
|
718
738
|
continue;
|
|
719
739
|
const ext = entry.slice(entry.lastIndexOf('.'));
|
|
720
|
-
if (!['.ts', '.tsx', '.js', '.jsx'].includes(ext))
|
|
740
|
+
if (!['.ts', '.tsx', '.js', '.jsx', '.go'].includes(ext))
|
|
741
|
+
continue;
|
|
742
|
+
// Skip Go test files
|
|
743
|
+
if (entry.endsWith('_test.go'))
|
|
721
744
|
continue;
|
|
722
745
|
const fullPath = (0, path_1.join)(root, entry);
|
|
723
746
|
try {
|
|
@@ -15,7 +15,7 @@ export declare function getCommitFiles(projectRoot: string, since: string): Arra
|
|
|
15
15
|
message: string;
|
|
16
16
|
files: string[];
|
|
17
17
|
}>;
|
|
18
|
-
export declare function validateCommit(manifest: RouteFamilyManifest, files: string[], hash: string, message: string): CommitValidation;
|
|
18
|
+
export declare function validateCommit(manifest: RouteFamilyManifest, files: string[], hash: string, message: string, pathPrefixes?: string[]): CommitValidation;
|
|
19
19
|
export declare function buildValidationReport(commits: CommitValidation[], manifest: RouteFamilyManifest): ValidationReport;
|
|
20
20
|
export declare function formatValidationReport(report: ValidationReport): string;
|
|
21
21
|
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -1 +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;
|
|
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;AAoBnE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CA6BrD;AAED,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;AA+CD,wBAAgB,cAAc,CAC1B,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,MAAM,EAAE,GACxB,gBAAgB,CA+BlB;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"}
|
|
@@ -20,9 +20,13 @@ const INFRA_GLOBS = [
|
|
|
20
20
|
'*.lock',
|
|
21
21
|
'**/mocks/*', '**/storetest/*', '**/testlib/*',
|
|
22
22
|
'**/i18n/*',
|
|
23
|
-
'**/.github/*', '**/scripts/*',
|
|
23
|
+
'**/.github/*', '**/.ci/*', '**/scripts/*',
|
|
24
24
|
'**/docker-compose*',
|
|
25
25
|
'**/__fixtures__/*', '**/test_templates/*',
|
|
26
|
+
'**/__snapshots__/*', '**/retrylayer/*', '**/timerlayer/*',
|
|
27
|
+
'**/cmd/mmctl/*',
|
|
28
|
+
'**/mattermost-redux/src/reducers/*',
|
|
29
|
+
'playwright.config.ts', 'global-setup.ts',
|
|
26
30
|
];
|
|
27
31
|
/**
|
|
28
32
|
* Check if a file path matches any infrastructure glob pattern.
|
|
@@ -112,7 +116,45 @@ function getCommitFiles(projectRoot, since) {
|
|
|
112
116
|
}
|
|
113
117
|
return parseGitLog(log);
|
|
114
118
|
}
|
|
115
|
-
|
|
119
|
+
/**
|
|
120
|
+
* For each file, try matching both the original path and any prefix-stripped
|
|
121
|
+
* variant against the manifest. Returns one FileBinding per original file.
|
|
122
|
+
*/
|
|
123
|
+
function bindWithPrefixes(files, manifest, prefixes) {
|
|
124
|
+
if (prefixes.length === 0) {
|
|
125
|
+
return (0, route_families_js_1.bindFilesToFamilies)(files, manifest);
|
|
126
|
+
}
|
|
127
|
+
// Build candidate variants for each file
|
|
128
|
+
const variants = files.map((f) => {
|
|
129
|
+
const normalized = f.replace(/\\/g, '/');
|
|
130
|
+
const candidates = [normalized];
|
|
131
|
+
for (const prefix of prefixes) {
|
|
132
|
+
if (normalized.startsWith(prefix)) {
|
|
133
|
+
candidates.push(normalized.slice(prefix.length));
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return candidates;
|
|
138
|
+
});
|
|
139
|
+
// Bind all variants and merge results per original file
|
|
140
|
+
return files.map((f, i) => {
|
|
141
|
+
const normalized = f.replace(/\\/g, '/');
|
|
142
|
+
const allBindings = [];
|
|
143
|
+
const seen = new Set();
|
|
144
|
+
for (const variant of variants[i]) {
|
|
145
|
+
const [result] = (0, route_families_js_1.bindFilesToFamilies)([variant], manifest);
|
|
146
|
+
for (const b of result.bindings) {
|
|
147
|
+
const key = `${b.family}:${b.feature || ''}`;
|
|
148
|
+
if (!seen.has(key)) {
|
|
149
|
+
seen.add(key);
|
|
150
|
+
allBindings.push(b);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { file: normalized, bindings: allBindings };
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function validateCommit(manifest, files, hash, message, pathPrefixes) {
|
|
116
158
|
// Filter out non-source files and infrastructure files
|
|
117
159
|
const sourceFiles = files.filter((f) => {
|
|
118
160
|
return !f.endsWith('.md') && !f.endsWith('.json') && !f.endsWith('.yml') && !f.endsWith('.yaml') &&
|
|
@@ -121,7 +163,9 @@ function validateCommit(manifest, files, hash, message) {
|
|
|
121
163
|
if (sourceFiles.length === 0) {
|
|
122
164
|
return { hash, message, changedFiles: [], boundFiles: 0, unboundFiles: [], familiesHit: [] };
|
|
123
165
|
}
|
|
124
|
-
const bindings =
|
|
166
|
+
const bindings = pathPrefixes
|
|
167
|
+
? bindWithPrefixes(sourceFiles, manifest, pathPrefixes)
|
|
168
|
+
: (0, route_families_js_1.bindFilesToFamilies)(sourceFiles, manifest);
|
|
125
169
|
const bound = bindings.filter((b) => b.bindings.length > 0);
|
|
126
170
|
const unbound = bindings.filter((b) => b.bindings.length === 0);
|
|
127
171
|
const familiesHit = new Set();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "AI-powered E2E test impact analysis, generation, and healing. Analyzes code changes to identify affected Playwright tests, detects coverage gaps, and generates or repairs specs using pluggable LLM providers (Claude, OpenAI, Ollama). Includes MCP server, traceability, and CI/CD integration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|