@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
|
@@ -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,CAsP1F"}
|
|
@@ -43,6 +43,7 @@ const readline = __importStar(require("readline"));
|
|
|
43
43
|
const config_js_1 = require("../../agent/config.js");
|
|
44
44
|
const route_families_js_1 = require("../../knowledge/route_families.js");
|
|
45
45
|
const provider_factory_js_1 = require("../../provider_factory.js");
|
|
46
|
+
const logger_js_1 = require("../../logger.js");
|
|
46
47
|
const scanner_js_1 = require("../../training/scanner.js");
|
|
47
48
|
const merger_js_1 = require("../../training/merger.js");
|
|
48
49
|
const enricher_js_1 = require("../../training/enricher.js");
|
|
@@ -176,40 +177,47 @@ function serializeManifest(manifest) {
|
|
|
176
177
|
}
|
|
177
178
|
async function runTrainCommand(args, autoConfig) {
|
|
178
179
|
const opts = resolveTrainOptions(args, autoConfig);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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('===================');
|
|
183
189
|
// ---------- Phase 1: Deterministic scan ----------
|
|
184
|
-
|
|
190
|
+
logger_js_1.logger.info('Scanning project structure...');
|
|
185
191
|
if (opts.serverRoot) {
|
|
186
|
-
|
|
192
|
+
logger_js_1.logger.info(`Server root: ${opts.serverRoot}`);
|
|
187
193
|
}
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
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`);
|
|
191
199
|
if (scanResult.families.length === 0) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
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.');
|
|
196
203
|
return;
|
|
197
204
|
}
|
|
198
205
|
// ---------- Phase 2: Merge with existing ----------
|
|
206
|
+
const mergeTimer = logger_js_1.logger.timer('merge');
|
|
199
207
|
const existing = (0, route_families_js_1.loadRouteFamilyManifest)(opts.testsRoot);
|
|
200
208
|
if (existing) {
|
|
201
|
-
|
|
209
|
+
logger_js_1.logger.info(`Found existing manifest with ${existing.families.length} families`);
|
|
202
210
|
}
|
|
203
211
|
let mergeResult = (0, merger_js_1.mergeFamilies)(existing, scanResult.families);
|
|
204
|
-
|
|
212
|
+
timings.merge = mergeTimer.end();
|
|
213
|
+
logger_js_1.logger.info(`Merge: ${mergeResult.summary}`);
|
|
205
214
|
// ---------- Phase 3: Stale detection ----------
|
|
206
215
|
if (mergeResult.manifest.families.length > 0) {
|
|
207
216
|
const stale = (0, merger_js_1.detectStaleFamilies)(mergeResult.manifest, opts.appPath, opts.testsRoot);
|
|
208
217
|
if (stale.length > 0) {
|
|
209
|
-
|
|
210
|
-
console.log(` Stale families detected (${stale.length}):`);
|
|
218
|
+
logger_js_1.logger.info(`Stale families detected (${stale.length}):`);
|
|
211
219
|
for (const id of stale) {
|
|
212
|
-
|
|
220
|
+
logger_js_1.logger.info(` ${id} — paths no longer exist`);
|
|
213
221
|
}
|
|
214
222
|
if (!opts.yes && !opts.dryRun && process.stdin.isTTY) {
|
|
215
223
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -219,7 +227,7 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
219
227
|
const staleSet = new Set(stale);
|
|
220
228
|
mergeResult.manifest.families = mergeResult.manifest.families.filter((f) => !staleSet.has(f.id));
|
|
221
229
|
mergeResult.staleFamilies = stale;
|
|
222
|
-
|
|
230
|
+
logger_js_1.logger.info(`Removed ${stale.length} stale families`);
|
|
223
231
|
}
|
|
224
232
|
}
|
|
225
233
|
finally {
|
|
@@ -229,29 +237,41 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
229
237
|
}
|
|
230
238
|
}
|
|
231
239
|
// ---------- Phase 4: LLM Enrichment ----------
|
|
240
|
+
let enrichTokens = 0;
|
|
241
|
+
let enrichCost = 0;
|
|
242
|
+
let enrichRequests = 0;
|
|
243
|
+
let enrichAvgResponseMs = 0;
|
|
232
244
|
if (opts.enrich) {
|
|
233
|
-
|
|
234
|
-
|
|
245
|
+
logger_js_1.logger.info('Enriching with LLM...');
|
|
246
|
+
const enrichTimer = logger_js_1.logger.timer('enrich');
|
|
235
247
|
try {
|
|
236
248
|
const provider = await provider_factory_js_1.LLMProviderFactory.createFromEnv();
|
|
237
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);
|
|
238
250
|
mergeResult.manifest.families = enrichResult.enrichedFamilies;
|
|
239
|
-
|
|
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
|
+
});
|
|
240
261
|
if (enrichResult.skippedFamilies.length > 0) {
|
|
241
|
-
|
|
262
|
+
logger_js_1.logger.info(`Skipped ${enrichResult.skippedFamilies.length} families (budget limit)`);
|
|
242
263
|
}
|
|
243
264
|
}
|
|
244
265
|
catch (error) {
|
|
245
|
-
|
|
246
|
-
|
|
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.');
|
|
247
268
|
}
|
|
269
|
+
timings.enrich = enrichTimer.end();
|
|
248
270
|
}
|
|
249
271
|
// ---------- Phase 5: Write manifest ----------
|
|
250
|
-
console.log('');
|
|
251
272
|
const json = serializeManifest(mergeResult.manifest);
|
|
252
273
|
if (opts.dryRun) {
|
|
253
|
-
|
|
254
|
-
console.log('');
|
|
274
|
+
logger_js_1.logger.info('Dry run — proposed manifest:');
|
|
255
275
|
console.log(json);
|
|
256
276
|
}
|
|
257
277
|
else {
|
|
@@ -262,28 +282,42 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
262
282
|
const tmpPath = `${opts.outputPath}.tmp`;
|
|
263
283
|
(0, fs_1.writeFileSync)(tmpPath, json, 'utf-8');
|
|
264
284
|
(0, fs_1.renameSync)(tmpPath, opts.outputPath);
|
|
265
|
-
|
|
266
|
-
|
|
285
|
+
logger_js_1.logger.info(`Wrote ${opts.outputPath}`);
|
|
286
|
+
logger_js_1.logger.info(`${mergeResult.manifest.families.length} families`);
|
|
267
287
|
}
|
|
268
288
|
// ---------- Phase 6: Report unmatched ----------
|
|
269
289
|
if (scanResult.unmatchedSourceDirs.length > 0 || scanResult.unmatchedTestDirs.length > 0) {
|
|
270
|
-
|
|
271
|
-
console.log(' Unmatched (review manually):');
|
|
290
|
+
logger_js_1.logger.info('Unmatched (review manually):');
|
|
272
291
|
for (const dir of scanResult.unmatchedSourceDirs.slice(0, 10)) {
|
|
273
|
-
|
|
292
|
+
logger_js_1.logger.info(` source: ${dir.relativePath}`);
|
|
274
293
|
}
|
|
275
294
|
for (const dir of scanResult.unmatchedTestDirs.slice(0, 10)) {
|
|
276
|
-
|
|
295
|
+
logger_js_1.logger.info(` test: ${dir.relativePath}`);
|
|
277
296
|
}
|
|
278
297
|
if (scanResult.unmatchedSourceDirs.length + scanResult.unmatchedTestDirs.length > 20) {
|
|
279
|
-
|
|
298
|
+
logger_js_1.logger.info(' ... and more');
|
|
280
299
|
}
|
|
281
300
|
}
|
|
282
301
|
// ---------- Phase 7: Validation (optional) ----------
|
|
302
|
+
let validationReport;
|
|
283
303
|
if (opts.validate) {
|
|
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 });
|
|
284
319
|
if (opts.pr) {
|
|
285
|
-
|
|
286
|
-
console.log(` Validating against PR #${opts.pr}...`);
|
|
320
|
+
logger_js_1.logger.info(`Validating against PR #${opts.pr}...`);
|
|
287
321
|
// Check for gh CLI
|
|
288
322
|
const { execFileSync } = await import('child_process');
|
|
289
323
|
try {
|
|
@@ -306,29 +340,57 @@ async function runTrainCommand(args, autoConfig) {
|
|
|
306
340
|
throw new TrainError(`Error fetching PR #${opts.pr}: ${error instanceof Error ? error.message : String(error)}`);
|
|
307
341
|
}
|
|
308
342
|
if (prFiles.length === 0) {
|
|
309
|
-
|
|
343
|
+
logger_js_1.logger.info('No files found in PR.');
|
|
310
344
|
}
|
|
311
345
|
else {
|
|
312
|
-
const validation = (0, validator_js_1.validateCommit)(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
console.log((0, validator_js_1.formatValidationReport)(report));
|
|
346
|
+
const validation = (0, validator_js_1.validateCommit)(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}`, pathPrefixes);
|
|
347
|
+
validationReport = (0, validator_js_1.buildValidationReport)([validation], mergeResult.manifest);
|
|
348
|
+
logger_js_1.logger.info((0, validator_js_1.formatValidationReport)(validationReport));
|
|
316
349
|
}
|
|
317
350
|
}
|
|
318
351
|
else {
|
|
319
|
-
|
|
320
|
-
console.log(` Validating against git history (${opts.since})...`);
|
|
352
|
+
logger_js_1.logger.info(`Validating against git history (${opts.since})...`);
|
|
321
353
|
const commits = (0, validator_js_1.getCommitFiles)(opts.gitRepoRoot || opts.appPath, opts.since);
|
|
322
354
|
if (commits.length === 0) {
|
|
323
|
-
|
|
355
|
+
logger_js_1.logger.info('No commits found in range.');
|
|
324
356
|
}
|
|
325
357
|
else {
|
|
326
|
-
const validations = commits.map((c) => (0, validator_js_1.validateCommit)(mergeResult.manifest, c.files, c.hash, c.message));
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
console.log((0, validator_js_1.formatValidationReport)(report));
|
|
358
|
+
const validations = commits.map((c) => (0, validator_js_1.validateCommit)(mergeResult.manifest, c.files, c.hash, c.message, pathPrefixes));
|
|
359
|
+
validationReport = (0, validator_js_1.buildValidationReport)(validations, mergeResult.manifest);
|
|
360
|
+
logger_js_1.logger.info((0, validator_js_1.formatValidationReport)(validationReport));
|
|
330
361
|
}
|
|
331
362
|
}
|
|
363
|
+
timings.validate = validateTimer.end();
|
|
364
|
+
}
|
|
365
|
+
timings.total = totalTimer.end();
|
|
366
|
+
// ---------- Write train report ----------
|
|
367
|
+
if (!opts.dryRun) {
|
|
368
|
+
const reportDir = (0, path_1.dirname)(opts.outputPath);
|
|
369
|
+
const trainReport = {
|
|
370
|
+
timestamp: new Date().toISOString(),
|
|
371
|
+
version: '1.7.0',
|
|
372
|
+
timings,
|
|
373
|
+
families: {
|
|
374
|
+
total: mergeResult.manifest.families.length,
|
|
375
|
+
new: mergeResult.newFamilies.length,
|
|
376
|
+
updated: mergeResult.updatedFamilies.length,
|
|
377
|
+
stale: mergeResult.staleFamilies.length,
|
|
378
|
+
},
|
|
379
|
+
coverage: validationReport ? {
|
|
380
|
+
percent: validationReport.coveragePercent,
|
|
381
|
+
boundFiles: validationReport.boundFiles,
|
|
382
|
+
totalFiles: validationReport.totalFiles,
|
|
383
|
+
} : undefined,
|
|
384
|
+
llm: opts.enrich ? {
|
|
385
|
+
tokensUsed: enrichTokens,
|
|
386
|
+
costUSD: enrichCost,
|
|
387
|
+
requests: enrichRequests,
|
|
388
|
+
avgResponseMs: enrichAvgResponseMs,
|
|
389
|
+
} : undefined,
|
|
390
|
+
};
|
|
391
|
+
const reportPath = (0, path_1.join)(reportDir, 'train-report.json');
|
|
392
|
+
(0, fs_1.writeFileSync)(reportPath, JSON.stringify(trainReport, null, 2) + '\n', 'utf-8');
|
|
393
|
+
logger_js_1.logger.debug('Wrote train report', { path: reportPath });
|
|
332
394
|
}
|
|
333
|
-
|
|
395
|
+
logger_js_1.logger.info('Done.');
|
|
334
396
|
}
|
|
@@ -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' },
|
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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"}
|
|
@@ -7,6 +7,7 @@ import * as readline from 'readline';
|
|
|
7
7
|
import { resolveConfig } from '../../agent/config.js';
|
|
8
8
|
import { loadRouteFamilyManifest } from '../../knowledge/route_families.js';
|
|
9
9
|
import { LLMProviderFactory } from '../../provider_factory.js';
|
|
10
|
+
import { logger, LogLevel } from '../../logger.js';
|
|
10
11
|
import { scanProject } from '../../training/scanner.js';
|
|
11
12
|
import { mergeFamilies, detectStaleFamilies } from '../../training/merger.js';
|
|
12
13
|
import { enrichFamilies } from '../../training/enricher.js';
|
|
@@ -140,40 +141,47 @@ function serializeManifest(manifest) {
|
|
|
140
141
|
}
|
|
141
142
|
export async function runTrainCommand(args, autoConfig) {
|
|
142
143
|
const opts = resolveTrainOptions(args, autoConfig);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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('===================');
|
|
147
153
|
// ---------- Phase 1: Deterministic scan ----------
|
|
148
|
-
|
|
154
|
+
logger.info('Scanning project structure...');
|
|
149
155
|
if (opts.serverRoot) {
|
|
150
|
-
|
|
156
|
+
logger.info(`Server root: ${opts.serverRoot}`);
|
|
151
157
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
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`);
|
|
155
163
|
if (scanResult.families.length === 0) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
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.');
|
|
160
167
|
return;
|
|
161
168
|
}
|
|
162
169
|
// ---------- Phase 2: Merge with existing ----------
|
|
170
|
+
const mergeTimer = logger.timer('merge');
|
|
163
171
|
const existing = loadRouteFamilyManifest(opts.testsRoot);
|
|
164
172
|
if (existing) {
|
|
165
|
-
|
|
173
|
+
logger.info(`Found existing manifest with ${existing.families.length} families`);
|
|
166
174
|
}
|
|
167
175
|
let mergeResult = mergeFamilies(existing, scanResult.families);
|
|
168
|
-
|
|
176
|
+
timings.merge = mergeTimer.end();
|
|
177
|
+
logger.info(`Merge: ${mergeResult.summary}`);
|
|
169
178
|
// ---------- Phase 3: Stale detection ----------
|
|
170
179
|
if (mergeResult.manifest.families.length > 0) {
|
|
171
180
|
const stale = detectStaleFamilies(mergeResult.manifest, opts.appPath, opts.testsRoot);
|
|
172
181
|
if (stale.length > 0) {
|
|
173
|
-
|
|
174
|
-
console.log(` Stale families detected (${stale.length}):`);
|
|
182
|
+
logger.info(`Stale families detected (${stale.length}):`);
|
|
175
183
|
for (const id of stale) {
|
|
176
|
-
|
|
184
|
+
logger.info(` ${id} — paths no longer exist`);
|
|
177
185
|
}
|
|
178
186
|
if (!opts.yes && !opts.dryRun && process.stdin.isTTY) {
|
|
179
187
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -183,7 +191,7 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
183
191
|
const staleSet = new Set(stale);
|
|
184
192
|
mergeResult.manifest.families = mergeResult.manifest.families.filter((f) => !staleSet.has(f.id));
|
|
185
193
|
mergeResult.staleFamilies = stale;
|
|
186
|
-
|
|
194
|
+
logger.info(`Removed ${stale.length} stale families`);
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
finally {
|
|
@@ -193,29 +201,41 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
// ---------- Phase 4: LLM Enrichment ----------
|
|
204
|
+
let enrichTokens = 0;
|
|
205
|
+
let enrichCost = 0;
|
|
206
|
+
let enrichRequests = 0;
|
|
207
|
+
let enrichAvgResponseMs = 0;
|
|
196
208
|
if (opts.enrich) {
|
|
197
|
-
|
|
198
|
-
|
|
209
|
+
logger.info('Enriching with LLM...');
|
|
210
|
+
const enrichTimer = logger.timer('enrich');
|
|
199
211
|
try {
|
|
200
212
|
const provider = await LLMProviderFactory.createFromEnv();
|
|
201
213
|
const enrichResult = await enrichFamilies(mergeResult.manifest.families, scanResult.families, opts.appPath, provider, opts.budgetUSD, opts.testsRoot !== opts.appPath ? opts.testsRoot : undefined);
|
|
202
214
|
mergeResult.manifest.families = enrichResult.enrichedFamilies;
|
|
203
|
-
|
|
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
|
+
});
|
|
204
225
|
if (enrichResult.skippedFamilies.length > 0) {
|
|
205
|
-
|
|
226
|
+
logger.info(`Skipped ${enrichResult.skippedFamilies.length} families (budget limit)`);
|
|
206
227
|
}
|
|
207
228
|
}
|
|
208
229
|
catch (error) {
|
|
209
|
-
|
|
210
|
-
|
|
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.');
|
|
211
232
|
}
|
|
233
|
+
timings.enrich = enrichTimer.end();
|
|
212
234
|
}
|
|
213
235
|
// ---------- Phase 5: Write manifest ----------
|
|
214
|
-
console.log('');
|
|
215
236
|
const json = serializeManifest(mergeResult.manifest);
|
|
216
237
|
if (opts.dryRun) {
|
|
217
|
-
|
|
218
|
-
console.log('');
|
|
238
|
+
logger.info('Dry run — proposed manifest:');
|
|
219
239
|
console.log(json);
|
|
220
240
|
}
|
|
221
241
|
else {
|
|
@@ -226,28 +246,42 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
226
246
|
const tmpPath = `${opts.outputPath}.tmp`;
|
|
227
247
|
writeFileSync(tmpPath, json, 'utf-8');
|
|
228
248
|
renameSync(tmpPath, opts.outputPath);
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
logger.info(`Wrote ${opts.outputPath}`);
|
|
250
|
+
logger.info(`${mergeResult.manifest.families.length} families`);
|
|
231
251
|
}
|
|
232
252
|
// ---------- Phase 6: Report unmatched ----------
|
|
233
253
|
if (scanResult.unmatchedSourceDirs.length > 0 || scanResult.unmatchedTestDirs.length > 0) {
|
|
234
|
-
|
|
235
|
-
console.log(' Unmatched (review manually):');
|
|
254
|
+
logger.info('Unmatched (review manually):');
|
|
236
255
|
for (const dir of scanResult.unmatchedSourceDirs.slice(0, 10)) {
|
|
237
|
-
|
|
256
|
+
logger.info(` source: ${dir.relativePath}`);
|
|
238
257
|
}
|
|
239
258
|
for (const dir of scanResult.unmatchedTestDirs.slice(0, 10)) {
|
|
240
|
-
|
|
259
|
+
logger.info(` test: ${dir.relativePath}`);
|
|
241
260
|
}
|
|
242
261
|
if (scanResult.unmatchedSourceDirs.length + scanResult.unmatchedTestDirs.length > 20) {
|
|
243
|
-
|
|
262
|
+
logger.info(' ... and more');
|
|
244
263
|
}
|
|
245
264
|
}
|
|
246
265
|
// ---------- Phase 7: Validation (optional) ----------
|
|
266
|
+
let validationReport;
|
|
247
267
|
if (opts.validate) {
|
|
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 });
|
|
248
283
|
if (opts.pr) {
|
|
249
|
-
|
|
250
|
-
console.log(` Validating against PR #${opts.pr}...`);
|
|
284
|
+
logger.info(`Validating against PR #${opts.pr}...`);
|
|
251
285
|
// Check for gh CLI
|
|
252
286
|
const { execFileSync } = await import('child_process');
|
|
253
287
|
try {
|
|
@@ -270,29 +304,57 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
270
304
|
throw new TrainError(`Error fetching PR #${opts.pr}: ${error instanceof Error ? error.message : String(error)}`);
|
|
271
305
|
}
|
|
272
306
|
if (prFiles.length === 0) {
|
|
273
|
-
|
|
307
|
+
logger.info('No files found in PR.');
|
|
274
308
|
}
|
|
275
309
|
else {
|
|
276
|
-
const validation = validateCommit(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
console.log(formatValidationReport(report));
|
|
310
|
+
const validation = validateCommit(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}`, pathPrefixes);
|
|
311
|
+
validationReport = buildValidationReport([validation], mergeResult.manifest);
|
|
312
|
+
logger.info(formatValidationReport(validationReport));
|
|
280
313
|
}
|
|
281
314
|
}
|
|
282
315
|
else {
|
|
283
|
-
|
|
284
|
-
console.log(` Validating against git history (${opts.since})...`);
|
|
316
|
+
logger.info(`Validating against git history (${opts.since})...`);
|
|
285
317
|
const commits = getCommitFiles(opts.gitRepoRoot || opts.appPath, opts.since);
|
|
286
318
|
if (commits.length === 0) {
|
|
287
|
-
|
|
319
|
+
logger.info('No commits found in range.');
|
|
288
320
|
}
|
|
289
321
|
else {
|
|
290
|
-
const validations = commits.map((c) => validateCommit(mergeResult.manifest, c.files, c.hash, c.message));
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
console.log(formatValidationReport(report));
|
|
322
|
+
const validations = commits.map((c) => validateCommit(mergeResult.manifest, c.files, c.hash, c.message, pathPrefixes));
|
|
323
|
+
validationReport = buildValidationReport(validations, mergeResult.manifest);
|
|
324
|
+
logger.info(formatValidationReport(validationReport));
|
|
294
325
|
}
|
|
295
326
|
}
|
|
327
|
+
timings.validate = validateTimer.end();
|
|
328
|
+
}
|
|
329
|
+
timings.total = totalTimer.end();
|
|
330
|
+
// ---------- Write train report ----------
|
|
331
|
+
if (!opts.dryRun) {
|
|
332
|
+
const reportDir = dirname(opts.outputPath);
|
|
333
|
+
const trainReport = {
|
|
334
|
+
timestamp: new Date().toISOString(),
|
|
335
|
+
version: '1.7.0',
|
|
336
|
+
timings,
|
|
337
|
+
families: {
|
|
338
|
+
total: mergeResult.manifest.families.length,
|
|
339
|
+
new: mergeResult.newFamilies.length,
|
|
340
|
+
updated: mergeResult.updatedFamilies.length,
|
|
341
|
+
stale: mergeResult.staleFamilies.length,
|
|
342
|
+
},
|
|
343
|
+
coverage: validationReport ? {
|
|
344
|
+
percent: validationReport.coveragePercent,
|
|
345
|
+
boundFiles: validationReport.boundFiles,
|
|
346
|
+
totalFiles: validationReport.totalFiles,
|
|
347
|
+
} : undefined,
|
|
348
|
+
llm: opts.enrich ? {
|
|
349
|
+
tokensUsed: enrichTokens,
|
|
350
|
+
costUSD: enrichCost,
|
|
351
|
+
requests: enrichRequests,
|
|
352
|
+
avgResponseMs: enrichAvgResponseMs,
|
|
353
|
+
} : undefined,
|
|
354
|
+
};
|
|
355
|
+
const reportPath = join(reportDir, 'train-report.json');
|
|
356
|
+
writeFileSync(reportPath, JSON.stringify(trainReport, null, 2) + '\n', 'utf-8');
|
|
357
|
+
logger.debug('Wrote train report', { path: reportPath });
|
|
296
358
|
}
|
|
297
|
-
|
|
359
|
+
logger.info('Done.');
|
|
298
360
|
}
|
|
@@ -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' },
|
|
@@ -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 });
|
package/dist/esm/logger.js
CHANGED
|
@@ -48,6 +48,7 @@ function logLevelToString(level) {
|
|
|
48
48
|
export class Logger {
|
|
49
49
|
constructor(minLevel) {
|
|
50
50
|
this.level = minLevel ?? getLogLevelFromEnv();
|
|
51
|
+
this.jsonMode = process.env.LOG_FORMAT?.toLowerCase() === 'json';
|
|
51
52
|
}
|
|
52
53
|
error(message, context) {
|
|
53
54
|
if (this.level >= LogLevel.ERROR) {
|
|
@@ -72,11 +73,37 @@ export class Logger {
|
|
|
72
73
|
setLevel(level) {
|
|
73
74
|
this.level = level;
|
|
74
75
|
}
|
|
76
|
+
setJsonMode(enabled) {
|
|
77
|
+
this.jsonMode = enabled;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Start a timer for measuring duration of an operation.
|
|
81
|
+
* Returns an object with `end()` that logs at DEBUG level and returns elapsed ms.
|
|
82
|
+
*/
|
|
83
|
+
timer(label) {
|
|
84
|
+
const start = performance.now();
|
|
85
|
+
return {
|
|
86
|
+
end: () => {
|
|
87
|
+
const elapsed = Math.round(performance.now() - start);
|
|
88
|
+
this.debug(`${label} completed`, { durationMs: elapsed });
|
|
89
|
+
return elapsed;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
75
93
|
log(level, message, context) {
|
|
76
94
|
const timestamp = new Date().toISOString();
|
|
77
95
|
const levelStr = logLevelToString(level);
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
let output;
|
|
97
|
+
if (this.jsonMode) {
|
|
98
|
+
const entry = { ts: timestamp, level: levelStr, msg: message };
|
|
99
|
+
if (context)
|
|
100
|
+
entry.ctx = context;
|
|
101
|
+
output = JSON.stringify(entry);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const contextStr = context ? ` ${JSON.stringify(context)}` : '';
|
|
105
|
+
output = `[${timestamp}] [${levelStr}] ${message}${contextStr}`;
|
|
106
|
+
}
|
|
80
107
|
if (level <= LogLevel.WARN) {
|
|
81
108
|
console.error(output);
|
|
82
109
|
}
|