@yasserkhanorg/e2e-agents 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -9
- package/dist/cli/commands/train.d.ts +3 -0
- package/dist/cli/commands/train.d.ts.map +1 -0
- package/dist/cli/commands/train.js +307 -0
- package/dist/cli/parse_args.d.ts.map +1 -1
- package/dist/cli/parse_args.js +7 -1
- package/dist/cli/types.d.ts +6 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/usage.d.ts.map +1 -1
- package/dist/cli/usage.js +7 -1
- package/dist/cli.js +5 -0
- package/dist/esm/cli/commands/train.js +271 -0
- package/dist/esm/cli/parse_args.js +7 -1
- package/dist/esm/cli/usage.js +7 -1
- package/dist/esm/cli.js +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/knowledge/route_families.js +2 -2
- package/dist/esm/training/enricher.js +273 -0
- package/dist/esm/training/merger.js +137 -0
- package/dist/esm/training/scanner.js +386 -0
- package/dist/esm/training/types.js +6 -0
- package/dist/esm/training/validator.js +153 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/knowledge/route_families.d.ts +2 -0
- package/dist/knowledge/route_families.d.ts.map +1 -1
- package/dist/knowledge/route_families.js +2 -0
- package/dist/training/enricher.d.ts +15 -0
- package/dist/training/enricher.d.ts.map +1 -0
- package/dist/training/enricher.js +278 -0
- package/dist/training/merger.d.ts +5 -0
- package/dist/training/merger.d.ts.map +1 -0
- package/dist/training/merger.js +141 -0
- package/dist/training/scanner.d.ts +5 -0
- package/dist/training/scanner.d.ts.map +1 -0
- package/dist/training/scanner.js +391 -0
- package/dist/training/types.d.ts +109 -0
- package/dist/training/types.d.ts.map +1 -0
- package/dist/training/types.js +9 -0
- package/dist/training/validator.d.ts +16 -0
- package/dist/training/validator.d.ts.map +1 -0
- package/dist/training/validator.js +160 -0
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.parseGitLog = parseGitLog;
|
|
6
|
+
exports.getCommitFiles = getCommitFiles;
|
|
7
|
+
exports.validateCommit = validateCommit;
|
|
8
|
+
exports.buildValidationReport = buildValidationReport;
|
|
9
|
+
exports.formatValidationReport = formatValidationReport;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const route_families_js_1 = require("../knowledge/route_families.js");
|
|
13
|
+
function parseGitLog(log) {
|
|
14
|
+
const commits = [];
|
|
15
|
+
let current = null;
|
|
16
|
+
for (const line of log.split('\n')) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (!trimmed) {
|
|
19
|
+
if (current) {
|
|
20
|
+
commits.push(current);
|
|
21
|
+
current = null;
|
|
22
|
+
}
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (trimmed.includes('|') && /^[0-9a-f]{7,40}\|/.test(trimmed)) {
|
|
26
|
+
if (current) {
|
|
27
|
+
commits.push(current);
|
|
28
|
+
}
|
|
29
|
+
const [hash, ...rest] = trimmed.split('|');
|
|
30
|
+
current = { hash, message: rest.join('|'), files: [] };
|
|
31
|
+
}
|
|
32
|
+
else if (current) {
|
|
33
|
+
current.files.push(trimmed);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (current) {
|
|
37
|
+
commits.push(current);
|
|
38
|
+
}
|
|
39
|
+
return commits;
|
|
40
|
+
}
|
|
41
|
+
function getCommitFiles(projectRoot, since) {
|
|
42
|
+
const resolved = (0, path_1.resolve)(projectRoot);
|
|
43
|
+
let log;
|
|
44
|
+
try {
|
|
45
|
+
log = (0, child_process_1.execFileSync)('git', ['log', '--name-only', '--pretty=format:%H|%s', `${since}..HEAD`], {
|
|
46
|
+
cwd: resolved,
|
|
47
|
+
encoding: 'utf-8',
|
|
48
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.warn(`[train] git log failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
return parseGitLog(log);
|
|
57
|
+
}
|
|
58
|
+
function validateCommit(manifest, files, hash, message) {
|
|
59
|
+
// Filter out non-source files
|
|
60
|
+
const sourceFiles = files.filter((f) => {
|
|
61
|
+
return !f.endsWith('.md') && !f.endsWith('.json') && !f.endsWith('.yml') && !f.endsWith('.yaml') &&
|
|
62
|
+
!f.startsWith('.') && !f.includes('node_modules/');
|
|
63
|
+
});
|
|
64
|
+
if (sourceFiles.length === 0) {
|
|
65
|
+
return { hash, message, changedFiles: [], boundFiles: 0, unboundFiles: [], familiesHit: [] };
|
|
66
|
+
}
|
|
67
|
+
const bindings = (0, route_families_js_1.bindFilesToFamilies)(sourceFiles, manifest);
|
|
68
|
+
const bound = bindings.filter((b) => b.bindings.length > 0);
|
|
69
|
+
const unbound = bindings.filter((b) => b.bindings.length === 0);
|
|
70
|
+
const familiesHit = new Set();
|
|
71
|
+
for (const b of bound) {
|
|
72
|
+
for (const binding of b.bindings) {
|
|
73
|
+
familiesHit.add(binding.family);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
hash,
|
|
78
|
+
message,
|
|
79
|
+
changedFiles: sourceFiles,
|
|
80
|
+
boundFiles: bound.length,
|
|
81
|
+
unboundFiles: unbound.map((b) => b.file),
|
|
82
|
+
familiesHit: Array.from(familiesHit),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function buildValidationReport(commits, manifest) {
|
|
86
|
+
let totalFiles = 0;
|
|
87
|
+
let boundFiles = 0;
|
|
88
|
+
let unboundFiles = 0;
|
|
89
|
+
const familyHits = {};
|
|
90
|
+
const unboundCounts = {};
|
|
91
|
+
for (const commit of commits) {
|
|
92
|
+
totalFiles += commit.changedFiles.length;
|
|
93
|
+
boundFiles += commit.boundFiles;
|
|
94
|
+
unboundFiles += commit.unboundFiles.length;
|
|
95
|
+
for (const fam of commit.familiesHit) {
|
|
96
|
+
familyHits[fam] = (familyHits[fam] || 0) + 1;
|
|
97
|
+
}
|
|
98
|
+
for (const uf of commit.unboundFiles) {
|
|
99
|
+
unboundCounts[uf] = (unboundCounts[uf] || 0) + 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const allFamilyIds = manifest.families.map((f) => f.id);
|
|
103
|
+
const hitFamilyIds = new Set(Object.keys(familyHits));
|
|
104
|
+
const neverHitFamilies = allFamilyIds.filter((id) => !hitFamilyIds.has(id));
|
|
105
|
+
// Cluster unbound files by directory
|
|
106
|
+
const dirCounts = {};
|
|
107
|
+
for (const [file, count] of Object.entries(unboundCounts)) {
|
|
108
|
+
const dir = file.split('/').slice(0, -1).join('/');
|
|
109
|
+
dirCounts[dir] = (dirCounts[dir] || 0) + count;
|
|
110
|
+
}
|
|
111
|
+
const unboundFileClusters = Object.entries(dirCounts)
|
|
112
|
+
.sort(([, a], [, b]) => b - a)
|
|
113
|
+
.slice(0, 20)
|
|
114
|
+
.map(([pattern, count]) => ({
|
|
115
|
+
pattern: `${pattern}/*`,
|
|
116
|
+
count,
|
|
117
|
+
suggestedFamily: pattern.split('/').pop() || 'unknown',
|
|
118
|
+
}));
|
|
119
|
+
return {
|
|
120
|
+
totalCommits: commits.length,
|
|
121
|
+
totalFiles,
|
|
122
|
+
boundFiles,
|
|
123
|
+
unboundFiles,
|
|
124
|
+
coveragePercent: totalFiles > 0 ? Math.round((boundFiles / totalFiles) * 100) : 100,
|
|
125
|
+
commits,
|
|
126
|
+
familyHits,
|
|
127
|
+
neverHitFamilies,
|
|
128
|
+
unboundFileClusters,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function formatValidationReport(report) {
|
|
132
|
+
const lines = [];
|
|
133
|
+
lines.push(`Validated against ${report.totalCommits} commits`);
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push(`Coverage: ${report.coveragePercent}% of files bound (${report.boundFiles}/${report.totalFiles})`);
|
|
136
|
+
lines.push('');
|
|
137
|
+
// Family hit distribution
|
|
138
|
+
const sorted = Object.entries(report.familyHits).sort(([, a], [, b]) => b - a);
|
|
139
|
+
if (sorted.length > 0) {
|
|
140
|
+
lines.push('Family hit distribution:');
|
|
141
|
+
const maxHits = sorted[0][1];
|
|
142
|
+
for (const [family, hits] of sorted) {
|
|
143
|
+
const bar = '\u2588'.repeat(Math.max(1, Math.round((hits / maxHits) * 12)));
|
|
144
|
+
lines.push(` ${family.padEnd(20)} ${bar} ${hits} commits`);
|
|
145
|
+
}
|
|
146
|
+
if (report.neverHitFamilies.length > 0) {
|
|
147
|
+
lines.push(` (never hit)${' '.repeat(8)}${report.neverHitFamilies.join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
lines.push('');
|
|
150
|
+
}
|
|
151
|
+
// Unbound file clusters
|
|
152
|
+
if (report.unboundFileClusters.length > 0) {
|
|
153
|
+
lines.push(`Unbound files (${report.unboundFiles} files across ${report.totalCommits} commits):`);
|
|
154
|
+
for (const cluster of report.unboundFileClusters.slice(0, 10)) {
|
|
155
|
+
lines.push(` ${cluster.pattern.padEnd(50)} — ${cluster.count} commits (suggest: ${cluster.suggestedFamily})`);
|
|
156
|
+
}
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
return lines.join('\n');
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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",
|