@yasserkhanorg/e2e-agents 1.3.2 → 1.5.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/agent/feedback.d.ts +16 -0
- package/dist/agent/feedback.d.ts.map +1 -1
- package/dist/agent/feedback.js +62 -0
- package/dist/agent/process_runner.d.ts +1 -1
- package/dist/agent/process_runner.d.ts.map +1 -1
- package/dist/agent/process_runner.js +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -2
- 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/engine/plan_builder.d.ts +2 -1
- package/dist/engine/plan_builder.d.ts.map +1 -1
- package/dist/engine/plan_builder.js +22 -9
- package/dist/esm/agent/feedback.js +61 -0
- package/dist/esm/agent/process_runner.js +3 -3
- package/dist/esm/api.js +5 -2
- 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/engine/plan_builder.js +22 -9
- package/dist/esm/index.js +6 -1
- package/dist/esm/knowledge/route_families.js +2 -2
- package/dist/esm/pipeline/spec_verifier.js +75 -0
- package/dist/esm/pipeline/stage3_generation.js +122 -4
- package/dist/esm/pipeline/stage4_heal.js +146 -3
- package/dist/esm/prompts/heal.js +4 -0
- package/dist/esm/qa-agent/phase2/agent_loop.js +60 -24
- package/dist/esm/qa-agent/phase2/exploration_state.js +21 -0
- package/dist/esm/qa-agent/phase2/tools.js +99 -1
- package/dist/esm/qa-agent/phase3/reporter.js +31 -4
- 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/esm/validation/guardrails.js +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -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/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/spec_verifier.d.ts +20 -0
- package/dist/pipeline/spec_verifier.d.ts.map +1 -0
- package/dist/pipeline/spec_verifier.js +79 -0
- package/dist/pipeline/stage3_generation.d.ts +10 -0
- package/dist/pipeline/stage3_generation.d.ts.map +1 -1
- package/dist/pipeline/stage3_generation.js +120 -2
- package/dist/pipeline/stage4_heal.d.ts +4 -0
- package/dist/pipeline/stage4_heal.d.ts.map +1 -1
- package/dist/pipeline/stage4_heal.js +145 -2
- package/dist/prompts/heal.d.ts +2 -0
- package/dist/prompts/heal.d.ts.map +1 -1
- package/dist/prompts/heal.js +4 -0
- package/dist/qa-agent/phase2/agent_loop.d.ts.map +1 -1
- package/dist/qa-agent/phase2/agent_loop.js +60 -24
- package/dist/qa-agent/phase2/exploration_state.d.ts.map +1 -1
- package/dist/qa-agent/phase2/exploration_state.js +21 -0
- package/dist/qa-agent/phase2/tools.d.ts.map +1 -1
- package/dist/qa-agent/phase2/tools.js +99 -1
- package/dist/qa-agent/phase3/reporter.js +31 -4
- package/dist/qa-agent/types.d.ts +9 -1
- package/dist/qa-agent/types.d.ts.map +1 -1
- 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/dist/validation/guardrails.d.ts +2 -0
- package/dist/validation/guardrails.d.ts.map +1 -1
- package/dist/validation/guardrails.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,29 +24,29 @@ Requires Node.js >= 20. Ships both CommonJS and ESM builds.
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
# All-in-one: impact + plan + optional generate/heal
|
|
27
|
-
npx e2e-ai-agents analyze --path /path/to/
|
|
27
|
+
npx e2e-ai-agents analyze --path /path/to/project [--generate] [--heal]
|
|
28
28
|
|
|
29
29
|
# Analyze which flows are impacted by code changes
|
|
30
|
-
npx e2e-ai-agents impact --path /path/to/
|
|
30
|
+
npx e2e-ai-agents impact --path /path/to/project
|
|
31
31
|
|
|
32
32
|
# Generate a coverage plan with gap analysis
|
|
33
|
-
npx e2e-ai-agents plan --path /path/to/
|
|
33
|
+
npx e2e-ai-agents plan --path /path/to/project
|
|
34
34
|
|
|
35
35
|
# Generate tests for uncovered gaps (requires plan output)
|
|
36
|
-
npx e2e-ai-agents generate --path /path/to/
|
|
36
|
+
npx e2e-ai-agents generate --path /path/to/project
|
|
37
37
|
|
|
38
38
|
# Heal flaky/failing specs from a Playwright report
|
|
39
|
-
npx e2e-ai-agents heal --path /path/to/
|
|
39
|
+
npx e2e-ai-agents heal --path /path/to/project --traceability-report ./playwright-report.json
|
|
40
40
|
|
|
41
41
|
# Stage generated tests, commit, and open a PR
|
|
42
|
-
npx e2e-ai-agents finalize-generated-tests --path /path/to/
|
|
42
|
+
npx e2e-ai-agents finalize-generated-tests --path /path/to/project --create-pr
|
|
43
43
|
|
|
44
44
|
# Ingest test execution data for traceability
|
|
45
|
-
npx e2e-ai-agents traceability-capture --path /path/to/
|
|
46
|
-
npx e2e-ai-agents traceability-ingest --path /path/to/
|
|
45
|
+
npx e2e-ai-agents traceability-capture --path /path/to/project --traceability-report ./playwright-report.json
|
|
46
|
+
npx e2e-ai-agents traceability-ingest --path /path/to/project --traceability-input ./traceability-input.json
|
|
47
47
|
|
|
48
48
|
# Ingest recommendation feedback for calibration
|
|
49
|
-
npx e2e-ai-agents feedback --path /path/to/
|
|
49
|
+
npx e2e-ai-agents feedback --path /path/to/project --feedback-input ./feedback.json
|
|
50
50
|
|
|
51
51
|
# Test LLM provider connectivity
|
|
52
52
|
npx e2e-ai-agents llm-health
|
|
@@ -54,6 +54,37 @@ npx e2e-ai-agents llm-health
|
|
|
54
54
|
|
|
55
55
|
`plan` and `suggest` are aliases. `analyze` is a convenience wrapper that runs impact + plan and optionally generation/healing in one invocation. Use `--help` for all available flags.
|
|
56
56
|
|
|
57
|
+
## Route-Families Training
|
|
58
|
+
|
|
59
|
+
Route-families map your source files to features, test directories, and user flows. They are the context that powers accurate impact analysis. The `train` command bootstraps and maintains this manifest.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Scan your codebase + LLM enrichment (default)
|
|
63
|
+
npx e2e-ai-agents train --path /path/to/project
|
|
64
|
+
|
|
65
|
+
# Offline mode (no LLM, no API key needed)
|
|
66
|
+
npx e2e-ai-agents train --path /path/to/project --no-enrich
|
|
67
|
+
|
|
68
|
+
# Validate accuracy against recent git history
|
|
69
|
+
npx e2e-ai-agents train --path /path/to/project --validate --since HEAD~50
|
|
70
|
+
|
|
71
|
+
# Full pipeline: scan + enrich + validate
|
|
72
|
+
npx e2e-ai-agents train --path /path/to/project --validate --since HEAD~20
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Why LLM enrichment is on by default:** The manifest exists to give AI context for impact analysis, scenario suggestion, and bug detection. AI-generated context produces better AI reasoning downstream. Use `--no-enrich` for offline/free operation or to avoid sending code snippets to third-party LLM APIs.
|
|
76
|
+
|
|
77
|
+
**Training loop:** Run `train` → review the generated `route-families.json` → run `train --validate` to check coverage % → fix gaps → repeat until 95%+.
|
|
78
|
+
|
|
79
|
+
The `train` command:
|
|
80
|
+
1. **Scans** your project structure (frontend `src/`, backend `server/`, test dirs)
|
|
81
|
+
2. **Matches** source directories to test directories by name
|
|
82
|
+
3. **Enriches** with LLM (priority, user flows, routes, components)
|
|
83
|
+
4. **Merges** intelligently with any existing manifest (preserves human curation)
|
|
84
|
+
5. **Validates** against git history to measure accuracy
|
|
85
|
+
|
|
86
|
+
Output is written to `<testsRoot>/.e2e-ai-agents/route-families.json`.
|
|
87
|
+
|
|
57
88
|
## Configuration
|
|
58
89
|
|
|
59
90
|
Create `e2e-ai-agents.config.json` in your project (auto-discovered):
|
package/dist/agent/feedback.d.ts
CHANGED
|
@@ -71,5 +71,21 @@ export declare function appendFeedbackAndRecompute(appRoot: string, input: Recom
|
|
|
71
71
|
calibration: CalibrationSummary;
|
|
72
72
|
};
|
|
73
73
|
export declare function readCalibration(appRoot: string): CalibrationSummary | null;
|
|
74
|
+
export interface AdaptiveThresholds {
|
|
75
|
+
minConfidenceForTargeted: number;
|
|
76
|
+
safeMergeMinConfidence: number;
|
|
77
|
+
/** Subsystems that should always be included regardless of confidence */
|
|
78
|
+
alwaysIncludeSubsystems: string[];
|
|
79
|
+
/** Human-readable adjustment reasons for logging */
|
|
80
|
+
adjustmentReasons: string[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Compute adaptive thresholds based on calibration data.
|
|
84
|
+
* - If recent recall < 0.8: lower minConfidence (catch more escapes)
|
|
85
|
+
* - If recent precision > 0.9: raise minConfidence (fewer unnecessary tests)
|
|
86
|
+
* - Per-subsystem: if falseNegativeRate > 0.3 in 30d, always include tests
|
|
87
|
+
* Returns defaults if no calibration data exists.
|
|
88
|
+
*/
|
|
89
|
+
export declare function getAdaptiveThresholds(appRoot: string): AdaptiveThresholds;
|
|
74
90
|
export declare function readFlakyTests(appRoot: string): FlakySummary | null;
|
|
75
91
|
//# sourceMappingURL=feedback.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../../src/agent/feedback.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,2BAA2B;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,QAAQ,EAAE;QACN,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,OAAO,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,SAAS,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,OAAO,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,WAAW,EAAE,MAAM,CACnB,MAAM,EACN;QACI,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE;YACN,SAAS,EAAE,MAAM,CAAC;YAClB,MAAM,EAAE,MAAM,CAAC;YACf,iBAAiB,EAAE,MAAM,CAAC;YAC1B,OAAO,EAAE,MAAM,CAAC;SACnB,CAAC;QACF,SAAS,EAAE;YACP,SAAS,EAAE,MAAM,CAAC;YAClB,MAAM,EAAE,MAAM,CAAC;YACf,iBAAiB,EAAE,MAAM,CAAC;YAC1B,OAAO,EAAE,MAAM,CAAC;SACnB,CAAC;KACL,CACA,CAAC;CACL;AAOD,MAAM,WAAW,YAAY;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;QACT,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;QAChC,UAAU,EAAE,OAAO,CAAC;QACpB,eAAe,EAAE,MAAM,GAAG,QAAQ,GAAG,kBAAkB,CAAC;QACxD,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACN;AAyQD,wBAAgB,0BAA0B,CACtC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,2BAA2B,GACnC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,kBAAkB,CAAA;CAAC,CAwBlF;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAE1E;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAEnE"}
|
|
1
|
+
{"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../../src/agent/feedback.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/feedback.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.appendFeedbackAndRecompute = appendFeedbackAndRecompute;
|
|
6
6
|
exports.readCalibration = readCalibration;
|
|
7
|
+
exports.getAdaptiveThresholds = getAdaptiveThresholds;
|
|
7
8
|
exports.readFlakyTests = readFlakyTests;
|
|
8
9
|
const fs_1 = require("fs");
|
|
9
10
|
const path_1 = require("path");
|
|
@@ -256,6 +257,67 @@ function appendFeedbackAndRecompute(appRoot, input) {
|
|
|
256
257
|
function readCalibration(appRoot) {
|
|
257
258
|
return readJson((0, path_1.join)(appRoot, '.e2e-ai-agents', 'calibration.json'));
|
|
258
259
|
}
|
|
260
|
+
const DEFAULT_MIN_CONFIDENCE = 60;
|
|
261
|
+
const DEFAULT_SAFE_MERGE = 85;
|
|
262
|
+
const MIN_CONFIDENCE_FLOOR = 40;
|
|
263
|
+
const MIN_CONFIDENCE_CEILING = 80;
|
|
264
|
+
/**
|
|
265
|
+
* Compute adaptive thresholds based on calibration data.
|
|
266
|
+
* - If recent recall < 0.8: lower minConfidence (catch more escapes)
|
|
267
|
+
* - If recent precision > 0.9: raise minConfidence (fewer unnecessary tests)
|
|
268
|
+
* - Per-subsystem: if falseNegativeRate > 0.3 in 30d, always include tests
|
|
269
|
+
* Returns defaults if no calibration data exists.
|
|
270
|
+
*/
|
|
271
|
+
function getAdaptiveThresholds(appRoot) {
|
|
272
|
+
const calibration = readCalibration(appRoot);
|
|
273
|
+
const reasons = [];
|
|
274
|
+
const alwaysInclude = [];
|
|
275
|
+
if (!calibration || calibration.samples === 0) {
|
|
276
|
+
return {
|
|
277
|
+
minConfidenceForTargeted: DEFAULT_MIN_CONFIDENCE,
|
|
278
|
+
safeMergeMinConfidence: DEFAULT_SAFE_MERGE,
|
|
279
|
+
alwaysIncludeSubsystems: [],
|
|
280
|
+
adjustmentReasons: ['No calibration data — using defaults'],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
let minConfidence = DEFAULT_MIN_CONFIDENCE;
|
|
284
|
+
let safeMerge = DEFAULT_SAFE_MERGE;
|
|
285
|
+
// Adjust based on 7-day recall
|
|
286
|
+
if (calibration.recent7d.samples >= 3) {
|
|
287
|
+
if (calibration.recent7d.recall < 0.8) {
|
|
288
|
+
const adjustment = 10;
|
|
289
|
+
minConfidence -= adjustment;
|
|
290
|
+
safeMerge -= adjustment;
|
|
291
|
+
reasons.push(`Lowering confidence threshold by ${adjustment} (7d recall: ${calibration.recent7d.recall.toFixed(2)})`);
|
|
292
|
+
}
|
|
293
|
+
else if (calibration.recent7d.precision > 0.9) {
|
|
294
|
+
const adjustment = 5;
|
|
295
|
+
minConfidence += adjustment;
|
|
296
|
+
safeMerge += adjustment;
|
|
297
|
+
reasons.push(`Raising confidence threshold by ${adjustment} (7d precision: ${calibration.recent7d.precision.toFixed(2)})`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Clamp to safe ranges
|
|
301
|
+
minConfidence = Math.max(MIN_CONFIDENCE_FLOOR, Math.min(MIN_CONFIDENCE_CEILING, minConfidence));
|
|
302
|
+
safeMerge = Math.max(70, Math.min(95, safeMerge));
|
|
303
|
+
// Per-subsystem blind spot detection (30-day window)
|
|
304
|
+
for (const [subsystem, metrics] of Object.entries(calibration.bySubsystem)) {
|
|
305
|
+
const recent = metrics.recent30d;
|
|
306
|
+
if (recent.samples >= 3 && recent.falseNegativeRate > 0.3) {
|
|
307
|
+
alwaysInclude.push(subsystem);
|
|
308
|
+
reasons.push(`Always including ${subsystem} tests (30d false-negative rate: ${recent.falseNegativeRate.toFixed(2)})`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (reasons.length === 0) {
|
|
312
|
+
reasons.push('Calibration data within normal range — using defaults');
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
minConfidenceForTargeted: minConfidence,
|
|
316
|
+
safeMergeMinConfidence: safeMerge,
|
|
317
|
+
alwaysIncludeSubsystems: alwaysInclude,
|
|
318
|
+
adjustmentReasons: reasons,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
259
321
|
function readFlakyTests(appRoot) {
|
|
260
322
|
return readJson((0, path_1.join)(appRoot, '.e2e-ai-agents', 'flaky-tests.json'));
|
|
261
323
|
}
|
|
@@ -2,7 +2,7 @@ import type { PipelineConfig } from './config.js';
|
|
|
2
2
|
import type { CommandResult } from './pipeline_types.js';
|
|
3
3
|
export declare function resolvePlaywrightBinary(testsRoot: string): string | null;
|
|
4
4
|
export declare function summarizeCommandOutput(stdout: string, stderr: string): string;
|
|
5
|
-
export declare function runCommand(command: string, args: string[], cwd: string, timeoutMs?: number): CommandResult;
|
|
5
|
+
export declare function runCommand(command: string, args: string[], cwd: string, timeoutMs?: number, envOverride?: NodeJS.ProcessEnv): CommandResult;
|
|
6
6
|
export declare function resolveMcpCommandTimeoutMs(pipeline: PipelineConfig): number;
|
|
7
7
|
export declare function resolveMcpRetries(pipeline: PipelineConfig): number;
|
|
8
8
|
export declare function isRetryableMcpFailure(result: CommandResult): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process_runner.d.ts","sourceRoot":"","sources":["../../src/agent/process_runner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAEvD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUxE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAO7E;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,SAAiB,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"process_runner.d.ts","sourceRoot":"","sources":["../../src/agent/process_runner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAEvD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUxE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAO7E;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,SAAiB,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,aAAa,CAsBnJ;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAM3E;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAMlE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAQpE;AAED,wBAAgB,qBAAqB,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAChB,aAAa,CAYf"}
|
|
@@ -31,12 +31,12 @@ function summarizeCommandOutput(stdout, stderr) {
|
|
|
31
31
|
const lines = combined.split('\n').slice(-20);
|
|
32
32
|
return lines.join('\n').slice(0, 2000);
|
|
33
33
|
}
|
|
34
|
-
function runCommand(command, args, cwd, timeoutMs = 60 * 60 * 1000) {
|
|
34
|
+
function runCommand(command, args, cwd, timeoutMs = 60 * 60 * 1000, envOverride) {
|
|
35
35
|
// When spawning `claude`, unset CLAUDECODE so nested invocations are allowed.
|
|
36
36
|
// Claude Code sets this variable to block nested sessions; child processes
|
|
37
37
|
// that spawn their own claude instance must run without it.
|
|
38
|
-
let env;
|
|
39
|
-
if (command === 'claude') {
|
|
38
|
+
let env = envOverride;
|
|
39
|
+
if (!env && command === 'claude') {
|
|
40
40
|
const { CLAUDECODE: _, ...rest } = process.env;
|
|
41
41
|
env = rest;
|
|
42
42
|
}
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,eAAe,EAAC,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAEH,KAAK,UAAU,EAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAmC,KAAK,YAAY,EAAC,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,eAAe,EAAC,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAEH,KAAK,UAAU,EAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAmC,KAAK,YAAY,EAAC,MAAM,2BAA2B,CAAC;AAU9F,OAAO,EAAqB,KAAK,kBAAkB,EAAC,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAyB,KAAK,6BAA6B,EAAE,KAAK,4BAA4B,EAAC,MAAM,oBAAoB,CAAC;AACjI,OAAO,EAEH,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAChC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAGH,KAAK,yBAAyB,EACjC,MAAM,iCAAiC,CAAC;AAEzC,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,4BAA4B;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,yBAAyB,CAAC;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAcD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,6BAA6B,GAAG,4BAA4B,CAE1G;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,4BAA4B,GAAG,wBAAwB,CASlG;AAED,MAAM,WAAW,sBAAsB;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,eAAoB,GAAG,YAAY,CAQtF;AAED,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,eAAoB,GAAG,sBAAsB,CAejG;AAED,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,sBAAsB,GAAG;IAAE,YAAY,CAAC,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAiD7I;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,6BAA6B,GAAG,yBAAyB,CAkBrG"}
|
package/dist/api.js
CHANGED
|
@@ -13,6 +13,7 @@ const plan_js_1 = require("./agent/plan.js");
|
|
|
13
13
|
const impact_engine_js_1 = require("./engine/impact_engine.js");
|
|
14
14
|
const plan_builder_js_1 = require("./engine/plan_builder.js");
|
|
15
15
|
const git_js_1 = require("./agent/git.js");
|
|
16
|
+
const feedback_js_1 = require("./agent/feedback.js");
|
|
16
17
|
const diff_loader_js_1 = require("./engine/diff_loader.js");
|
|
17
18
|
const ai_enrichment_js_1 = require("./engine/ai_enrichment.js");
|
|
18
19
|
const anthropic_provider_js_1 = require("./anthropic_provider.js");
|
|
@@ -60,7 +61,8 @@ function recommendTestsDeterministic(options = {}) {
|
|
|
60
61
|
testsRoot: reportRoot,
|
|
61
62
|
routeFamilies: config.routeFamilies,
|
|
62
63
|
});
|
|
63
|
-
const
|
|
64
|
+
const adaptive = (0, feedback_js_1.getAdaptiveThresholds)(reportRoot);
|
|
65
|
+
const plan = (0, plan_builder_js_1.buildPlanFromImpact)(impact, config.policy, undefined, adaptive);
|
|
64
66
|
const planPath = (0, plan_builder_js_1.writePlanReport)(reportRoot, plan);
|
|
65
67
|
const ciSummaryMarkdown = (0, plan_builder_js_1.renderCiSummaryMarkdown)(plan);
|
|
66
68
|
const ciSummaryPath = (0, plan_builder_js_1.writeCiSummary)(reportRoot, ciSummaryMarkdown);
|
|
@@ -106,7 +108,8 @@ async function recommendTestsAI(options = {}) {
|
|
|
106
108
|
specDetails: [...specDetailsMap.values()],
|
|
107
109
|
});
|
|
108
110
|
}
|
|
109
|
-
const
|
|
111
|
+
const adaptive = (0, feedback_js_1.getAdaptiveThresholds)(reportRoot);
|
|
112
|
+
const plan = (0, plan_builder_js_1.buildPlanFromImpact)(impact, config.policy, aiEnrichment, adaptive);
|
|
110
113
|
const planPath = (0, plan_builder_js_1.writePlanReport)(reportRoot, plan);
|
|
111
114
|
const ciSummaryMarkdown = (0, plan_builder_js_1.renderCiSummaryMarkdown)(plan);
|
|
112
115
|
const ciSummaryPath = (0, plan_builder_js_1.writeCiSummary)(reportRoot, ciSummaryMarkdown);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"train.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/train.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AA6H5C,wBAAsB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6K1F"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
+
}) : function(o, v) {
|
|
18
|
+
o["default"] = v;
|
|
19
|
+
});
|
|
20
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
+
var ownKeys = function(o) {
|
|
22
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
+
var ar = [];
|
|
24
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
+
return ar;
|
|
26
|
+
};
|
|
27
|
+
return ownKeys(o);
|
|
28
|
+
};
|
|
29
|
+
return function (mod) {
|
|
30
|
+
if (mod && mod.__esModule) return mod;
|
|
31
|
+
var result = {};
|
|
32
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
+
__setModuleDefault(result, mod);
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.runTrainCommand = runTrainCommand;
|
|
39
|
+
const fs_1 = require("fs");
|
|
40
|
+
const path_1 = require("path");
|
|
41
|
+
const readline = __importStar(require("readline"));
|
|
42
|
+
const config_js_1 = require("../../agent/config.js");
|
|
43
|
+
const route_families_js_1 = require("../../knowledge/route_families.js");
|
|
44
|
+
const provider_factory_js_1 = require("../../provider_factory.js");
|
|
45
|
+
const scanner_js_1 = require("../../training/scanner.js");
|
|
46
|
+
const merger_js_1 = require("../../training/merger.js");
|
|
47
|
+
const enricher_js_1 = require("../../training/enricher.js");
|
|
48
|
+
const validator_js_1 = require("../../training/validator.js");
|
|
49
|
+
class TrainError extends Error {
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = 'TrainError';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const MAX_BUDGET_USD = 10;
|
|
56
|
+
/**
|
|
57
|
+
* Resolves train-specific options from CLI args.
|
|
58
|
+
* Unlike other commands (analyze, plan, heal) that use the shared resolveConfig()
|
|
59
|
+
* for full pipeline configuration, train only needs appPath and testsRoot.
|
|
60
|
+
* We call resolveConfig() solely to extract testsRoot from the config file.
|
|
61
|
+
*/
|
|
62
|
+
function resolveTrainOptions(args, autoConfig) {
|
|
63
|
+
const appPath = args.path || '.';
|
|
64
|
+
let testsRoot = args.testsRoot || appPath;
|
|
65
|
+
// Try to resolve testsRoot from config
|
|
66
|
+
if (autoConfig) {
|
|
67
|
+
try {
|
|
68
|
+
const { config } = (0, config_js_1.resolveConfig)(process.cwd(), autoConfig, {
|
|
69
|
+
path: appPath,
|
|
70
|
+
testsRoot: args.testsRoot,
|
|
71
|
+
});
|
|
72
|
+
testsRoot = config.testsRoot || config.path || appPath;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// use defaults
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const outputPath = args.trainOutput ||
|
|
79
|
+
(0, path_1.join)(testsRoot, '.e2e-ai-agents', 'route-families.json');
|
|
80
|
+
// Validate --pr is a positive integer
|
|
81
|
+
if (args.trainPr !== undefined && (!Number.isInteger(args.trainPr) || args.trainPr <= 0)) {
|
|
82
|
+
throw new TrainError('--pr must be a positive integer');
|
|
83
|
+
}
|
|
84
|
+
// Validate --pr and --since are mutually exclusive
|
|
85
|
+
if (args.trainPr && args.gitSince) {
|
|
86
|
+
throw new TrainError('--pr and --since are mutually exclusive.');
|
|
87
|
+
}
|
|
88
|
+
// Validate --since format (reject leading '-' to prevent git flag injection)
|
|
89
|
+
const since = args.gitSince || 'HEAD~20';
|
|
90
|
+
if (/^-/.test(since) || !/^[a-zA-Z0-9_.~^@\/-]+$/.test(since)) {
|
|
91
|
+
throw new TrainError(`Invalid git ref: ${since}`);
|
|
92
|
+
}
|
|
93
|
+
// Validate budget bounds
|
|
94
|
+
const budget = args.budgetUSD || 0.50;
|
|
95
|
+
if (budget <= 0) {
|
|
96
|
+
throw new TrainError('--budget-usd must be a positive number');
|
|
97
|
+
}
|
|
98
|
+
if (budget > MAX_BUDGET_USD) {
|
|
99
|
+
throw new TrainError(`Budget exceeds maximum of $${MAX_BUDGET_USD}. Use a lower --budget-usd value.`);
|
|
100
|
+
}
|
|
101
|
+
const resolvedAppPath = (0, path_1.resolve)(appPath);
|
|
102
|
+
const resolvedTestsRoot = (0, path_1.resolve)(testsRoot);
|
|
103
|
+
const resolvedOutputPath = (0, path_1.resolve)(outputPath);
|
|
104
|
+
// Validate --path is a real project
|
|
105
|
+
if (!(0, fs_1.existsSync)(resolvedAppPath)) {
|
|
106
|
+
throw new TrainError(`Project root not found: ${resolvedAppPath}`);
|
|
107
|
+
}
|
|
108
|
+
// Validate --output is within project boundary (append separator to prevent prefix attacks)
|
|
109
|
+
const inApp = resolvedOutputPath === resolvedAppPath || resolvedOutputPath.startsWith(resolvedAppPath + '/');
|
|
110
|
+
const inTests = resolvedOutputPath === resolvedTestsRoot || resolvedOutputPath.startsWith(resolvedTestsRoot + '/');
|
|
111
|
+
if (!inApp && !inTests) {
|
|
112
|
+
throw new TrainError(`Output path must be within the project root or tests root: ${resolvedOutputPath}`);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
appPath: resolvedAppPath,
|
|
116
|
+
testsRoot: resolvedTestsRoot,
|
|
117
|
+
enrich: args.trainEnrich !== false,
|
|
118
|
+
validate: args.trainValidate || false,
|
|
119
|
+
since,
|
|
120
|
+
pr: args.trainPr,
|
|
121
|
+
outputPath: resolvedOutputPath,
|
|
122
|
+
dryRun: args.dryRun || false,
|
|
123
|
+
yes: args.trainYes || false,
|
|
124
|
+
budgetUSD: budget,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function ask(rl, question, defaultValue) {
|
|
128
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
129
|
+
return new Promise((res) => {
|
|
130
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
131
|
+
res(answer.trim() || defaultValue || '');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function serializeManifest(manifest) {
|
|
136
|
+
const output = {
|
|
137
|
+
families: manifest.families.map((f) => {
|
|
138
|
+
// Remove undefined/empty optional fields for clean JSON
|
|
139
|
+
const cleaned = { ...f };
|
|
140
|
+
const optionalArrays = ['pageObjects', 'components', 'webappPaths', 'serverPaths', 'specDirs', 'cypressSpecDirs', 'tags', 'userFlows', 'features'];
|
|
141
|
+
for (const key of optionalArrays) {
|
|
142
|
+
if (!cleaned[key] || (Array.isArray(cleaned[key]) && cleaned[key].length === 0)) {
|
|
143
|
+
delete cleaned[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!cleaned.priority)
|
|
147
|
+
delete cleaned.priority;
|
|
148
|
+
return cleaned;
|
|
149
|
+
}),
|
|
150
|
+
};
|
|
151
|
+
return JSON.stringify(output, null, 2) + '\n';
|
|
152
|
+
}
|
|
153
|
+
async function runTrainCommand(args, autoConfig) {
|
|
154
|
+
const opts = resolveTrainOptions(args, autoConfig);
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log(' e2e-ai-agents train');
|
|
157
|
+
console.log(' ===================');
|
|
158
|
+
console.log('');
|
|
159
|
+
// ---------- Phase 1: Deterministic scan ----------
|
|
160
|
+
console.log(' Scanning project structure...');
|
|
161
|
+
const scanResult = (0, scanner_js_1.scanProject)(opts.appPath);
|
|
162
|
+
console.log(` Found ${scanResult.stats.totalSourceFiles} source files, ${scanResult.stats.totalTestFiles} test files`);
|
|
163
|
+
console.log(` Discovered ${scanResult.families.length} candidate families`);
|
|
164
|
+
if (scanResult.families.length === 0) {
|
|
165
|
+
console.log('');
|
|
166
|
+
console.log(' No families discovered. Make sure your project has recognizable');
|
|
167
|
+
console.log(' source directories (src/, server/, app/) and test directories');
|
|
168
|
+
console.log(' (tests/, e2e/, specs/) with matching names.');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// ---------- Phase 2: Merge with existing ----------
|
|
172
|
+
const existing = (0, route_families_js_1.loadRouteFamilyManifest)(opts.testsRoot);
|
|
173
|
+
if (existing) {
|
|
174
|
+
console.log(` Found existing manifest with ${existing.families.length} families`);
|
|
175
|
+
}
|
|
176
|
+
let mergeResult = (0, merger_js_1.mergeFamilies)(existing, scanResult.families);
|
|
177
|
+
console.log(` Merge: ${mergeResult.summary}`);
|
|
178
|
+
// ---------- Phase 3: Stale detection ----------
|
|
179
|
+
if (mergeResult.manifest.families.length > 0) {
|
|
180
|
+
const stale = (0, merger_js_1.detectStaleFamilies)(mergeResult.manifest, opts.appPath);
|
|
181
|
+
if (stale.length > 0) {
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log(` Stale families detected (${stale.length}):`);
|
|
184
|
+
for (const id of stale) {
|
|
185
|
+
console.log(` ${id} — paths no longer exist`);
|
|
186
|
+
}
|
|
187
|
+
if (!opts.yes && !opts.dryRun && process.stdin.isTTY) {
|
|
188
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
189
|
+
try {
|
|
190
|
+
const answer = await ask(rl, ' Remove stale families? [y/N]', 'N');
|
|
191
|
+
if (answer.toLowerCase() === 'y') {
|
|
192
|
+
const staleSet = new Set(stale);
|
|
193
|
+
mergeResult.manifest.families = mergeResult.manifest.families.filter((f) => !staleSet.has(f.id));
|
|
194
|
+
mergeResult.staleFamilies = stale;
|
|
195
|
+
console.log(` Removed ${stale.length} stale families`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
rl.close();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// ---------- Phase 4: LLM Enrichment ----------
|
|
205
|
+
if (opts.enrich) {
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(' Enriching with LLM...');
|
|
208
|
+
try {
|
|
209
|
+
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);
|
|
211
|
+
mergeResult.manifest.families = enrichResult.enrichedFamilies;
|
|
212
|
+
console.log(` Enriched ${enrichResult.enrichedFamilies.length} families (${enrichResult.tokensUsed} tokens, ~$${enrichResult.costUSD})`);
|
|
213
|
+
if (enrichResult.skippedFamilies.length > 0) {
|
|
214
|
+
console.log(` Skipped ${enrichResult.skippedFamilies.length} families (budget limit)`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
console.warn(` LLM enrichment failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
219
|
+
console.warn(' Continuing with deterministic results. Use --no-enrich to skip LLM.');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// ---------- Phase 5: Write manifest ----------
|
|
223
|
+
console.log('');
|
|
224
|
+
const json = serializeManifest(mergeResult.manifest);
|
|
225
|
+
if (opts.dryRun) {
|
|
226
|
+
console.log(' Dry run — proposed manifest:');
|
|
227
|
+
console.log('');
|
|
228
|
+
console.log(json);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const dir = (0, path_1.dirname)(opts.outputPath);
|
|
232
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
233
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
234
|
+
}
|
|
235
|
+
const tmpPath = `${opts.outputPath}.tmp`;
|
|
236
|
+
(0, fs_1.writeFileSync)(tmpPath, json, 'utf-8');
|
|
237
|
+
(0, fs_1.renameSync)(tmpPath, opts.outputPath);
|
|
238
|
+
console.log(` Wrote ${opts.outputPath}`);
|
|
239
|
+
console.log(` ${mergeResult.manifest.families.length} families`);
|
|
240
|
+
}
|
|
241
|
+
// ---------- Phase 6: Report unmatched ----------
|
|
242
|
+
if (scanResult.unmatchedSourceDirs.length > 0 || scanResult.unmatchedTestDirs.length > 0) {
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log(' Unmatched (review manually):');
|
|
245
|
+
for (const dir of scanResult.unmatchedSourceDirs.slice(0, 10)) {
|
|
246
|
+
console.log(` source: ${dir.relativePath}`);
|
|
247
|
+
}
|
|
248
|
+
for (const dir of scanResult.unmatchedTestDirs.slice(0, 10)) {
|
|
249
|
+
console.log(` test: ${dir.relativePath}`);
|
|
250
|
+
}
|
|
251
|
+
if (scanResult.unmatchedSourceDirs.length + scanResult.unmatchedTestDirs.length > 20) {
|
|
252
|
+
console.log(' ... and more');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// ---------- Phase 7: Validation (optional) ----------
|
|
256
|
+
if (opts.validate) {
|
|
257
|
+
if (opts.pr) {
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log(` Validating against PR #${opts.pr}...`);
|
|
260
|
+
// Check for gh CLI
|
|
261
|
+
const { execFileSync } = await import('child_process');
|
|
262
|
+
try {
|
|
263
|
+
execFileSync('gh', ['--version'], { stdio: 'pipe' });
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
throw new TrainError('--pr requires the GitHub CLI (gh). Install: https://cli.github.com/');
|
|
267
|
+
}
|
|
268
|
+
// Fetch PR changed files via gh CLI
|
|
269
|
+
let prFiles;
|
|
270
|
+
try {
|
|
271
|
+
const output = execFileSync('gh', ['pr', 'view', String(opts.pr), '--json', 'files', '-q', '.files[].path'], {
|
|
272
|
+
cwd: opts.appPath,
|
|
273
|
+
encoding: 'utf-8',
|
|
274
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
275
|
+
});
|
|
276
|
+
prFiles = output.trim().split('\n').filter(Boolean);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
throw new TrainError(`Error fetching PR #${opts.pr}: ${error instanceof Error ? error.message : String(error)}`);
|
|
280
|
+
}
|
|
281
|
+
if (prFiles.length === 0) {
|
|
282
|
+
console.log(' No files found in PR.');
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const validation = (0, validator_js_1.validateCommit)(mergeResult.manifest, prFiles, `PR#${opts.pr}`, `PR #${opts.pr}`);
|
|
286
|
+
const report = (0, validator_js_1.buildValidationReport)([validation], mergeResult.manifest);
|
|
287
|
+
console.log('');
|
|
288
|
+
console.log((0, validator_js_1.formatValidationReport)(report));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.log('');
|
|
293
|
+
console.log(` Validating against git history (${opts.since})...`);
|
|
294
|
+
const commits = (0, validator_js_1.getCommitFiles)(opts.appPath, opts.since);
|
|
295
|
+
if (commits.length === 0) {
|
|
296
|
+
console.log(' No commits found in range.');
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const validations = commits.map((c) => (0, validator_js_1.validateCommit)(mergeResult.manifest, c.files, c.hash, c.message));
|
|
300
|
+
const report = (0, validator_js_1.buildValidationReport)(validations, mergeResult.manifest);
|
|
301
|
+
console.log('');
|
|
302
|
+
console.log((0, validator_js_1.formatValidationReport)(report));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
console.log('');
|
|
307
|
+
}
|
|
@@ -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;AA2ID,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA4EpD"}
|
package/dist/cli/parse_args.js
CHANGED
|
@@ -67,6 +67,10 @@ const FLAGS = {
|
|
|
67
67
|
'--generate': { key: 'analyzeGenerate', type: 'boolean' },
|
|
68
68
|
'--heal': { key: 'analyzeHeal', type: 'boolean' },
|
|
69
69
|
'--no-ai': { key: 'noAi', type: 'boolean' },
|
|
70
|
+
'--enrich': { key: 'trainEnrich', type: 'boolean' },
|
|
71
|
+
'--no-enrich': { key: 'trainEnrich', type: 'boolean-false' },
|
|
72
|
+
'--validate': { key: 'trainValidate', type: 'boolean' },
|
|
73
|
+
'--yes': { key: 'trainYes', type: 'boolean', aliases: ['-y'] },
|
|
70
74
|
'--mattermost': { key: 'profile', type: 'boolean', transform: () => 'mattermost' },
|
|
71
75
|
// -- string flags --
|
|
72
76
|
'--config': { key: 'configPath', type: 'string' },
|
|
@@ -96,6 +100,7 @@ const FLAGS = {
|
|
|
96
100
|
'--generate-output': { key: 'analyzeGenerateOutputDir', type: 'string' },
|
|
97
101
|
'--heal-report': { key: 'analyzeHealReport', type: 'string' },
|
|
98
102
|
'--flow-catalog': { key: 'flowCatalogPath', type: 'string' },
|
|
103
|
+
'--output': { key: 'trainOutput', type: 'string' },
|
|
99
104
|
// -- number flags (with isFinite guard) --
|
|
100
105
|
'--pipeline-scenarios': { key: 'pipelineScenarios', type: 'number' },
|
|
101
106
|
'--time': { key: 'timeLimitMinutes', type: 'number' },
|
|
@@ -107,6 +112,7 @@ const FLAGS = {
|
|
|
107
112
|
'--traceability-min-hits': { key: 'traceabilityMinHits', type: 'number' },
|
|
108
113
|
'--traceability-max-files-per-test': { key: 'traceabilityMaxFilesPerTest', type: 'number' },
|
|
109
114
|
'--traceability-max-age-days': { key: 'traceabilityMaxAgeDays', type: 'number' },
|
|
115
|
+
'--pr': { key: 'trainPr', type: 'number' },
|
|
110
116
|
// -- number-raw flags (no isFinite guard, assigned directly via Number()) --
|
|
111
117
|
'--max-attempts': { key: 'maxAttempts', type: 'number-raw', transform: (v) => parseInt(v, 10) },
|
|
112
118
|
'--pipeline-mcp-timeout-ms': { key: 'pipelineMcpTimeoutMs', type: 'number-raw' },
|
|
@@ -140,7 +146,7 @@ const COMMANDS = new Set([
|
|
|
140
146
|
'init', 'impact', 'plan', 'heal', 'suggest', 'generate',
|
|
141
147
|
'finalize-generated-tests', 'feedback',
|
|
142
148
|
'traceability-capture', 'traceability-ingest',
|
|
143
|
-
'analyze', 'llm-health',
|
|
149
|
+
'analyze', 'llm-health', 'train',
|
|
144
150
|
]);
|
|
145
151
|
// ---------------------------------------------------------------------------
|
|
146
152
|
// Parser
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnalysisProfile, FrameworkType } from '../agent/config.js';
|
|
2
|
-
export type Command = 'init' | 'impact' | 'plan' | 'heal' | 'suggest' | 'generate' | 'finalize-generated-tests' | 'feedback' | 'traceability-capture' | 'traceability-ingest' | 'analyze' | 'llm-health';
|
|
2
|
+
export type Command = 'init' | 'impact' | 'plan' | 'heal' | 'suggest' | 'generate' | 'finalize-generated-tests' | 'feedback' | 'traceability-capture' | 'traceability-ingest' | 'analyze' | 'llm-health' | 'train';
|
|
3
3
|
export interface ParsedArgs {
|
|
4
4
|
command?: Command;
|
|
5
5
|
configPath?: string;
|
|
@@ -66,5 +66,10 @@ export interface ParsedArgs {
|
|
|
66
66
|
noAi?: boolean;
|
|
67
67
|
maxAttempts?: number;
|
|
68
68
|
generateScenarios?: string;
|
|
69
|
+
trainEnrich?: boolean;
|
|
70
|
+
trainValidate?: boolean;
|
|
71
|
+
trainPr?: number;
|
|
72
|
+
trainOutput?: string;
|
|
73
|
+
trainYes?: boolean;
|
|
69
74
|
}
|
|
70
75
|
//# sourceMappingURL=types.d.ts.map
|