@yasserkhanorg/e2e-agents 1.7.0 → 1.7.1

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.
@@ -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,CAsO1F"}
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() || !stat.isDirectory())
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 directory
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,10 @@ 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
+ 'playwright.config.ts', 'global-setup.ts',
18
19
  ];
19
20
  /**
20
21
  * Check if a file path matches any infrastructure glob pattern.
@@ -104,7 +105,45 @@ export function getCommitFiles(projectRoot, since) {
104
105
  }
105
106
  return parseGitLog(log);
106
107
  }
107
- export function validateCommit(manifest, files, hash, message) {
108
+ /**
109
+ * For each file, try matching both the original path and any prefix-stripped
110
+ * variant against the manifest. Returns one FileBinding per original file.
111
+ */
112
+ function bindWithPrefixes(files, manifest, prefixes) {
113
+ if (prefixes.length === 0) {
114
+ return bindFilesToFamilies(files, manifest);
115
+ }
116
+ // Build candidate variants for each file
117
+ const variants = files.map((f) => {
118
+ const normalized = f.replace(/\\/g, '/');
119
+ const candidates = [normalized];
120
+ for (const prefix of prefixes) {
121
+ if (normalized.startsWith(prefix)) {
122
+ candidates.push(normalized.slice(prefix.length));
123
+ break;
124
+ }
125
+ }
126
+ return candidates;
127
+ });
128
+ // Bind all variants and merge results per original file
129
+ return files.map((f, i) => {
130
+ const normalized = f.replace(/\\/g, '/');
131
+ const allBindings = [];
132
+ const seen = new Set();
133
+ for (const variant of variants[i]) {
134
+ const [result] = bindFilesToFamilies([variant], manifest);
135
+ for (const b of result.bindings) {
136
+ const key = `${b.family}:${b.feature || ''}`;
137
+ if (!seen.has(key)) {
138
+ seen.add(key);
139
+ allBindings.push(b);
140
+ }
141
+ }
142
+ }
143
+ return { file: normalized, bindings: allBindings };
144
+ });
145
+ }
146
+ export function validateCommit(manifest, files, hash, message, pathPrefixes) {
108
147
  // Filter out non-source files and infrastructure files
109
148
  const sourceFiles = files.filter((f) => {
110
149
  return !f.endsWith('.md') && !f.endsWith('.json') && !f.endsWith('.yml') && !f.endsWith('.yaml') &&
@@ -113,7 +152,9 @@ export function validateCommit(manifest, files, hash, message) {
113
152
  if (sourceFiles.length === 0) {
114
153
  return { hash, message, changedFiles: [], boundFiles: 0, unboundFiles: [], familiesHit: [] };
115
154
  }
116
- const bindings = bindFilesToFamilies(sourceFiles, manifest);
155
+ const bindings = pathPrefixes
156
+ ? bindWithPrefixes(sourceFiles, manifest, pathPrefixes)
157
+ : bindFilesToFamilies(sourceFiles, manifest);
117
158
  const bound = bindings.filter((b) => b.bindings.length > 0);
118
159
  const unbound = bindings.filter((b) => b.bindings.length === 0);
119
160
  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,CAsCxG;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEtG;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE/F;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAaV;AAED,wBAAgB,4BAA4B,CACxC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAaV;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,eAAe,CAYjB;AAED,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAYV;AAED,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAYV;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
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,CAmC7E;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACrB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAmDvB;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"}
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"}
@@ -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() || !stat.isDirectory())
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 directory
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;AAgBnE;;;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;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"}
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;AAiBnE;;;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,10 @@ 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
+ 'playwright.config.ts', 'global-setup.ts',
26
27
  ];
27
28
  /**
28
29
  * Check if a file path matches any infrastructure glob pattern.
@@ -112,7 +113,45 @@ function getCommitFiles(projectRoot, since) {
112
113
  }
113
114
  return parseGitLog(log);
114
115
  }
115
- function validateCommit(manifest, files, hash, message) {
116
+ /**
117
+ * For each file, try matching both the original path and any prefix-stripped
118
+ * variant against the manifest. Returns one FileBinding per original file.
119
+ */
120
+ function bindWithPrefixes(files, manifest, prefixes) {
121
+ if (prefixes.length === 0) {
122
+ return (0, route_families_js_1.bindFilesToFamilies)(files, manifest);
123
+ }
124
+ // Build candidate variants for each file
125
+ const variants = files.map((f) => {
126
+ const normalized = f.replace(/\\/g, '/');
127
+ const candidates = [normalized];
128
+ for (const prefix of prefixes) {
129
+ if (normalized.startsWith(prefix)) {
130
+ candidates.push(normalized.slice(prefix.length));
131
+ break;
132
+ }
133
+ }
134
+ return candidates;
135
+ });
136
+ // Bind all variants and merge results per original file
137
+ return files.map((f, i) => {
138
+ const normalized = f.replace(/\\/g, '/');
139
+ const allBindings = [];
140
+ const seen = new Set();
141
+ for (const variant of variants[i]) {
142
+ const [result] = (0, route_families_js_1.bindFilesToFamilies)([variant], manifest);
143
+ for (const b of result.bindings) {
144
+ const key = `${b.family}:${b.feature || ''}`;
145
+ if (!seen.has(key)) {
146
+ seen.add(key);
147
+ allBindings.push(b);
148
+ }
149
+ }
150
+ }
151
+ return { file: normalized, bindings: allBindings };
152
+ });
153
+ }
154
+ function validateCommit(manifest, files, hash, message, pathPrefixes) {
116
155
  // Filter out non-source files and infrastructure files
117
156
  const sourceFiles = files.filter((f) => {
118
157
  return !f.endsWith('.md') && !f.endsWith('.json') && !f.endsWith('.yml') && !f.endsWith('.yaml') &&
@@ -121,7 +160,9 @@ function validateCommit(manifest, files, hash, message) {
121
160
  if (sourceFiles.length === 0) {
122
161
  return { hash, message, changedFiles: [], boundFiles: 0, unboundFiles: [], familiesHit: [] };
123
162
  }
124
- const bindings = (0, route_families_js_1.bindFilesToFamilies)(sourceFiles, manifest);
163
+ const bindings = pathPrefixes
164
+ ? bindWithPrefixes(sourceFiles, manifest, pathPrefixes)
165
+ : (0, route_families_js_1.bindFilesToFamilies)(sourceFiles, manifest);
125
166
  const bound = bindings.filter((b) => b.bindings.length > 0);
126
167
  const unbound = bindings.filter((b) => b.bindings.length === 0);
127
168
  const familiesHit = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
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",