@yasserkhanorg/e2e-agents 1.6.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.
- package/dist/cli/commands/train.d.ts.map +1 -1
- package/dist/cli/commands/train.js +112 -50
- package/dist/cli/parse_args.d.ts.map +1 -1
- package/dist/cli/parse_args.js +2 -0
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/esm/cli/commands/train.js +112 -50
- package/dist/esm/cli/parse_args.js +2 -0
- package/dist/esm/knowledge/route_families.js +3 -0
- package/dist/esm/logger.js +29 -2
- package/dist/esm/pipeline/orchestrator.js +17 -3
- package/dist/esm/training/enricher.js +11 -4
- package/dist/esm/training/scanner.js +190 -12
- package/dist/esm/training/validator.js +101 -4
- package/dist/knowledge/route_families.d.ts.map +1 -1
- package/dist/knowledge/route_families.js +3 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +29 -2
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +17 -3
- package/dist/training/enricher.d.ts.map +1 -1
- package/dist/training/enricher.js +11 -4
- package/dist/training/scanner.d.ts +15 -2
- package/dist/training/scanner.d.ts.map +1 -1
- package/dist/training/scanner.js +192 -12
- package/dist/training/types.d.ts +4 -0
- package/dist/training/types.d.ts.map +1 -1
- package/dist/training/validator.d.ts +6 -1
- package/dist/training/validator.d.ts.map +1 -1
- package/dist/training/validator.js +102 -4
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ exports.runPipeline = runPipeline;
|
|
|
6
6
|
const fs_1 = require("fs");
|
|
7
7
|
const path_1 = require("path");
|
|
8
8
|
const git_js_1 = require("../agent/git.js");
|
|
9
|
+
const logger_js_1 = require("../logger.js");
|
|
9
10
|
const stage0_preprocess_js_1 = require("./stage0_preprocess.js");
|
|
10
11
|
const stage1_impact_js_1 = require("./stage1_impact.js");
|
|
11
12
|
const stage2_coverage_js_1 = require("./stage2_coverage.js");
|
|
@@ -61,20 +62,25 @@ async function runPipeline(config) {
|
|
|
61
62
|
const reportPath = writeReport(config.testsRoot, emptyReport);
|
|
62
63
|
return { report: emptyReport, reportPath, warnings: allWarnings };
|
|
63
64
|
}
|
|
65
|
+
const timings = {};
|
|
64
66
|
// Step 2: Preprocess — deterministic file classification + route family binding
|
|
67
|
+
const preprocessTimer = logger_js_1.logger.timer('preprocess');
|
|
65
68
|
const preprocessResult = (0, stage0_preprocess_js_1.preprocess)(changedFiles, {
|
|
66
69
|
appPath: config.appPath,
|
|
67
70
|
testsRoot: config.testsRoot,
|
|
68
71
|
routeFamilies: config.routeFamilies,
|
|
69
72
|
apiSurface: config.apiSurface,
|
|
70
73
|
});
|
|
74
|
+
timings.preprocess = preprocessTimer.end();
|
|
71
75
|
allWarnings.push(...preprocessResult.warnings);
|
|
72
76
|
let decisions = [];
|
|
73
77
|
// Step 3: Impact stage — AI-powered flow identification per family
|
|
74
78
|
if (stages.includes('impact')) {
|
|
79
|
+
const impactTimer = logger_js_1.logger.timer('impact');
|
|
75
80
|
const impactResult = await (0, stage1_impact_js_1.runImpactStage)(preprocessResult.familyGroups, preprocessResult.manifest, preprocessResult.specIndex, preprocessResult.apiSurface, preprocessResult.context, config.impact || {});
|
|
76
81
|
decisions = impactResult.decisions;
|
|
77
82
|
allWarnings.push(...impactResult.warnings);
|
|
83
|
+
timings.impact = impactTimer.end();
|
|
78
84
|
// Check cannot_determine ratio
|
|
79
85
|
const cannotDetermineRatio = (0, guardrails_js_1.computeCannotDetermineRatio)(decisions);
|
|
80
86
|
if (cannotDetermineRatio > 0.3) {
|
|
@@ -83,18 +89,23 @@ async function runPipeline(config) {
|
|
|
83
89
|
}
|
|
84
90
|
// Step 4: Coverage stage — AI-powered spec coverage evaluation
|
|
85
91
|
if (stages.includes('coverage') && decisions.length > 0) {
|
|
92
|
+
const coverageTimer = logger_js_1.logger.timer('coverage');
|
|
86
93
|
const coverageResult = await (0, stage2_coverage_js_1.runCoverageStage)(decisions, preprocessResult.specIndex, preprocessResult.context, config.testsRoot, config.coverage || {});
|
|
87
94
|
decisions = coverageResult.decisions;
|
|
95
|
+
timings.coverage = coverageTimer.end();
|
|
88
96
|
allWarnings.push(...coverageResult.warnings);
|
|
89
97
|
}
|
|
90
98
|
// Step 5: Generation stage — AI-powered spec generation for create_spec / add_scenarios
|
|
91
99
|
if (stages.includes('generation') && decisions.length > 0) {
|
|
100
|
+
const generationTimer = logger_js_1.logger.timer('generation');
|
|
92
101
|
const generationResult = await (0, stage3_generation_js_1.runGenerationStage)(decisions, preprocessResult.apiSurface, config.testsRoot, config.generation || {});
|
|
93
102
|
generatedSpecs = generationResult.generated;
|
|
103
|
+
timings.generation = generationTimer.end();
|
|
94
104
|
allWarnings.push(...generationResult.warnings);
|
|
95
105
|
}
|
|
96
106
|
// Step 6: Heal stage — MCP-backed playwright-test-healer for failing/flaky specs
|
|
97
107
|
if (stages.includes('heal')) {
|
|
108
|
+
const healTimer = logger_js_1.logger.timer('heal');
|
|
98
109
|
const healTargets = (0, stage4_heal_js_1.resolveHealTargets)(config.testsRoot, {
|
|
99
110
|
playwrightReportPath: config.playwrightReportPath,
|
|
100
111
|
generatedSpecs,
|
|
@@ -106,6 +117,7 @@ async function runPipeline(config) {
|
|
|
106
117
|
else {
|
|
107
118
|
allWarnings.push('Heal stage: no targets found (no failing specs in report, no generated specs).');
|
|
108
119
|
}
|
|
120
|
+
timings.heal = healTimer.end();
|
|
109
121
|
}
|
|
110
122
|
// Build report
|
|
111
123
|
const report = {
|
|
@@ -121,16 +133,18 @@ async function runPipeline(config) {
|
|
|
121
133
|
generationAgent: stages.includes('generation') ? (config.generation?.provider || 'auto') : undefined,
|
|
122
134
|
},
|
|
123
135
|
};
|
|
124
|
-
const reportPath = writeReport(config.testsRoot, report, healResult);
|
|
136
|
+
const reportPath = writeReport(config.testsRoot, report, healResult, timings);
|
|
125
137
|
return { report, reportPath, warnings: allWarnings, generated: generatedSpecs, healResult };
|
|
126
138
|
}
|
|
127
|
-
function writeReport(testsRoot, report, healResult) {
|
|
139
|
+
function writeReport(testsRoot, report, healResult, timings) {
|
|
128
140
|
const outputDir = (0, path_1.join)(testsRoot, '.e2e-ai-agents');
|
|
129
141
|
if (!(0, fs_1.existsSync)(outputDir)) {
|
|
130
142
|
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
131
143
|
}
|
|
144
|
+
// Include timings in the JSON report if available
|
|
145
|
+
const reportWithTimings = timings ? { ...report, timings } : report;
|
|
132
146
|
const jsonPath = (0, path_1.join)(outputDir, 'pipeline-report.json');
|
|
133
|
-
(0, fs_1.writeFileSync)(jsonPath, JSON.stringify(
|
|
147
|
+
(0, fs_1.writeFileSync)(jsonPath, JSON.stringify(reportWithTimings, null, 2), 'utf-8');
|
|
134
148
|
const mdPath = (0, path_1.join)(outputDir, 'pipeline-report.md');
|
|
135
149
|
(0, fs_1.writeFileSync)(mdPath, renderMarkdown(report, healResult), 'utf-8');
|
|
136
150
|
return jsonPath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enricher.d.ts","sourceRoot":"","sources":["../../src/training/enricher.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAGhE,OAAO,KAAK,EAAC,gBAAgB,EAAE,aAAa,EAAC,MAAM,YAAY,CAAC;AAkLhE,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,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;CAC1B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAmBlE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAwBrE;AAkCD,wBAAsB,cAAc,CAChC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,aAAa,EAAE,EACxB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"enricher.d.ts","sourceRoot":"","sources":["../../src/training/enricher.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAGhE,OAAO,KAAK,EAAC,gBAAgB,EAAE,aAAa,EAAC,MAAM,YAAY,CAAC;AAkLhE,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,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;CAC1B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAmBlE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAwBrE;AAkCD,wBAAsB,cAAc,CAChC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,aAAa,EAAE,EACxB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAuF3B"}
|
|
@@ -268,6 +268,8 @@ async function enrichFamilies(families, scanned, projectRoot, provider, budgetUS
|
|
|
268
268
|
const enriched = [];
|
|
269
269
|
let totalTokens = 0;
|
|
270
270
|
let totalCost = 0;
|
|
271
|
+
let requestCount = 0;
|
|
272
|
+
let totalResponseMs = 0;
|
|
271
273
|
const skipped = [];
|
|
272
274
|
// Process in chunks of 4 families
|
|
273
275
|
const chunkSize = 4;
|
|
@@ -300,15 +302,18 @@ async function enrichFamilies(families, scanned, projectRoot, provider, budgetUS
|
|
|
300
302
|
prompt = prompt.slice(0, MAX_PROMPT_CHARS);
|
|
301
303
|
}
|
|
302
304
|
}
|
|
303
|
-
let
|
|
305
|
+
let timeoutTimer;
|
|
304
306
|
try {
|
|
305
307
|
const timeoutPromise = new Promise((_, reject) => {
|
|
306
|
-
|
|
308
|
+
timeoutTimer = setTimeout(() => reject(new Error('LLM request timed out')), LLM_TIMEOUT_MS);
|
|
307
309
|
});
|
|
310
|
+
const reqStart = performance.now();
|
|
308
311
|
const response = await Promise.race([
|
|
309
312
|
provider.generateText(prompt, { maxTokens: 4096, temperature: 0.3 }),
|
|
310
313
|
timeoutPromise,
|
|
311
314
|
]);
|
|
315
|
+
totalResponseMs += performance.now() - reqStart;
|
|
316
|
+
requestCount++;
|
|
312
317
|
totalTokens += (response.usage?.inputTokens ?? 0) + (response.usage?.outputTokens ?? 0);
|
|
313
318
|
totalCost += response.cost ?? 0;
|
|
314
319
|
const entries = parseEnrichResponse(response.text);
|
|
@@ -329,8 +334,8 @@ async function enrichFamilies(families, scanned, projectRoot, provider, budgetUS
|
|
|
329
334
|
enriched.push(...chunk);
|
|
330
335
|
}
|
|
331
336
|
finally {
|
|
332
|
-
if (
|
|
333
|
-
clearTimeout(
|
|
337
|
+
if (timeoutTimer)
|
|
338
|
+
clearTimeout(timeoutTimer);
|
|
334
339
|
}
|
|
335
340
|
}
|
|
336
341
|
return {
|
|
@@ -338,5 +343,7 @@ async function enrichFamilies(families, scanned, projectRoot, provider, budgetUS
|
|
|
338
343
|
tokensUsed: totalTokens,
|
|
339
344
|
costUSD: Math.round(totalCost * 100) / 100,
|
|
340
345
|
skippedFamilies: skipped,
|
|
346
|
+
requestCount,
|
|
347
|
+
avgResponseMs: requestCount > 0 ? Math.round(totalResponseMs / requestCount) : 0,
|
|
341
348
|
};
|
|
342
349
|
}
|
|
@@ -12,7 +12,20 @@ export declare function discoverTestDirs(projectRoot: string): DiscoveredDir[];
|
|
|
12
12
|
*
|
|
13
13
|
* Each domain becomes a candidate family with precise serverPaths.
|
|
14
14
|
*/
|
|
15
|
-
export declare function discoverServerDerivedFamilies(serverRoot: string):
|
|
15
|
+
export declare function discoverServerDerivedFamilies(serverRoot: string): {
|
|
16
|
+
multiTierFamilies: ScannedFamily[];
|
|
17
|
+
singleTierFamilies: ScannedFamily[];
|
|
18
|
+
};
|
|
16
19
|
export declare function discoverTestDerivedFamilies(testsRoot: string): ScannedFamily[];
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Discover test library paths (page objects, helpers) organized by feature.
|
|
22
|
+
* Walks well-known test lib directories and maps subdirectories and files to family IDs.
|
|
23
|
+
*/
|
|
24
|
+
export declare function discoverTestLibPaths(testsRoot: string): Map<string, string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Discover files in well-known directories (types, utils) whose basename
|
|
27
|
+
* maps directly to a family ID.
|
|
28
|
+
*/
|
|
29
|
+
export declare function discoverNameMatchedPaths(appPath: string, gitRepoRoot?: string): Map<string, string[]>;
|
|
30
|
+
export declare function scanProject(projectRoot: string, testsRoot?: string, serverRoot?: string, gitRepoRoot?: string): ScanResult;
|
|
18
31
|
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -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,aAAa,EAAE,
|
|
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
|
@@ -6,6 +6,8 @@ exports.discoverSourceDirs = discoverSourceDirs;
|
|
|
6
6
|
exports.discoverTestDirs = discoverTestDirs;
|
|
7
7
|
exports.discoverServerDerivedFamilies = discoverServerDerivedFamilies;
|
|
8
8
|
exports.discoverTestDerivedFamilies = discoverTestDerivedFamilies;
|
|
9
|
+
exports.discoverTestLibPaths = discoverTestLibPaths;
|
|
10
|
+
exports.discoverNameMatchedPaths = discoverNameMatchedPaths;
|
|
9
11
|
exports.scanProject = scanProject;
|
|
10
12
|
const fs_1 = require("fs");
|
|
11
13
|
const path_1 = require("path");
|
|
@@ -520,15 +522,15 @@ function discoverServerDerivedFamilies(serverRoot) {
|
|
|
520
522
|
}
|
|
521
523
|
}
|
|
522
524
|
// Build families from grouped domains.
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
+
// Multi-tier families (≥2 tiers) can be new families.
|
|
526
|
+
// Single-tier families can only merge into existing families.
|
|
527
|
+
const multiTierFamilies = [];
|
|
528
|
+
const singleTierFamilies = [];
|
|
525
529
|
for (const [domain, paths] of familyPaths) {
|
|
526
530
|
if (paths.size === 0)
|
|
527
531
|
continue;
|
|
528
532
|
const tierCount = familyTiers.get(domain)?.size ?? 0;
|
|
529
|
-
|
|
530
|
-
continue; // Skip single-tier domains (likely infrastructure)
|
|
531
|
-
families.push({
|
|
533
|
+
const family = {
|
|
532
534
|
id: domain,
|
|
533
535
|
routes: [`/${domain.replace(/_/g, '-')}`],
|
|
534
536
|
webappPaths: [],
|
|
@@ -538,9 +540,15 @@ function discoverServerDerivedFamilies(serverRoot) {
|
|
|
538
540
|
tags: [],
|
|
539
541
|
features: [],
|
|
540
542
|
routesGuessed: true,
|
|
541
|
-
}
|
|
543
|
+
};
|
|
544
|
+
if (tierCount >= 2) {
|
|
545
|
+
multiTierFamilies.push(family);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
singleTierFamilies.push(family);
|
|
549
|
+
}
|
|
542
550
|
}
|
|
543
|
-
return
|
|
551
|
+
return { multiTierFamilies, singleTierFamilies };
|
|
544
552
|
}
|
|
545
553
|
function discoverTestDerivedFamilies(testsRoot) {
|
|
546
554
|
const resolved = (0, path_1.resolve)(testsRoot);
|
|
@@ -627,7 +635,136 @@ function discoverTestDerivedFamilies(testsRoot) {
|
|
|
627
635
|
}
|
|
628
636
|
return Array.from(familyMap.values());
|
|
629
637
|
}
|
|
630
|
-
|
|
638
|
+
/**
|
|
639
|
+
* Discover test library paths (page objects, helpers) organized by feature.
|
|
640
|
+
* Walks well-known test lib directories and maps subdirectories and files to family IDs.
|
|
641
|
+
*/
|
|
642
|
+
function discoverTestLibPaths(testsRoot) {
|
|
643
|
+
const resolved = (0, path_1.resolve)(testsRoot);
|
|
644
|
+
const result = new Map();
|
|
645
|
+
const libDirs = [
|
|
646
|
+
'lib/src/ui/components',
|
|
647
|
+
'lib/src/ui/pages',
|
|
648
|
+
'lib/src/server',
|
|
649
|
+
];
|
|
650
|
+
for (const libDir of libDirs) {
|
|
651
|
+
const fullDir = (0, path_1.join)(resolved, libDir);
|
|
652
|
+
if (!(0, fs_1.existsSync)(fullDir))
|
|
653
|
+
continue;
|
|
654
|
+
let entries;
|
|
655
|
+
try {
|
|
656
|
+
entries = (0, fs_1.readdirSync)(fullDir);
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
for (const entry of entries) {
|
|
662
|
+
if (isSkipped(entry))
|
|
663
|
+
continue;
|
|
664
|
+
const fullPath = (0, path_1.join)(fullDir, entry);
|
|
665
|
+
try {
|
|
666
|
+
const stat = (0, fs_1.lstatSync)(fullPath);
|
|
667
|
+
if (stat.isSymbolicLink())
|
|
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
|
+
}
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Discover files in well-known directories (types, utils) whose basename
|
|
701
|
+
* maps directly to a family ID.
|
|
702
|
+
*/
|
|
703
|
+
function discoverNameMatchedPaths(appPath, gitRepoRoot) {
|
|
704
|
+
const result = new Map();
|
|
705
|
+
const resolvedApp = (0, path_1.resolve)(appPath);
|
|
706
|
+
const scanRoots = [
|
|
707
|
+
{ root: (0, path_1.join)(resolvedApp, 'src/utils'), base: resolvedApp },
|
|
708
|
+
{ root: (0, path_1.join)(resolvedApp, 'src/types'), base: resolvedApp },
|
|
709
|
+
];
|
|
710
|
+
// Monorepo-aware: scan platform types and server model directories
|
|
711
|
+
if (gitRepoRoot) {
|
|
712
|
+
const resolvedGitRoot = (0, path_1.resolve)(gitRepoRoot);
|
|
713
|
+
const platformTypes = (0, path_1.join)(resolvedGitRoot, 'webapp/platform/types/src');
|
|
714
|
+
if ((0, fs_1.existsSync)(platformTypes)) {
|
|
715
|
+
scanRoots.push({ root: platformTypes, base: resolvedGitRoot });
|
|
716
|
+
}
|
|
717
|
+
const platformClient = (0, path_1.join)(resolvedGitRoot, 'webapp/platform/client/src');
|
|
718
|
+
if ((0, fs_1.existsSync)(platformClient)) {
|
|
719
|
+
scanRoots.push({ root: platformClient, base: resolvedGitRoot });
|
|
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
|
+
}
|
|
725
|
+
}
|
|
726
|
+
for (const { root, base } of scanRoots) {
|
|
727
|
+
if (!(0, fs_1.existsSync)(root))
|
|
728
|
+
continue;
|
|
729
|
+
let entries;
|
|
730
|
+
try {
|
|
731
|
+
entries = (0, fs_1.readdirSync)(root);
|
|
732
|
+
}
|
|
733
|
+
catch {
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
for (const entry of entries) {
|
|
737
|
+
if (entry.startsWith('.'))
|
|
738
|
+
continue;
|
|
739
|
+
const ext = entry.slice(entry.lastIndexOf('.'));
|
|
740
|
+
if (!['.ts', '.tsx', '.js', '.jsx', '.go'].includes(ext))
|
|
741
|
+
continue;
|
|
742
|
+
// Skip Go test files
|
|
743
|
+
if (entry.endsWith('_test.go'))
|
|
744
|
+
continue;
|
|
745
|
+
const fullPath = (0, path_1.join)(root, entry);
|
|
746
|
+
try {
|
|
747
|
+
const stat = (0, fs_1.lstatSync)(fullPath);
|
|
748
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
// Strip extension and normalize
|
|
755
|
+
const baseName = entry.slice(0, entry.lastIndexOf('.'));
|
|
756
|
+
const familyId = normalizeId(baseName);
|
|
757
|
+
if (familyId.length < 3)
|
|
758
|
+
continue;
|
|
759
|
+
const relPath = (0, path_1.relative)(base, fullPath).replace(/\\/g, '/');
|
|
760
|
+
if (!result.has(familyId))
|
|
761
|
+
result.set(familyId, []);
|
|
762
|
+
result.get(familyId).push(relPath);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
function scanProject(projectRoot, testsRoot, serverRoot, gitRepoRoot) {
|
|
631
768
|
const resolved = (0, path_1.resolve)(projectRoot);
|
|
632
769
|
const resolvedTestsRoot = testsRoot ? (0, path_1.resolve)(testsRoot) : resolved;
|
|
633
770
|
const sourceDirs = discoverSourceDirs(resolved);
|
|
@@ -690,9 +827,12 @@ function scanProject(projectRoot, testsRoot, serverRoot) {
|
|
|
690
827
|
// When a separate serverRoot is provided, discover families from Go source
|
|
691
828
|
// filenames across the three-tier backend (api4, app, store).
|
|
692
829
|
if (serverRoot) {
|
|
693
|
-
const
|
|
830
|
+
const { multiTierFamilies: serverMulti, singleTierFamilies: serverSingle } = discoverServerDerivedFamilies((0, path_1.resolve)(serverRoot));
|
|
694
831
|
const existingIds = new Set(families.map((f) => f.id));
|
|
695
|
-
|
|
832
|
+
// Merge ALL server families (multi + single tier) into existing families,
|
|
833
|
+
// but only add NEW families if they span ≥2 tiers.
|
|
834
|
+
const allServerFamilies = [...serverMulti, ...serverSingle];
|
|
835
|
+
for (const sf of allServerFamilies) {
|
|
696
836
|
// Try exact match, then singular/plural variants
|
|
697
837
|
let target = families.find((f) => f.id === sf.id);
|
|
698
838
|
if (!target && !sf.id.endsWith('s')) {
|
|
@@ -709,13 +849,53 @@ function scanProject(projectRoot, testsRoot, serverRoot) {
|
|
|
709
849
|
}
|
|
710
850
|
}
|
|
711
851
|
}
|
|
712
|
-
else {
|
|
713
|
-
//
|
|
852
|
+
else if (serverMulti.includes(sf)) {
|
|
853
|
+
// Only add new families if they span ≥2 tiers
|
|
714
854
|
families.push(sf);
|
|
715
855
|
existingIds.add(sf.id);
|
|
716
856
|
}
|
|
717
857
|
}
|
|
718
858
|
}
|
|
859
|
+
// Merge test library paths (page objects, helpers) into existing families
|
|
860
|
+
if (testsRoot) {
|
|
861
|
+
const testLibPaths = discoverTestLibPaths(resolvedTestsRoot);
|
|
862
|
+
for (const [libFamilyId, patterns] of testLibPaths) {
|
|
863
|
+
let target = families.find((f) => f.id === libFamilyId);
|
|
864
|
+
if (!target && !libFamilyId.endsWith('s')) {
|
|
865
|
+
target = families.find((f) => f.id === libFamilyId + 's');
|
|
866
|
+
}
|
|
867
|
+
if (!target && libFamilyId.endsWith('s')) {
|
|
868
|
+
target = families.find((f) => f.id === libFamilyId.slice(0, -1));
|
|
869
|
+
}
|
|
870
|
+
if (target) {
|
|
871
|
+
for (const p of patterns) {
|
|
872
|
+
if (!target.webappPaths.includes(p)) {
|
|
873
|
+
target.webappPaths.push(p);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
// Merge name-matched type/util files into existing families
|
|
880
|
+
{
|
|
881
|
+
const nameMatchedPaths = discoverNameMatchedPaths(resolved, gitRepoRoot);
|
|
882
|
+
for (const [nmFamilyId, paths] of nameMatchedPaths) {
|
|
883
|
+
let target = families.find((f) => f.id === nmFamilyId);
|
|
884
|
+
if (!target && !nmFamilyId.endsWith('s')) {
|
|
885
|
+
target = families.find((f) => f.id === nmFamilyId + 's');
|
|
886
|
+
}
|
|
887
|
+
if (!target && nmFamilyId.endsWith('s')) {
|
|
888
|
+
target = families.find((f) => f.id === nmFamilyId.slice(0, -1));
|
|
889
|
+
}
|
|
890
|
+
if (target) {
|
|
891
|
+
for (const p of paths) {
|
|
892
|
+
if (!target.webappPaths.includes(p)) {
|
|
893
|
+
target.webappPaths.push(p);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
719
899
|
const familyIds = new Set(families.map((f) => f.id));
|
|
720
900
|
const unmatchedSourceDirs = sourceDirs.filter((d) => !familyIds.has(normalizeId(d.familyHint)));
|
|
721
901
|
const unmatchedTestDirs = testDirs.filter((d) => !familyIds.has(normalizeId(d.familyHint)));
|
package/dist/training/types.d.ts
CHANGED
|
@@ -47,6 +47,10 @@ export interface EnrichmentResult {
|
|
|
47
47
|
tokensUsed: number;
|
|
48
48
|
costUSD: number;
|
|
49
49
|
skippedFamilies: string[];
|
|
50
|
+
/** Number of LLM requests made */
|
|
51
|
+
requestCount?: number;
|
|
52
|
+
/** Average response time per LLM request in ms */
|
|
53
|
+
avgResponseMs?: number;
|
|
50
54
|
}
|
|
51
55
|
/** A single commit's validation result */
|
|
52
56
|
export interface CommitValidation {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/training/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,WAAW,EAAE,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AAErF,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC1B,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACnD,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,wDAAwD;IACxD,aAAa,EAAE,OAAO,CAAC;CAC1B;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,0CAA0C;AAC1C,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,mBAAmB,EAAE,aAAa,EAAE,CAAC;IACrC,iBAAiB,EAAE,aAAa,EAAE,CAAC;IACnC,KAAK,EAAE;QACH,gBAAgB,EAAE,MAAM,CAAC;QACzB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED,+BAA+B;AAC/B,MAAM,WAAW,gBAAgB;IAC7B,gBAAgB,EAAE,WAAW,EAAE,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/training/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { RouteFamilyManifest } from '../knowledge/route_families.js';
|
|
2
2
|
import type { CommitValidation, ValidationReport } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Check if a file path matches any infrastructure glob pattern.
|
|
5
|
+
* Uses simple string matching — no external glob library needed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isInfraFile(filePath: string): boolean;
|
|
3
8
|
export declare function parseGitLog(log: string): Array<{
|
|
4
9
|
hash: string;
|
|
5
10
|
message: string;
|
|
@@ -10,7 +15,7 @@ export declare function getCommitFiles(projectRoot: string, since: string): Arra
|
|
|
10
15
|
message: string;
|
|
11
16
|
files: string[];
|
|
12
17
|
}>;
|
|
13
|
-
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;
|
|
14
19
|
export declare function buildValidationReport(commits: CommitValidation[], manifest: RouteFamilyManifest): ValidationReport;
|
|
15
20
|
export declare function formatValidationReport(report: ValidationReport): string;
|
|
16
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;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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
3
|
// See LICENSE.txt for license information.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.isInfraFile = isInfraFile;
|
|
5
6
|
exports.parseGitLog = parseGitLog;
|
|
6
7
|
exports.getCommitFiles = getCommitFiles;
|
|
7
8
|
exports.validateCommit = validateCommit;
|
|
@@ -10,6 +11,63 @@ exports.formatValidationReport = formatValidationReport;
|
|
|
10
11
|
const child_process_1 = require("child_process");
|
|
11
12
|
const path_1 = require("path");
|
|
12
13
|
const route_families_js_1 = require("../knowledge/route_families.js");
|
|
14
|
+
/**
|
|
15
|
+
* Glob-style patterns for infrastructure / cross-cutting files that will never
|
|
16
|
+
* belong to a single route family. Excluded from coverage calculations.
|
|
17
|
+
*/
|
|
18
|
+
const INFRA_GLOBS = [
|
|
19
|
+
'Makefile', 'go.mod', 'go.sum',
|
|
20
|
+
'*.lock',
|
|
21
|
+
'**/mocks/*', '**/storetest/*', '**/testlib/*',
|
|
22
|
+
'**/i18n/*',
|
|
23
|
+
'**/.github/*', '**/.ci/*', '**/scripts/*',
|
|
24
|
+
'**/docker-compose*',
|
|
25
|
+
'**/__fixtures__/*', '**/test_templates/*',
|
|
26
|
+
'playwright.config.ts', 'global-setup.ts',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Check if a file path matches any infrastructure glob pattern.
|
|
30
|
+
* Uses simple string matching — no external glob library needed.
|
|
31
|
+
*/
|
|
32
|
+
function isInfraFile(filePath) {
|
|
33
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
34
|
+
for (const pattern of INFRA_GLOBS) {
|
|
35
|
+
if (pattern.startsWith('**/')) {
|
|
36
|
+
// Match anywhere in the path
|
|
37
|
+
const suffix = pattern.slice(3);
|
|
38
|
+
if (suffix.endsWith('/*')) {
|
|
39
|
+
// Directory match: **/mocks/* → any segment named "mocks" with a child
|
|
40
|
+
const dirName = suffix.slice(0, -2);
|
|
41
|
+
if (normalized.includes(`/${dirName}/`) || normalized.startsWith(`${dirName}/`))
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
else if (suffix.endsWith('*')) {
|
|
45
|
+
// Prefix match: **/docker-compose* → file starting with docker-compose
|
|
46
|
+
const prefix = suffix.slice(0, -1);
|
|
47
|
+
const base = normalized.split('/').pop() || '';
|
|
48
|
+
if (base.startsWith(prefix))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
if (normalized.endsWith(`/${suffix}`) || normalized === suffix)
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (pattern.startsWith('*.')) {
|
|
57
|
+
// Extension match: *.lock
|
|
58
|
+
const ext = pattern.slice(1);
|
|
59
|
+
if (normalized.endsWith(ext))
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Exact basename match: Makefile, go.mod, go.sum
|
|
64
|
+
const base = normalized.split('/').pop() || '';
|
|
65
|
+
if (base === pattern)
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
13
71
|
function parseGitLog(log) {
|
|
14
72
|
const commits = [];
|
|
15
73
|
let current = null;
|
|
@@ -55,16 +113,56 @@ function getCommitFiles(projectRoot, since) {
|
|
|
55
113
|
}
|
|
56
114
|
return parseGitLog(log);
|
|
57
115
|
}
|
|
58
|
-
|
|
59
|
-
|
|
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) {
|
|
155
|
+
// Filter out non-source files and infrastructure files
|
|
60
156
|
const sourceFiles = files.filter((f) => {
|
|
61
157
|
return !f.endsWith('.md') && !f.endsWith('.json') && !f.endsWith('.yml') && !f.endsWith('.yaml') &&
|
|
62
|
-
!f.startsWith('.') && !f.includes('node_modules/');
|
|
158
|
+
!f.startsWith('.') && !f.includes('node_modules/') && !isInfraFile(f);
|
|
63
159
|
});
|
|
64
160
|
if (sourceFiles.length === 0) {
|
|
65
161
|
return { hash, message, changedFiles: [], boundFiles: 0, unboundFiles: [], familiesHit: [] };
|
|
66
162
|
}
|
|
67
|
-
const bindings =
|
|
163
|
+
const bindings = pathPrefixes
|
|
164
|
+
? bindWithPrefixes(sourceFiles, manifest, pathPrefixes)
|
|
165
|
+
: (0, route_families_js_1.bindFilesToFamilies)(sourceFiles, manifest);
|
|
68
166
|
const bound = bindings.filter((b) => b.bindings.length > 0);
|
|
69
167
|
const unbound = bindings.filter((b) => b.bindings.length === 0);
|
|
70
168
|
const familiesHit = new Set();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.
|
|
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",
|