@yasserkhanorg/e2e-agents 1.5.0 → 1.7.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/dist/cli/commands/train.d.ts.map +1 -1
- package/dist/cli/commands/train.js +125 -50
- package/dist/cli/parse_args.d.ts.map +1 -1
- package/dist/cli/parse_args.js +3 -0
- package/dist/cli/types.d.ts +3 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/esm/cli/commands/train.js +125 -50
- package/dist/esm/cli/parse_args.js +3 -0
- package/dist/esm/logger.js +29 -2
- package/dist/esm/pipeline/orchestrator.js +17 -3
- package/dist/esm/training/enricher.js +82 -11
- package/dist/esm/training/merger.js +77 -10
- package/dist/esm/training/scanner.js +523 -2
- package/dist/esm/training/validator.js +58 -2
- 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 +3 -1
- package/dist/training/enricher.d.ts.map +1 -1
- package/dist/training/enricher.js +82 -11
- package/dist/training/merger.d.ts +11 -1
- package/dist/training/merger.d.ts.map +1 -1
- package/dist/training/merger.js +77 -10
- package/dist/training/scanner.d.ts +28 -2
- package/dist/training/scanner.d.ts.map +1 -1
- package/dist/training/scanner.js +527 -2
- package/dist/training/types.d.ts +8 -0
- package/dist/training/types.d.ts.map +1 -1
- package/dist/training/validator.d.ts +5 -0
- package/dist/training/validator.d.ts.map +1 -1
- package/dist/training/validator.js +59 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"train.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/train.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -36,12 +36,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
})();
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.runTrainCommand = runTrainCommand;
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
39
40
|
const fs_1 = require("fs");
|
|
40
41
|
const path_1 = require("path");
|
|
41
42
|
const readline = __importStar(require("readline"));
|
|
42
43
|
const config_js_1 = require("../../agent/config.js");
|
|
43
44
|
const route_families_js_1 = require("../../knowledge/route_families.js");
|
|
44
45
|
const provider_factory_js_1 = require("../../provider_factory.js");
|
|
46
|
+
const logger_js_1 = require("../../logger.js");
|
|
45
47
|
const scanner_js_1 = require("../../training/scanner.js");
|
|
46
48
|
const merger_js_1 = require("../../training/merger.js");
|
|
47
49
|
const enricher_js_1 = require("../../training/enricher.js");
|
|
@@ -111,9 +113,32 @@ function resolveTrainOptions(args, autoConfig) {
|
|
|
111
113
|
if (!inApp && !inTests) {
|
|
112
114
|
throw new TrainError(`Output path must be within the project root or tests root: ${resolvedOutputPath}`);
|
|
113
115
|
}
|
|
116
|
+
// Discover git repo root for monorepo-aware scanning and validation
|
|
117
|
+
let gitRepoRoot;
|
|
118
|
+
try {
|
|
119
|
+
gitRepoRoot = (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
|
|
120
|
+
cwd: resolvedAppPath,
|
|
121
|
+
encoding: 'utf-8',
|
|
122
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
123
|
+
}).trim();
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Not a git repo or git not available
|
|
127
|
+
}
|
|
128
|
+
// Resolve serverRoot: explicit flag, or auto-detect from git repo root
|
|
129
|
+
let serverRoot = args.serverPath;
|
|
130
|
+
if (!serverRoot && gitRepoRoot) {
|
|
131
|
+
const serverDir = (0, path_1.join)(gitRepoRoot, 'server');
|
|
132
|
+
if ((0, fs_1.existsSync)(serverDir)) {
|
|
133
|
+
serverRoot = serverDir;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const resolvedServerRoot = serverRoot ? (0, path_1.resolve)(serverRoot) : undefined;
|
|
114
137
|
return {
|
|
115
138
|
appPath: resolvedAppPath,
|
|
116
139
|
testsRoot: resolvedTestsRoot,
|
|
140
|
+
serverRoot: resolvedServerRoot,
|
|
141
|
+
gitRepoRoot: gitRepoRoot ? (0, path_1.resolve)(gitRepoRoot) : undefined,
|
|
117
142
|
enrich: args.trainEnrich !== false,
|
|
118
143
|
validate: args.trainValidate || false,
|
|
119
144
|
since,
|
|
@@ -152,37 +177,47 @@ function serializeManifest(manifest) {
|
|
|
152
177
|
}
|
|
153
178
|
async function runTrainCommand(args, autoConfig) {
|
|
154
179
|
const opts = resolveTrainOptions(args, autoConfig);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
180
|
+
const totalTimer = logger_js_1.logger.timer('train-total');
|
|
181
|
+
const timings = {};
|
|
182
|
+
// Configure observability from CLI flags
|
|
183
|
+
if (args.verbose)
|
|
184
|
+
logger_js_1.logger.setLevel(logger_js_1.LogLevel.DEBUG);
|
|
185
|
+
if (args.jsonOutput)
|
|
186
|
+
logger_js_1.logger.setJsonMode(true);
|
|
187
|
+
logger_js_1.logger.info('e2e-ai-agents train');
|
|
188
|
+
logger_js_1.logger.info('===================');
|
|
159
189
|
// ---------- Phase 1: Deterministic scan ----------
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
logger_js_1.logger.info('Scanning project structure...');
|
|
191
|
+
if (opts.serverRoot) {
|
|
192
|
+
logger_js_1.logger.info(`Server root: ${opts.serverRoot}`);
|
|
193
|
+
}
|
|
194
|
+
const scanTimer = logger_js_1.logger.timer('scan');
|
|
195
|
+
const scanResult = (0, scanner_js_1.scanProject)(opts.appPath, opts.testsRoot !== opts.appPath ? opts.testsRoot : undefined, opts.serverRoot, opts.gitRepoRoot);
|
|
196
|
+
timings.scan = scanTimer.end();
|
|
197
|
+
logger_js_1.logger.info(`Found ${scanResult.stats.totalSourceFiles} source files, ${scanResult.stats.totalTestFiles} test files`);
|
|
198
|
+
logger_js_1.logger.info(`Discovered ${scanResult.families.length} candidate families`);
|
|
164
199
|
if (scanResult.families.length === 0) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
console.log(' (tests/, e2e/, specs/) with matching names.');
|
|
200
|
+
logger_js_1.logger.info('No families discovered. Make sure your project has recognizable');
|
|
201
|
+
logger_js_1.logger.info('source directories (src/, server/, app/) and test directories');
|
|
202
|
+
logger_js_1.logger.info('(tests/, e2e/, specs/) with matching names.');
|
|
169
203
|
return;
|
|
170
204
|
}
|
|
171
205
|
// ---------- Phase 2: Merge with existing ----------
|
|
206
|
+
const mergeTimer = logger_js_1.logger.timer('merge');
|
|
172
207
|
const existing = (0, route_families_js_1.loadRouteFamilyManifest)(opts.testsRoot);
|
|
173
208
|
if (existing) {
|
|
174
|
-
|
|
209
|
+
logger_js_1.logger.info(`Found existing manifest with ${existing.families.length} families`);
|
|
175
210
|
}
|
|
176
211
|
let mergeResult = (0, merger_js_1.mergeFamilies)(existing, scanResult.families);
|
|
177
|
-
|
|
212
|
+
timings.merge = mergeTimer.end();
|
|
213
|
+
logger_js_1.logger.info(`Merge: ${mergeResult.summary}`);
|
|
178
214
|
// ---------- Phase 3: Stale detection ----------
|
|
179
215
|
if (mergeResult.manifest.families.length > 0) {
|
|
180
|
-
const stale = (0, merger_js_1.detectStaleFamilies)(mergeResult.manifest, opts.appPath);
|
|
216
|
+
const stale = (0, merger_js_1.detectStaleFamilies)(mergeResult.manifest, opts.appPath, opts.testsRoot);
|
|
181
217
|
if (stale.length > 0) {
|
|
182
|
-
|
|
183
|
-
console.log(` Stale families detected (${stale.length}):`);
|
|
218
|
+
logger_js_1.logger.info(`Stale families detected (${stale.length}):`);
|
|
184
219
|
for (const id of stale) {
|
|
185
|
-
|
|
220
|
+
logger_js_1.logger.info(` ${id} — paths no longer exist`);
|
|
186
221
|
}
|
|
187
222
|
if (!opts.yes && !opts.dryRun && process.stdin.isTTY) {
|
|
188
223
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -192,7 +227,7 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
192
227
|
const staleSet = new Set(stale);
|
|
193
228
|
mergeResult.manifest.families = mergeResult.manifest.families.filter((f) => !staleSet.has(f.id));
|
|
194
229
|
mergeResult.staleFamilies = stale;
|
|
195
|
-
|
|
230
|
+
logger_js_1.logger.info(`Removed ${stale.length} stale families`);
|
|
196
231
|
}
|
|
197
232
|
}
|
|
198
233
|
finally {
|
|
@@ -202,29 +237,41 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
202
237
|
}
|
|
203
238
|
}
|
|
204
239
|
// ---------- Phase 4: LLM Enrichment ----------
|
|
240
|
+
let enrichTokens = 0;
|
|
241
|
+
let enrichCost = 0;
|
|
242
|
+
let enrichRequests = 0;
|
|
243
|
+
let enrichAvgResponseMs = 0;
|
|
205
244
|
if (opts.enrich) {
|
|
206
|
-
|
|
207
|
-
|
|
245
|
+
logger_js_1.logger.info('Enriching with LLM...');
|
|
246
|
+
const enrichTimer = logger_js_1.logger.timer('enrich');
|
|
208
247
|
try {
|
|
209
248
|
const provider = await provider_factory_js_1.LLMProviderFactory.createFromEnv();
|
|
210
|
-
const enrichResult = await (0, enricher_js_1.enrichFamilies)(mergeResult.manifest.families, scanResult.families, opts.appPath, provider, opts.budgetUSD);
|
|
249
|
+
const enrichResult = await (0, enricher_js_1.enrichFamilies)(mergeResult.manifest.families, scanResult.families, opts.appPath, provider, opts.budgetUSD, opts.testsRoot !== opts.appPath ? opts.testsRoot : undefined);
|
|
211
250
|
mergeResult.manifest.families = enrichResult.enrichedFamilies;
|
|
212
|
-
|
|
251
|
+
enrichTokens = enrichResult.tokensUsed;
|
|
252
|
+
enrichCost = enrichResult.costUSD;
|
|
253
|
+
enrichRequests = enrichResult.requestCount ?? 0;
|
|
254
|
+
enrichAvgResponseMs = enrichResult.avgResponseMs ?? 0;
|
|
255
|
+
logger_js_1.logger.info(`Enriched ${enrichResult.enrichedFamilies.length} families`, {
|
|
256
|
+
tokens: enrichResult.tokensUsed,
|
|
257
|
+
cost: enrichResult.costUSD,
|
|
258
|
+
requests: enrichRequests,
|
|
259
|
+
avgResponseMs: enrichAvgResponseMs,
|
|
260
|
+
});
|
|
213
261
|
if (enrichResult.skippedFamilies.length > 0) {
|
|
214
|
-
|
|
262
|
+
logger_js_1.logger.info(`Skipped ${enrichResult.skippedFamilies.length} families (budget limit)`);
|
|
215
263
|
}
|
|
216
264
|
}
|
|
217
265
|
catch (error) {
|
|
218
|
-
|
|
219
|
-
|
|
266
|
+
logger_js_1.logger.warn(`LLM enrichment failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
267
|
+
logger_js_1.logger.warn('Continuing with deterministic results. Use --no-enrich to skip LLM.');
|
|
220
268
|
}
|
|
269
|
+
timings.enrich = enrichTimer.end();
|
|
221
270
|
}
|
|
222
271
|
// ---------- Phase 5: Write manifest ----------
|
|
223
|
-
console.log('');
|
|
224
272
|
const json = serializeManifest(mergeResult.manifest);
|
|
225
273
|
if (opts.dryRun) {
|
|
226
|
-
|
|
227
|
-
console.log('');
|
|
274
|
+
logger_js_1.logger.info('Dry run — proposed manifest:');
|
|
228
275
|
console.log(json);
|
|
229
276
|
}
|
|
230
277
|
else {
|
|
@@ -235,28 +282,28 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
235
282
|
const tmpPath = `${opts.outputPath}.tmp`;
|
|
236
283
|
(0, fs_1.writeFileSync)(tmpPath, json, 'utf-8');
|
|
237
284
|
(0, fs_1.renameSync)(tmpPath, opts.outputPath);
|
|
238
|
-
|
|
239
|
-
|
|
285
|
+
logger_js_1.logger.info(`Wrote ${opts.outputPath}`);
|
|
286
|
+
logger_js_1.logger.info(`${mergeResult.manifest.families.length} families`);
|
|
240
287
|
}
|
|
241
288
|
// ---------- Phase 6: Report unmatched ----------
|
|
242
289
|
if (scanResult.unmatchedSourceDirs.length > 0 || scanResult.unmatchedTestDirs.length > 0) {
|
|
243
|
-
|
|
244
|
-
console.log(' Unmatched (review manually):');
|
|
290
|
+
logger_js_1.logger.info('Unmatched (review manually):');
|
|
245
291
|
for (const dir of scanResult.unmatchedSourceDirs.slice(0, 10)) {
|
|
246
|
-
|
|
292
|
+
logger_js_1.logger.info(` source: ${dir.relativePath}`);
|
|
247
293
|
}
|
|
248
294
|
for (const dir of scanResult.unmatchedTestDirs.slice(0, 10)) {
|
|
249
|
-
|
|
295
|
+
logger_js_1.logger.info(` test: ${dir.relativePath}`);
|
|
250
296
|
}
|
|
251
297
|
if (scanResult.unmatchedSourceDirs.length + scanResult.unmatchedTestDirs.length > 20) {
|
|
252
|
-
|
|
298
|
+
logger_js_1.logger.info(' ... and more');
|
|
253
299
|
}
|
|
254
300
|
}
|
|
255
301
|
// ---------- Phase 7: Validation (optional) ----------
|
|
302
|
+
let validationReport;
|
|
256
303
|
if (opts.validate) {
|
|
304
|
+
const validateTimer = logger_js_1.logger.timer('validate');
|
|
257
305
|
if (opts.pr) {
|
|
258
|
-
|
|
259
|
-
console.log(` Validating against PR #${opts.pr}...`);
|
|
306
|
+
logger_js_1.logger.info(`Validating against PR #${opts.pr}...`);
|
|
260
307
|
// Check for gh CLI
|
|
261
308
|
const { execFileSync } = await import('child_process');
|
|
262
309
|
try {
|
|
@@ -279,29 +326,57 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
279
326
|
throw new TrainError(`Error fetching PR #${opts.pr}: ${error instanceof Error ? error.message : String(error)}`);
|
|
280
327
|
}
|
|
281
328
|
if (prFiles.length === 0) {
|
|
282
|
-
|
|
329
|
+
logger_js_1.logger.info('No files found in PR.');
|
|
283
330
|
}
|
|
284
331
|
else {
|
|
285
332
|
const validation = (0, validator_js_1.validateCommit)(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}`);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
console.log((0, validator_js_1.formatValidationReport)(report));
|
|
333
|
+
validationReport = (0, validator_js_1.buildValidationReport)([validation], mergeResult.manifest);
|
|
334
|
+
logger_js_1.logger.info((0, validator_js_1.formatValidationReport)(validationReport));
|
|
289
335
|
}
|
|
290
336
|
}
|
|
291
337
|
else {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const commits = (0, validator_js_1.getCommitFiles)(opts.appPath, opts.since);
|
|
338
|
+
logger_js_1.logger.info(`Validating against git history (${opts.since})...`);
|
|
339
|
+
const commits = (0, validator_js_1.getCommitFiles)(opts.gitRepoRoot || opts.appPath, opts.since);
|
|
295
340
|
if (commits.length === 0) {
|
|
296
|
-
|
|
341
|
+
logger_js_1.logger.info('No commits found in range.');
|
|
297
342
|
}
|
|
298
343
|
else {
|
|
299
344
|
const validations = commits.map((c) => (0, validator_js_1.validateCommit)(mergeResult.manifest, c.files, c.hash, c.message));
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
console.log((0, validator_js_1.formatValidationReport)(report));
|
|
345
|
+
validationReport = (0, validator_js_1.buildValidationReport)(validations, mergeResult.manifest);
|
|
346
|
+
logger_js_1.logger.info((0, validator_js_1.formatValidationReport)(validationReport));
|
|
303
347
|
}
|
|
304
348
|
}
|
|
349
|
+
timings.validate = validateTimer.end();
|
|
350
|
+
}
|
|
351
|
+
timings.total = totalTimer.end();
|
|
352
|
+
// ---------- Write train report ----------
|
|
353
|
+
if (!opts.dryRun) {
|
|
354
|
+
const reportDir = (0, path_1.dirname)(opts.outputPath);
|
|
355
|
+
const trainReport = {
|
|
356
|
+
timestamp: new Date().toISOString(),
|
|
357
|
+
version: '1.7.0',
|
|
358
|
+
timings,
|
|
359
|
+
families: {
|
|
360
|
+
total: mergeResult.manifest.families.length,
|
|
361
|
+
new: mergeResult.newFamilies.length,
|
|
362
|
+
updated: mergeResult.updatedFamilies.length,
|
|
363
|
+
stale: mergeResult.staleFamilies.length,
|
|
364
|
+
},
|
|
365
|
+
coverage: validationReport ? {
|
|
366
|
+
percent: validationReport.coveragePercent,
|
|
367
|
+
boundFiles: validationReport.boundFiles,
|
|
368
|
+
totalFiles: validationReport.totalFiles,
|
|
369
|
+
} : undefined,
|
|
370
|
+
llm: opts.enrich ? {
|
|
371
|
+
tokensUsed: enrichTokens,
|
|
372
|
+
costUSD: enrichCost,
|
|
373
|
+
requests: enrichRequests,
|
|
374
|
+
avgResponseMs: enrichAvgResponseMs,
|
|
375
|
+
} : undefined,
|
|
376
|
+
};
|
|
377
|
+
const reportPath = (0, path_1.join)(reportDir, 'train-report.json');
|
|
378
|
+
(0, fs_1.writeFileSync)(reportPath, JSON.stringify(trainReport, null, 2) + '\n', 'utf-8');
|
|
379
|
+
logger_js_1.logger.debug('Wrote train report', { path: reportPath });
|
|
305
380
|
}
|
|
306
|
-
|
|
381
|
+
logger_js_1.logger.info('Done.');
|
|
307
382
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse_args.d.ts","sourceRoot":"","sources":["../../src/cli/parse_args.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,UAAU,EAAC,MAAM,YAAY,CAAC;AAEpD,eAAO,MAAM,iBAAiB,UAA8D,CAAC;AAE7F,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAmBlF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAmBtE;
|
|
1
|
+
{"version":3,"file":"parse_args.d.ts","sourceRoot":"","sources":["../../src/cli/parse_args.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,UAAU,EAAC,MAAM,YAAY,CAAC;AAEpD,eAAO,MAAM,iBAAiB,UAA8D,CAAC;AAE7F,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAmBlF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAmBtE;AA8ID,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA4EpD"}
|
package/dist/cli/parse_args.js
CHANGED
|
@@ -71,6 +71,8 @@ const FLAGS = {
|
|
|
71
71
|
'--no-enrich': { key: 'trainEnrich', type: 'boolean-false' },
|
|
72
72
|
'--validate': { key: 'trainValidate', type: 'boolean' },
|
|
73
73
|
'--yes': { key: 'trainYes', type: 'boolean', aliases: ['-y'] },
|
|
74
|
+
'--verbose': { key: 'verbose', type: 'boolean', aliases: ['-v'] },
|
|
75
|
+
'--json': { key: 'jsonOutput', type: 'boolean' },
|
|
74
76
|
'--mattermost': { key: 'profile', type: 'boolean', transform: () => 'mattermost' },
|
|
75
77
|
// -- string flags --
|
|
76
78
|
'--config': { key: 'configPath', type: 'string' },
|
|
@@ -101,6 +103,7 @@ const FLAGS = {
|
|
|
101
103
|
'--heal-report': { key: 'analyzeHealReport', type: 'string' },
|
|
102
104
|
'--flow-catalog': { key: 'flowCatalogPath', type: 'string' },
|
|
103
105
|
'--output': { key: 'trainOutput', type: 'string' },
|
|
106
|
+
'--server-path': { key: 'serverPath', type: 'string' },
|
|
104
107
|
// -- number flags (with isFinite guard) --
|
|
105
108
|
'--pipeline-scenarios': { key: 'pipelineScenarios', type: 'number' },
|
|
106
109
|
'--time': { key: 'timeLimitMinutes', type: 'number' },
|
package/dist/cli/types.d.ts
CHANGED
package/dist/cli/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
3
4
|
import { existsSync, mkdirSync, renameSync, writeFileSync } from 'fs';
|
|
4
5
|
import { dirname, join, resolve } from 'path';
|
|
5
6
|
import * as readline from 'readline';
|
|
6
7
|
import { resolveConfig } from '../../agent/config.js';
|
|
7
8
|
import { loadRouteFamilyManifest } from '../../knowledge/route_families.js';
|
|
8
9
|
import { LLMProviderFactory } from '../../provider_factory.js';
|
|
10
|
+
import { logger, LogLevel } from '../../logger.js';
|
|
9
11
|
import { scanProject } from '../../training/scanner.js';
|
|
10
12
|
import { mergeFamilies, detectStaleFamilies } from '../../training/merger.js';
|
|
11
13
|
import { enrichFamilies } from '../../training/enricher.js';
|
|
@@ -75,9 +77,32 @@ function resolveTrainOptions(args, autoConfig) {
|
|
|
75
77
|
if (!inApp && !inTests) {
|
|
76
78
|
throw new TrainError(`Output path must be within the project root or tests root: ${resolvedOutputPath}`);
|
|
77
79
|
}
|
|
80
|
+
// Discover git repo root for monorepo-aware scanning and validation
|
|
81
|
+
let gitRepoRoot;
|
|
82
|
+
try {
|
|
83
|
+
gitRepoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
84
|
+
cwd: resolvedAppPath,
|
|
85
|
+
encoding: 'utf-8',
|
|
86
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
87
|
+
}).trim();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Not a git repo or git not available
|
|
91
|
+
}
|
|
92
|
+
// Resolve serverRoot: explicit flag, or auto-detect from git repo root
|
|
93
|
+
let serverRoot = args.serverPath;
|
|
94
|
+
if (!serverRoot && gitRepoRoot) {
|
|
95
|
+
const serverDir = join(gitRepoRoot, 'server');
|
|
96
|
+
if (existsSync(serverDir)) {
|
|
97
|
+
serverRoot = serverDir;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const resolvedServerRoot = serverRoot ? resolve(serverRoot) : undefined;
|
|
78
101
|
return {
|
|
79
102
|
appPath: resolvedAppPath,
|
|
80
103
|
testsRoot: resolvedTestsRoot,
|
|
104
|
+
serverRoot: resolvedServerRoot,
|
|
105
|
+
gitRepoRoot: gitRepoRoot ? resolve(gitRepoRoot) : undefined,
|
|
81
106
|
enrich: args.trainEnrich !== false,
|
|
82
107
|
validate: args.trainValidate || false,
|
|
83
108
|
since,
|
|
@@ -116,37 +141,47 @@ function serializeManifest(manifest) {
|
|
|
116
141
|
}
|
|
117
142
|
export async function runTrainCommand(args, autoConfig) {
|
|
118
143
|
const opts = resolveTrainOptions(args, autoConfig);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
const totalTimer = logger.timer('train-total');
|
|
145
|
+
const timings = {};
|
|
146
|
+
// Configure observability from CLI flags
|
|
147
|
+
if (args.verbose)
|
|
148
|
+
logger.setLevel(LogLevel.DEBUG);
|
|
149
|
+
if (args.jsonOutput)
|
|
150
|
+
logger.setJsonMode(true);
|
|
151
|
+
logger.info('e2e-ai-agents train');
|
|
152
|
+
logger.info('===================');
|
|
123
153
|
// ---------- Phase 1: Deterministic scan ----------
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
154
|
+
logger.info('Scanning project structure...');
|
|
155
|
+
if (opts.serverRoot) {
|
|
156
|
+
logger.info(`Server root: ${opts.serverRoot}`);
|
|
157
|
+
}
|
|
158
|
+
const scanTimer = logger.timer('scan');
|
|
159
|
+
const scanResult = scanProject(opts.appPath, opts.testsRoot !== opts.appPath ? opts.testsRoot : undefined, opts.serverRoot, opts.gitRepoRoot);
|
|
160
|
+
timings.scan = scanTimer.end();
|
|
161
|
+
logger.info(`Found ${scanResult.stats.totalSourceFiles} source files, ${scanResult.stats.totalTestFiles} test files`);
|
|
162
|
+
logger.info(`Discovered ${scanResult.families.length} candidate families`);
|
|
128
163
|
if (scanResult.families.length === 0) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
console.log(' (tests/, e2e/, specs/) with matching names.');
|
|
164
|
+
logger.info('No families discovered. Make sure your project has recognizable');
|
|
165
|
+
logger.info('source directories (src/, server/, app/) and test directories');
|
|
166
|
+
logger.info('(tests/, e2e/, specs/) with matching names.');
|
|
133
167
|
return;
|
|
134
168
|
}
|
|
135
169
|
// ---------- Phase 2: Merge with existing ----------
|
|
170
|
+
const mergeTimer = logger.timer('merge');
|
|
136
171
|
const existing = loadRouteFamilyManifest(opts.testsRoot);
|
|
137
172
|
if (existing) {
|
|
138
|
-
|
|
173
|
+
logger.info(`Found existing manifest with ${existing.families.length} families`);
|
|
139
174
|
}
|
|
140
175
|
let mergeResult = mergeFamilies(existing, scanResult.families);
|
|
141
|
-
|
|
176
|
+
timings.merge = mergeTimer.end();
|
|
177
|
+
logger.info(`Merge: ${mergeResult.summary}`);
|
|
142
178
|
// ---------- Phase 3: Stale detection ----------
|
|
143
179
|
if (mergeResult.manifest.families.length > 0) {
|
|
144
|
-
const stale = detectStaleFamilies(mergeResult.manifest, opts.appPath);
|
|
180
|
+
const stale = detectStaleFamilies(mergeResult.manifest, opts.appPath, opts.testsRoot);
|
|
145
181
|
if (stale.length > 0) {
|
|
146
|
-
|
|
147
|
-
console.log(` Stale families detected (${stale.length}):`);
|
|
182
|
+
logger.info(`Stale families detected (${stale.length}):`);
|
|
148
183
|
for (const id of stale) {
|
|
149
|
-
|
|
184
|
+
logger.info(` ${id} — paths no longer exist`);
|
|
150
185
|
}
|
|
151
186
|
if (!opts.yes && !opts.dryRun && process.stdin.isTTY) {
|
|
152
187
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -156,7 +191,7 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
156
191
|
const staleSet = new Set(stale);
|
|
157
192
|
mergeResult.manifest.families = mergeResult.manifest.families.filter((f) => !staleSet.has(f.id));
|
|
158
193
|
mergeResult.staleFamilies = stale;
|
|
159
|
-
|
|
194
|
+
logger.info(`Removed ${stale.length} stale families`);
|
|
160
195
|
}
|
|
161
196
|
}
|
|
162
197
|
finally {
|
|
@@ -166,29 +201,41 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
166
201
|
}
|
|
167
202
|
}
|
|
168
203
|
// ---------- Phase 4: LLM Enrichment ----------
|
|
204
|
+
let enrichTokens = 0;
|
|
205
|
+
let enrichCost = 0;
|
|
206
|
+
let enrichRequests = 0;
|
|
207
|
+
let enrichAvgResponseMs = 0;
|
|
169
208
|
if (opts.enrich) {
|
|
170
|
-
|
|
171
|
-
|
|
209
|
+
logger.info('Enriching with LLM...');
|
|
210
|
+
const enrichTimer = logger.timer('enrich');
|
|
172
211
|
try {
|
|
173
212
|
const provider = await LLMProviderFactory.createFromEnv();
|
|
174
|
-
const enrichResult = await enrichFamilies(mergeResult.manifest.families, scanResult.families, opts.appPath, provider, opts.budgetUSD);
|
|
213
|
+
const enrichResult = await enrichFamilies(mergeResult.manifest.families, scanResult.families, opts.appPath, provider, opts.budgetUSD, opts.testsRoot !== opts.appPath ? opts.testsRoot : undefined);
|
|
175
214
|
mergeResult.manifest.families = enrichResult.enrichedFamilies;
|
|
176
|
-
|
|
215
|
+
enrichTokens = enrichResult.tokensUsed;
|
|
216
|
+
enrichCost = enrichResult.costUSD;
|
|
217
|
+
enrichRequests = enrichResult.requestCount ?? 0;
|
|
218
|
+
enrichAvgResponseMs = enrichResult.avgResponseMs ?? 0;
|
|
219
|
+
logger.info(`Enriched ${enrichResult.enrichedFamilies.length} families`, {
|
|
220
|
+
tokens: enrichResult.tokensUsed,
|
|
221
|
+
cost: enrichResult.costUSD,
|
|
222
|
+
requests: enrichRequests,
|
|
223
|
+
avgResponseMs: enrichAvgResponseMs,
|
|
224
|
+
});
|
|
177
225
|
if (enrichResult.skippedFamilies.length > 0) {
|
|
178
|
-
|
|
226
|
+
logger.info(`Skipped ${enrichResult.skippedFamilies.length} families (budget limit)`);
|
|
179
227
|
}
|
|
180
228
|
}
|
|
181
229
|
catch (error) {
|
|
182
|
-
|
|
183
|
-
|
|
230
|
+
logger.warn(`LLM enrichment failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
231
|
+
logger.warn('Continuing with deterministic results. Use --no-enrich to skip LLM.');
|
|
184
232
|
}
|
|
233
|
+
timings.enrich = enrichTimer.end();
|
|
185
234
|
}
|
|
186
235
|
// ---------- Phase 5: Write manifest ----------
|
|
187
|
-
console.log('');
|
|
188
236
|
const json = serializeManifest(mergeResult.manifest);
|
|
189
237
|
if (opts.dryRun) {
|
|
190
|
-
|
|
191
|
-
console.log('');
|
|
238
|
+
logger.info('Dry run — proposed manifest:');
|
|
192
239
|
console.log(json);
|
|
193
240
|
}
|
|
194
241
|
else {
|
|
@@ -199,28 +246,28 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
199
246
|
const tmpPath = `${opts.outputPath}.tmp`;
|
|
200
247
|
writeFileSync(tmpPath, json, 'utf-8');
|
|
201
248
|
renameSync(tmpPath, opts.outputPath);
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
logger.info(`Wrote ${opts.outputPath}`);
|
|
250
|
+
logger.info(`${mergeResult.manifest.families.length} families`);
|
|
204
251
|
}
|
|
205
252
|
// ---------- Phase 6: Report unmatched ----------
|
|
206
253
|
if (scanResult.unmatchedSourceDirs.length > 0 || scanResult.unmatchedTestDirs.length > 0) {
|
|
207
|
-
|
|
208
|
-
console.log(' Unmatched (review manually):');
|
|
254
|
+
logger.info('Unmatched (review manually):');
|
|
209
255
|
for (const dir of scanResult.unmatchedSourceDirs.slice(0, 10)) {
|
|
210
|
-
|
|
256
|
+
logger.info(` source: ${dir.relativePath}`);
|
|
211
257
|
}
|
|
212
258
|
for (const dir of scanResult.unmatchedTestDirs.slice(0, 10)) {
|
|
213
|
-
|
|
259
|
+
logger.info(` test: ${dir.relativePath}`);
|
|
214
260
|
}
|
|
215
261
|
if (scanResult.unmatchedSourceDirs.length + scanResult.unmatchedTestDirs.length > 20) {
|
|
216
|
-
|
|
262
|
+
logger.info(' ... and more');
|
|
217
263
|
}
|
|
218
264
|
}
|
|
219
265
|
// ---------- Phase 7: Validation (optional) ----------
|
|
266
|
+
let validationReport;
|
|
220
267
|
if (opts.validate) {
|
|
268
|
+
const validateTimer = logger.timer('validate');
|
|
221
269
|
if (opts.pr) {
|
|
222
|
-
|
|
223
|
-
console.log(` Validating against PR #${opts.pr}...`);
|
|
270
|
+
logger.info(`Validating against PR #${opts.pr}...`);
|
|
224
271
|
// Check for gh CLI
|
|
225
272
|
const { execFileSync } = await import('child_process');
|
|
226
273
|
try {
|
|
@@ -243,29 +290,57 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
243
290
|
throw new TrainError(`Error fetching PR #${opts.pr}: ${error instanceof Error ? error.message : String(error)}`);
|
|
244
291
|
}
|
|
245
292
|
if (prFiles.length === 0) {
|
|
246
|
-
|
|
293
|
+
logger.info('No files found in PR.');
|
|
247
294
|
}
|
|
248
295
|
else {
|
|
249
296
|
const validation = validateCommit(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}`);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
console.log(formatValidationReport(report));
|
|
297
|
+
validationReport = buildValidationReport([validation], mergeResult.manifest);
|
|
298
|
+
logger.info(formatValidationReport(validationReport));
|
|
253
299
|
}
|
|
254
300
|
}
|
|
255
301
|
else {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const commits = getCommitFiles(opts.appPath, opts.since);
|
|
302
|
+
logger.info(`Validating against git history (${opts.since})...`);
|
|
303
|
+
const commits = getCommitFiles(opts.gitRepoRoot || opts.appPath, opts.since);
|
|
259
304
|
if (commits.length === 0) {
|
|
260
|
-
|
|
305
|
+
logger.info('No commits found in range.');
|
|
261
306
|
}
|
|
262
307
|
else {
|
|
263
308
|
const validations = commits.map((c) => validateCommit(mergeResult.manifest, c.files, c.hash, c.message));
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
console.log(formatValidationReport(report));
|
|
309
|
+
validationReport = buildValidationReport(validations, mergeResult.manifest);
|
|
310
|
+
logger.info(formatValidationReport(validationReport));
|
|
267
311
|
}
|
|
268
312
|
}
|
|
313
|
+
timings.validate = validateTimer.end();
|
|
314
|
+
}
|
|
315
|
+
timings.total = totalTimer.end();
|
|
316
|
+
// ---------- Write train report ----------
|
|
317
|
+
if (!opts.dryRun) {
|
|
318
|
+
const reportDir = dirname(opts.outputPath);
|
|
319
|
+
const trainReport = {
|
|
320
|
+
timestamp: new Date().toISOString(),
|
|
321
|
+
version: '1.7.0',
|
|
322
|
+
timings,
|
|
323
|
+
families: {
|
|
324
|
+
total: mergeResult.manifest.families.length,
|
|
325
|
+
new: mergeResult.newFamilies.length,
|
|
326
|
+
updated: mergeResult.updatedFamilies.length,
|
|
327
|
+
stale: mergeResult.staleFamilies.length,
|
|
328
|
+
},
|
|
329
|
+
coverage: validationReport ? {
|
|
330
|
+
percent: validationReport.coveragePercent,
|
|
331
|
+
boundFiles: validationReport.boundFiles,
|
|
332
|
+
totalFiles: validationReport.totalFiles,
|
|
333
|
+
} : undefined,
|
|
334
|
+
llm: opts.enrich ? {
|
|
335
|
+
tokensUsed: enrichTokens,
|
|
336
|
+
costUSD: enrichCost,
|
|
337
|
+
requests: enrichRequests,
|
|
338
|
+
avgResponseMs: enrichAvgResponseMs,
|
|
339
|
+
} : undefined,
|
|
340
|
+
};
|
|
341
|
+
const reportPath = join(reportDir, 'train-report.json');
|
|
342
|
+
writeFileSync(reportPath, JSON.stringify(trainReport, null, 2) + '\n', 'utf-8');
|
|
343
|
+
logger.debug('Wrote train report', { path: reportPath });
|
|
269
344
|
}
|
|
270
|
-
|
|
345
|
+
logger.info('Done.');
|
|
271
346
|
}
|
|
@@ -65,6 +65,8 @@ const FLAGS = {
|
|
|
65
65
|
'--no-enrich': { key: 'trainEnrich', type: 'boolean-false' },
|
|
66
66
|
'--validate': { key: 'trainValidate', type: 'boolean' },
|
|
67
67
|
'--yes': { key: 'trainYes', type: 'boolean', aliases: ['-y'] },
|
|
68
|
+
'--verbose': { key: 'verbose', type: 'boolean', aliases: ['-v'] },
|
|
69
|
+
'--json': { key: 'jsonOutput', type: 'boolean' },
|
|
68
70
|
'--mattermost': { key: 'profile', type: 'boolean', transform: () => 'mattermost' },
|
|
69
71
|
// -- string flags --
|
|
70
72
|
'--config': { key: 'configPath', type: 'string' },
|
|
@@ -95,6 +97,7 @@ const FLAGS = {
|
|
|
95
97
|
'--heal-report': { key: 'analyzeHealReport', type: 'string' },
|
|
96
98
|
'--flow-catalog': { key: 'flowCatalogPath', type: 'string' },
|
|
97
99
|
'--output': { key: 'trainOutput', type: 'string' },
|
|
100
|
+
'--server-path': { key: 'serverPath', type: 'string' },
|
|
98
101
|
// -- number flags (with isFinite guard) --
|
|
99
102
|
'--pipeline-scenarios': { key: 'pipelineScenarios', type: 'number' },
|
|
100
103
|
'--time': { key: 'timeLimitMinutes', type: 'number' },
|