@yasserkhanorg/e2e-agents 1.8.4 → 1.9.5
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 +95 -8
- package/dist/adapters/cypress.d.ts +10 -0
- package/dist/adapters/cypress.d.ts.map +1 -0
- package/dist/adapters/cypress.js +86 -0
- package/dist/adapters/framework_adapter.d.ts +41 -0
- package/dist/adapters/framework_adapter.d.ts.map +1 -0
- package/dist/adapters/framework_adapter.js +152 -0
- package/dist/adapters/playwright.d.ts +10 -0
- package/dist/adapters/playwright.d.ts.map +1 -0
- package/dist/adapters/playwright.js +86 -0
- package/dist/adapters/pytest.d.ts +10 -0
- package/dist/adapters/pytest.d.ts.map +1 -0
- package/dist/adapters/pytest.js +96 -0
- package/dist/adapters/supertest.d.ts +12 -0
- package/dist/adapters/supertest.d.ts.map +1 -0
- package/dist/adapters/supertest.js +85 -0
- package/dist/agent/config.d.ts +1 -1
- package/dist/agent/config.d.ts.map +1 -1
- package/dist/agent/git.d.ts +1 -0
- package/dist/agent/git.d.ts.map +1 -1
- package/dist/agent/git.js +3 -0
- package/dist/agentic/fix_loop.d.ts.map +1 -1
- package/dist/agentic/fix_loop.js +5 -4
- package/dist/agentic/runner.d.ts +2 -0
- package/dist/agentic/runner.d.ts.map +1 -1
- package/dist/agentic/runner.js +15 -12
- package/dist/agents/cross-impact.d.ts.map +1 -1
- package/dist/agents/cross-impact.js +6 -1
- package/dist/agents/executor.d.ts.map +1 -1
- package/dist/agents/executor.js +6 -1
- package/dist/agents/strategist.d.ts.map +1 -1
- package/dist/agents/strategist.js +6 -1
- package/dist/agents/test-designer.d.ts.map +1 -1
- package/dist/agents/test-designer.js +6 -1
- package/dist/anthropic_provider.d.ts.map +1 -1
- package/dist/anthropic_provider.js +1 -0
- package/dist/base_provider.d.ts +56 -0
- package/dist/base_provider.d.ts.map +1 -1
- package/dist/base_provider.js +123 -1
- package/dist/budget_ledger.d.ts +28 -0
- package/dist/budget_ledger.d.ts.map +1 -0
- package/dist/budget_ledger.js +62 -0
- package/dist/cache/cached_provider.d.ts +45 -0
- package/dist/cache/cached_provider.d.ts.map +1 -0
- package/dist/cache/cached_provider.js +88 -0
- package/dist/cache/response_cache.d.ts +79 -0
- package/dist/cache/response_cache.d.ts.map +1 -0
- package/dist/cache/response_cache.js +177 -0
- package/dist/cli/commands/bootstrap.d.ts +3 -0
- package/dist/cli/commands/bootstrap.d.ts.map +1 -0
- package/dist/cli/commands/bootstrap.js +109 -0
- package/dist/cli/commands/cost_report.d.ts +3 -0
- package/dist/cli/commands/cost_report.d.ts.map +1 -0
- package/dist/cli/commands/cost_report.js +115 -0
- package/dist/cli/commands/crew.d.ts.map +1 -1
- package/dist/cli/commands/crew.js +118 -1
- package/dist/cli/commands/gate.d.ts +3 -0
- package/dist/cli/commands/gate.d.ts.map +1 -0
- package/dist/cli/commands/gate.js +86 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -62
- package/dist/cli/commands/plan_crew.d.ts.map +1 -1
- package/dist/cli/commands/plan_crew.js +33 -21
- package/dist/cli/commands/train.d.ts.map +1 -1
- package/dist/cli/commands/train.js +16 -21
- package/dist/cli/defaults.d.ts +35 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +125 -0
- package/dist/cli/errors.d.ts +27 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +57 -0
- package/dist/cli/parse_args.d.ts.map +1 -1
- package/dist/cli/parse_args.js +24 -2
- package/dist/cli/types.d.ts +7 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli.js +47 -2
- package/dist/crew/context.d.ts +15 -0
- package/dist/crew/context.d.ts.map +1 -1
- package/dist/crew/orchestrator.d.ts +14 -0
- package/dist/crew/orchestrator.d.ts.map +1 -1
- package/dist/crew/orchestrator.js +162 -4
- package/dist/crew/protocol.d.ts +13 -0
- package/dist/crew/protocol.d.ts.map +1 -1
- package/dist/crew/provider.d.ts +15 -1
- package/dist/crew/provider.d.ts.map +1 -1
- package/dist/crew/provider.js +24 -4
- package/dist/custom_provider.d.ts.map +1 -1
- package/dist/custom_provider.js +1 -0
- package/dist/engine/diff_loader.d.ts.map +1 -1
- package/dist/engine/diff_loader.js +3 -14
- package/dist/engine/impact_engine.d.ts.map +1 -1
- package/dist/engine/impact_engine.js +9 -23
- package/dist/esm/adapters/cypress.js +49 -0
- package/dist/esm/adapters/framework_adapter.js +114 -0
- package/dist/esm/adapters/playwright.js +49 -0
- package/dist/esm/adapters/pytest.js +59 -0
- package/dist/esm/adapters/supertest.js +48 -0
- package/dist/esm/agent/git.js +3 -1
- package/dist/esm/agentic/fix_loop.js +5 -4
- package/dist/esm/agentic/runner.js +15 -12
- package/dist/esm/agents/cross-impact.js +6 -1
- package/dist/esm/agents/executor.js +6 -1
- package/dist/esm/agents/strategist.js +6 -1
- package/dist/esm/agents/test-designer.js +6 -1
- package/dist/esm/anthropic_provider.js +1 -0
- package/dist/esm/base_provider.js +121 -0
- package/dist/esm/budget_ledger.js +58 -0
- package/dist/esm/cache/cached_provider.js +82 -0
- package/dist/esm/cache/response_cache.js +140 -0
- package/dist/esm/cli/commands/bootstrap.js +106 -0
- package/dist/esm/cli/commands/cost_report.js +112 -0
- package/dist/esm/cli/commands/crew.js +118 -1
- package/dist/esm/cli/commands/gate.js +83 -0
- package/dist/esm/cli/commands/init.js +3 -58
- package/dist/esm/cli/commands/plan_crew.js +33 -21
- package/dist/esm/cli/commands/train.js +16 -21
- package/dist/esm/cli/defaults.js +118 -0
- package/dist/esm/cli/errors.js +52 -0
- package/dist/esm/cli/parse_args.js +24 -2
- package/dist/esm/cli.js +47 -2
- package/dist/esm/crew/orchestrator.js +162 -4
- package/dist/esm/crew/provider.js +24 -4
- package/dist/esm/custom_provider.js +1 -0
- package/dist/esm/engine/diff_loader.js +1 -12
- package/dist/esm/engine/impact_engine.js +9 -23
- package/dist/esm/index.js +21 -0
- package/dist/esm/knowledge/cluster_utils.js +60 -0
- package/dist/esm/knowledge/kg_bridge.js +381 -0
- package/dist/esm/knowledge/kg_types.js +3 -0
- package/dist/esm/knowledge/route_families.js +89 -0
- package/dist/esm/mcp-server.js +2 -4
- package/dist/esm/metrics/prometheus.js +149 -0
- package/dist/esm/model_router.js +59 -0
- package/dist/esm/ollama_provider.js +1 -0
- package/dist/esm/openai_provider.js +1 -0
- package/dist/esm/pipeline/orchestrator.js +6 -12
- package/dist/esm/pipeline/stage0_preprocess.js +12 -19
- package/dist/esm/pipeline/stage2_coverage.js +1 -0
- package/dist/esm/pipeline/stage3_generation.js +1 -0
- package/dist/esm/progress.js +112 -0
- package/dist/esm/prompts/coverage.js +7 -24
- package/dist/esm/prompts/cross-impact.js +3 -21
- package/dist/esm/prompts/generation.js +158 -36
- package/dist/esm/prompts/generation_profile.js +147 -0
- package/dist/esm/prompts/heal.js +33 -15
- package/dist/esm/prompts/impact.js +3 -22
- package/dist/esm/prompts/json_extract.js +36 -0
- package/dist/esm/prompts/strategist.js +2 -20
- package/dist/esm/prompts/test-designer.js +6 -21
- package/dist/esm/provider_factory.js +6 -4
- package/dist/esm/reporters/junit.js +86 -0
- package/dist/esm/reporters/reporter.js +3 -0
- package/dist/esm/reporters/sarif.js +131 -0
- package/dist/esm/resilience/circuit_breaker.js +78 -0
- package/dist/esm/resilience/retry.js +56 -0
- package/dist/esm/sanitize.js +66 -0
- package/dist/esm/training/kg_scanner.js +115 -0
- package/dist/esm/training/scanner.js +27 -34
- package/dist/esm/version.js +33 -0
- package/dist/index.d.ts +21 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -1
- package/dist/knowledge/cluster_utils.d.ts +28 -0
- package/dist/knowledge/cluster_utils.d.ts.map +1 -0
- package/dist/knowledge/cluster_utils.js +67 -0
- package/dist/knowledge/kg_bridge.d.ts +31 -0
- package/dist/knowledge/kg_bridge.d.ts.map +1 -0
- package/dist/knowledge/kg_bridge.js +388 -0
- package/dist/knowledge/kg_types.d.ts +75 -0
- package/dist/knowledge/kg_types.d.ts.map +1 -0
- package/dist/knowledge/kg_types.js +4 -0
- package/dist/knowledge/route_families.d.ts +18 -0
- package/dist/knowledge/route_families.d.ts.map +1 -1
- package/dist/knowledge/route_families.js +91 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +2 -4
- package/dist/metrics/prometheus.d.ts +37 -0
- package/dist/metrics/prometheus.d.ts.map +1 -0
- package/dist/metrics/prometheus.js +153 -0
- package/dist/model_router.d.ts +28 -0
- package/dist/model_router.d.ts.map +1 -0
- package/dist/model_router.js +63 -0
- package/dist/ollama_provider.d.ts.map +1 -1
- package/dist/ollama_provider.js +1 -0
- package/dist/openai_provider.d.ts.map +1 -1
- package/dist/openai_provider.js +1 -0
- package/dist/pipeline/orchestrator.d.ts +2 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +6 -12
- package/dist/pipeline/stage0_preprocess.d.ts.map +1 -1
- package/dist/pipeline/stage0_preprocess.js +11 -18
- package/dist/pipeline/stage2_coverage.d.ts +2 -0
- package/dist/pipeline/stage2_coverage.d.ts.map +1 -1
- package/dist/pipeline/stage2_coverage.js +1 -0
- package/dist/pipeline/stage3_generation.d.ts +2 -0
- package/dist/pipeline/stage3_generation.d.ts.map +1 -1
- package/dist/pipeline/stage3_generation.js +1 -0
- package/dist/pipeline/stage4_heal.d.ts +2 -0
- package/dist/pipeline/stage4_heal.d.ts.map +1 -1
- package/dist/progress.d.ts +22 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +116 -0
- package/dist/prompts/coverage.d.ts +2 -0
- package/dist/prompts/coverage.d.ts.map +1 -1
- package/dist/prompts/coverage.js +7 -24
- package/dist/prompts/cross-impact.d.ts +1 -0
- package/dist/prompts/cross-impact.d.ts.map +1 -1
- package/dist/prompts/cross-impact.js +3 -21
- package/dist/prompts/generation.d.ts +3 -1
- package/dist/prompts/generation.d.ts.map +1 -1
- package/dist/prompts/generation.js +158 -36
- package/dist/prompts/generation_profile.d.ts +29 -0
- package/dist/prompts/generation_profile.d.ts.map +1 -0
- package/dist/prompts/generation_profile.js +151 -0
- package/dist/prompts/heal.d.ts +3 -1
- package/dist/prompts/heal.d.ts.map +1 -1
- package/dist/prompts/heal.js +33 -15
- package/dist/prompts/impact.d.ts +1 -0
- package/dist/prompts/impact.d.ts.map +1 -1
- package/dist/prompts/impact.js +3 -22
- package/dist/prompts/json_extract.d.ts +14 -0
- package/dist/prompts/json_extract.d.ts.map +1 -0
- package/dist/prompts/json_extract.js +39 -0
- package/dist/prompts/strategist.d.ts.map +1 -1
- package/dist/prompts/strategist.js +2 -20
- package/dist/prompts/test-designer.d.ts +2 -0
- package/dist/prompts/test-designer.d.ts.map +1 -1
- package/dist/prompts/test-designer.js +6 -21
- package/dist/provider_factory.d.ts.map +1 -1
- package/dist/provider_factory.js +6 -4
- package/dist/reporters/junit.d.ts +6 -0
- package/dist/reporters/junit.d.ts.map +1 -0
- package/dist/reporters/junit.js +89 -0
- package/dist/reporters/reporter.d.ts +42 -0
- package/dist/reporters/reporter.d.ts.map +1 -0
- package/dist/reporters/reporter.js +4 -0
- package/dist/reporters/sarif.d.ts +7 -0
- package/dist/reporters/sarif.d.ts.map +1 -0
- package/dist/reporters/sarif.js +134 -0
- package/dist/resilience/circuit_breaker.d.ts +36 -0
- package/dist/resilience/circuit_breaker.d.ts.map +1 -0
- package/dist/resilience/circuit_breaker.js +82 -0
- package/dist/resilience/retry.d.ts +11 -0
- package/dist/resilience/retry.d.ts.map +1 -0
- package/dist/resilience/retry.js +59 -0
- package/dist/sanitize.d.ts +15 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +71 -0
- package/dist/training/kg_scanner.d.ts +13 -0
- package/dist/training/kg_scanner.d.ts.map +1 -0
- package/dist/training/kg_scanner.js +118 -0
- package/dist/training/scanner.d.ts +7 -2
- package/dist/training/scanner.d.ts.map +1 -1
- package/dist/training/scanner.js +27 -34
- package/dist/version.d.ts +6 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +36 -0
- package/package.json +7 -2
- package/schemas/route-families.schema.json +31 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
|
-
import { existsSync, writeFileSync
|
|
4
|
-
import {
|
|
5
|
-
import { join, resolve } from 'path';
|
|
3
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
6
5
|
import * as readline from 'readline';
|
|
6
|
+
import { detectFramework, detectTestsRoot, detectGitDefaultBranch } from '../defaults.js';
|
|
7
7
|
const CONFIG_FILENAME = 'e2e-ai-agents.config.json';
|
|
8
8
|
function createInterface() {
|
|
9
9
|
return readline.createInterface({
|
|
@@ -19,61 +19,6 @@ function ask(rl, question, defaultValue) {
|
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
function detectFramework(appPath) {
|
|
23
|
-
const resolvedPath = resolve(appPath);
|
|
24
|
-
const pkgPath = join(resolvedPath, 'package.json');
|
|
25
|
-
if (!existsSync(pkgPath)) {
|
|
26
|
-
return 'auto';
|
|
27
|
-
}
|
|
28
|
-
try {
|
|
29
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
30
|
-
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
31
|
-
if (allDeps['@playwright/test'] || allDeps.playwright) {
|
|
32
|
-
return 'playwright';
|
|
33
|
-
}
|
|
34
|
-
if (allDeps.cypress) {
|
|
35
|
-
return 'cypress';
|
|
36
|
-
}
|
|
37
|
-
if (allDeps['selenium-webdriver'] || allDeps.webdriverio) {
|
|
38
|
-
return 'selenium';
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// ignore
|
|
43
|
-
}
|
|
44
|
-
return 'auto';
|
|
45
|
-
}
|
|
46
|
-
function detectGitDefaultBranch(appPath) {
|
|
47
|
-
try {
|
|
48
|
-
const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
49
|
-
cwd: resolve(appPath),
|
|
50
|
-
encoding: 'utf-8',
|
|
51
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
52
|
-
}).trim();
|
|
53
|
-
return `origin/${result}`;
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return 'origin/main';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function detectTestsRoot(appPath) {
|
|
60
|
-
const resolvedPath = resolve(appPath);
|
|
61
|
-
const candidates = [
|
|
62
|
-
'e2e-tests/playwright',
|
|
63
|
-
'e2e-tests',
|
|
64
|
-
'e2e',
|
|
65
|
-
'tests/e2e',
|
|
66
|
-
'test/e2e',
|
|
67
|
-
'tests',
|
|
68
|
-
'test',
|
|
69
|
-
];
|
|
70
|
-
for (const candidate of candidates) {
|
|
71
|
-
if (existsSync(join(resolvedPath, candidate))) {
|
|
72
|
-
return candidate;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
22
|
function buildConfig(answers) {
|
|
78
23
|
const config = {
|
|
79
24
|
path: answers.path,
|
|
@@ -88,11 +88,11 @@ export function buildCrewMarkdown(crew, plan) {
|
|
|
88
88
|
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
89
89
|
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
90
90
|
const lines = [
|
|
91
|
-
'### Crew
|
|
91
|
+
'### Crew Analysis — What to Test',
|
|
92
92
|
'',
|
|
93
|
-
`
|
|
94
|
-
|
|
95
|
-
`Strategy entries: **${crew.summary.strategyEntries}**`,
|
|
93
|
+
`Crew analyzed the diff and recommends what to verify before merging.`,
|
|
94
|
+
'',
|
|
95
|
+
`Impacted flows: **${crew.summary.impactedFlows}** | Strategy entries: **${crew.summary.strategyEntries}**`,
|
|
96
96
|
];
|
|
97
97
|
if (totalCases > 0) {
|
|
98
98
|
const gapDesigns = gapFamilies.size > 0
|
|
@@ -177,37 +177,48 @@ export function buildCrewTestPlan(crew, plan) {
|
|
|
177
177
|
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
178
178
|
const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
179
179
|
const lines = [
|
|
180
|
-
'# Crew Test Plan',
|
|
180
|
+
'# Crew Test Plan — What to Verify',
|
|
181
|
+
'',
|
|
182
|
+
'> **This is a test recommendation, not a test execution report.**',
|
|
183
|
+
'> Crew analyzed the code diff and identified what needs to be tested.',
|
|
184
|
+
'> Use this plan to guide manual QA or write automated E2E tests.',
|
|
181
185
|
'',
|
|
182
|
-
|
|
186
|
+
`_Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)_`,
|
|
183
187
|
'',
|
|
184
188
|
'## Summary',
|
|
185
189
|
'',
|
|
186
190
|
`| Metric | Count |`,
|
|
187
191
|
`|--------|-------|`,
|
|
188
|
-
`| Gap flows
|
|
189
|
-
`| Covered flows
|
|
190
|
-
`| Total
|
|
192
|
+
`| Gap flows — **no existing tests, must verify** | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
|
|
193
|
+
`| Covered flows — **has tests, verify no regressions** | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
|
|
194
|
+
`| Total | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
|
|
191
195
|
`| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
|
|
192
196
|
`| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
|
|
193
197
|
'',
|
|
194
198
|
];
|
|
195
199
|
// ── Gap flows ──
|
|
196
200
|
if (gapStrategies.length > 0) {
|
|
197
|
-
lines.push('##
|
|
201
|
+
lines.push('## Action Required: Gap Flows (No Existing Tests)');
|
|
198
202
|
lines.push('');
|
|
199
|
-
lines.push('These flows have **no
|
|
203
|
+
lines.push('These flows have **no automated E2E coverage**. Before merging, either:');
|
|
204
|
+
lines.push('1. **Write E2E tests** for the critical scenarios below, or');
|
|
205
|
+
lines.push('2. **Manually verify** each flow works as expected');
|
|
200
206
|
lines.push('');
|
|
201
207
|
for (const strategy of gapStrategies) {
|
|
202
208
|
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
203
209
|
lines.push(`### ${strategy.flowName}`);
|
|
204
210
|
lines.push('');
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
const actionVerb = strategy.approach === 'full-test' ? 'Write full E2E test or verify manually'
|
|
212
|
+
: strategy.approach === 'smoke-test' ? 'Smoke-test manually or add basic E2E coverage'
|
|
213
|
+
: strategy.approach === 'manual-review' ? 'Manual review required'
|
|
214
|
+
: 'Can skip — low risk';
|
|
215
|
+
lines.push(`**${strategy.priority}** | Recommended: **${strategy.approach}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
|
|
216
|
+
lines.push(`> ${actionVerb}`);
|
|
217
|
+
if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
|
|
218
|
+
lines.push(`> Rationale: ${strategy.rationale}`);
|
|
208
219
|
}
|
|
209
220
|
if (strategy.testCategories.length > 0) {
|
|
210
|
-
lines.push(
|
|
221
|
+
lines.push(`> Test types to cover: ${strategy.testCategories.join(', ')}`);
|
|
211
222
|
}
|
|
212
223
|
lines.push('');
|
|
213
224
|
// If test designs exist, show P0/P1 cases
|
|
@@ -242,9 +253,10 @@ export function buildCrewTestPlan(crew, plan) {
|
|
|
242
253
|
}
|
|
243
254
|
// ── Covered flows ──
|
|
244
255
|
if (coveredStrategies.length > 0) {
|
|
245
|
-
lines.push('## Covered Flows (
|
|
256
|
+
lines.push('## Regression Check: Covered Flows (Already Have Tests)');
|
|
246
257
|
lines.push('');
|
|
247
|
-
lines.push('These flows
|
|
258
|
+
lines.push('These flows have existing E2E specs. The existing tests should catch regressions automatically.');
|
|
259
|
+
lines.push('**No manual action required** unless CI tests fail on these flows.');
|
|
248
260
|
lines.push('');
|
|
249
261
|
for (const strategy of coveredStrategies) {
|
|
250
262
|
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
@@ -252,8 +264,8 @@ export function buildCrewTestPlan(crew, plan) {
|
|
|
252
264
|
const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
|
|
253
265
|
lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
|
|
254
266
|
lines.push('');
|
|
255
|
-
lines.push(`Cross-impact risk: ${strategy.crossImpactRisk}`);
|
|
256
|
-
if (strategy.rationale) {
|
|
267
|
+
lines.push(`Existing tests should cover this. Cross-impact risk: ${strategy.crossImpactRisk}`);
|
|
268
|
+
if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
|
|
257
269
|
lines.push(`> ${strategy.rationale}`);
|
|
258
270
|
}
|
|
259
271
|
if (strategy.testCategories.length > 0) {
|
|
@@ -273,9 +285,9 @@ export function buildCrewTestPlan(crew, plan) {
|
|
|
273
285
|
// ── Cross-impacts ──
|
|
274
286
|
const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
|
|
275
287
|
if (highRisk.length > 0) {
|
|
276
|
-
lines.push('## High-Risk Cross-Impacts');
|
|
288
|
+
lines.push('## High-Risk Cross-Impacts — Verify Before Release');
|
|
277
289
|
lines.push('');
|
|
278
|
-
lines.push('
|
|
290
|
+
lines.push('Changes in one area may break these related areas. Manually verify or ensure E2E tests cover both sides:');
|
|
279
291
|
lines.push('');
|
|
280
292
|
for (const ci of highRisk) {
|
|
281
293
|
lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
|
|
@@ -5,13 +5,16 @@ import { existsSync, mkdirSync, renameSync, writeFileSync } from 'fs';
|
|
|
5
5
|
import { dirname, join, resolve } from 'path';
|
|
6
6
|
import * as readline from 'readline';
|
|
7
7
|
import { resolveConfig } from '../../agent/config.js';
|
|
8
|
-
import { loadRouteFamilyManifest } from '../../knowledge/route_families.js';
|
|
8
|
+
import { loadRouteFamilyManifest, serializeManifest } from '../../knowledge/route_families.js';
|
|
9
9
|
import { LLMProviderFactory } from '../../provider_factory.js';
|
|
10
10
|
import { logger, LogLevel } from '../../logger.js';
|
|
11
|
+
import { getVersion } from '../../version.js';
|
|
11
12
|
import { scanProject } from '../../training/scanner.js';
|
|
13
|
+
import { scanFromKnowledgeGraph } from '../../training/kg_scanner.js';
|
|
12
14
|
import { mergeFamilies, detectStaleFamilies } from '../../training/merger.js';
|
|
13
15
|
import { enrichFamilies } from '../../training/enricher.js';
|
|
14
16
|
import { getCommitFiles, validateCommit, buildValidationReport, formatValidationReport } from '../../training/validator.js';
|
|
17
|
+
import { loadKnowledgeGraph } from '../../knowledge/kg_bridge.js';
|
|
15
18
|
class TrainError extends Error {
|
|
16
19
|
constructor(message) {
|
|
17
20
|
super(message);
|
|
@@ -121,24 +124,6 @@ function ask(rl, question, defaultValue) {
|
|
|
121
124
|
});
|
|
122
125
|
});
|
|
123
126
|
}
|
|
124
|
-
function serializeManifest(manifest) {
|
|
125
|
-
const output = {
|
|
126
|
-
families: manifest.families.map((f) => {
|
|
127
|
-
// Remove undefined/empty optional fields for clean JSON
|
|
128
|
-
const cleaned = { ...f };
|
|
129
|
-
const optionalArrays = ['pageObjects', 'components', 'webappPaths', 'serverPaths', 'specDirs', 'cypressSpecDirs', 'tags', 'userFlows', 'features'];
|
|
130
|
-
for (const key of optionalArrays) {
|
|
131
|
-
if (!cleaned[key] || (Array.isArray(cleaned[key]) && cleaned[key].length === 0)) {
|
|
132
|
-
delete cleaned[key];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (!cleaned.priority)
|
|
136
|
-
delete cleaned.priority;
|
|
137
|
-
return cleaned;
|
|
138
|
-
}),
|
|
139
|
-
};
|
|
140
|
-
return JSON.stringify(output, null, 2) + '\n';
|
|
141
|
-
}
|
|
142
127
|
export async function runTrainCommand(args, autoConfig) {
|
|
143
128
|
const opts = resolveTrainOptions(args, autoConfig);
|
|
144
129
|
const totalTimer = logger.timer('train-total');
|
|
@@ -151,12 +136,22 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
151
136
|
logger.info('e2e-ai-agents train');
|
|
152
137
|
logger.info('===================');
|
|
153
138
|
// ---------- Phase 1: Deterministic scan ----------
|
|
139
|
+
// Prefer knowledge graph when available
|
|
140
|
+
const kg = loadKnowledgeGraph(opts.appPath);
|
|
154
141
|
logger.info('Scanning project structure...');
|
|
155
142
|
if (opts.serverRoot) {
|
|
156
143
|
logger.info(`Server root: ${opts.serverRoot}`);
|
|
157
144
|
}
|
|
158
145
|
const scanTimer = logger.timer('scan');
|
|
159
|
-
|
|
146
|
+
let scanResult;
|
|
147
|
+
if (kg) {
|
|
148
|
+
logger.info('Using knowledge graph for scanning (found .understand-anything/knowledge-graph.json)');
|
|
149
|
+
scanResult = scanFromKnowledgeGraph(kg);
|
|
150
|
+
logger.info(`KG: ${kg.nodes.length} nodes, ${kg.edges.length} edges`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
scanResult = scanProject(opts.appPath, opts.testsRoot !== opts.appPath ? opts.testsRoot : undefined, opts.serverRoot, opts.gitRepoRoot);
|
|
154
|
+
}
|
|
160
155
|
timings.scan = scanTimer.end();
|
|
161
156
|
logger.info(`Found ${scanResult.stats.totalSourceFiles} source files, ${scanResult.stats.totalTestFiles} test files`);
|
|
162
157
|
logger.info(`Discovered ${scanResult.families.length} candidate families`);
|
|
@@ -332,7 +327,7 @@ export async function runTrainCommand(args, autoConfig) {
|
|
|
332
327
|
const reportDir = dirname(opts.outputPath);
|
|
333
328
|
const trainReport = {
|
|
334
329
|
timestamp: new Date().toISOString(),
|
|
335
|
-
version:
|
|
330
|
+
version: getVersion(),
|
|
336
331
|
timings,
|
|
337
332
|
families: {
|
|
338
333
|
total: mergeResult.manifest.families.length,
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
+
// See LICENSE.txt for license information.
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* Detect the test framework from package.json dependencies.
|
|
8
|
+
*/
|
|
9
|
+
export function detectFramework(appPath) {
|
|
10
|
+
const resolvedPath = resolve(appPath);
|
|
11
|
+
const pkgPath = join(resolvedPath, 'package.json');
|
|
12
|
+
if (!existsSync(pkgPath)) {
|
|
13
|
+
return 'auto';
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
18
|
+
if (allDeps['@playwright/test'] || allDeps.playwright) {
|
|
19
|
+
return 'playwright';
|
|
20
|
+
}
|
|
21
|
+
if (allDeps.cypress) {
|
|
22
|
+
return 'cypress';
|
|
23
|
+
}
|
|
24
|
+
if (allDeps['selenium-webdriver'] || allDeps.webdriverio) {
|
|
25
|
+
return 'selenium';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// ignore malformed package.json
|
|
30
|
+
}
|
|
31
|
+
return 'auto';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detect the tests root directory by scanning common conventions.
|
|
35
|
+
*/
|
|
36
|
+
export function detectTestsRoot(appPath) {
|
|
37
|
+
const resolvedPath = resolve(appPath);
|
|
38
|
+
const candidates = [
|
|
39
|
+
'e2e-tests/playwright',
|
|
40
|
+
'e2e-tests',
|
|
41
|
+
'e2e',
|
|
42
|
+
'tests/e2e',
|
|
43
|
+
'test/e2e',
|
|
44
|
+
'tests',
|
|
45
|
+
'test',
|
|
46
|
+
'specs',
|
|
47
|
+
'playwright',
|
|
48
|
+
'cypress',
|
|
49
|
+
];
|
|
50
|
+
for (const candidate of candidates) {
|
|
51
|
+
if (existsSync(join(resolvedPath, candidate))) {
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Detect the git default branch for diffing.
|
|
59
|
+
* Returns origin/<branch> format.
|
|
60
|
+
*/
|
|
61
|
+
export function detectGitDefaultBranch(appPath) {
|
|
62
|
+
try {
|
|
63
|
+
// Try to find the remote HEAD branch first
|
|
64
|
+
const remoteInfo = execFileSync('git', ['remote', 'show', 'origin'], {
|
|
65
|
+
cwd: resolve(appPath),
|
|
66
|
+
encoding: 'utf-8',
|
|
67
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
68
|
+
timeout: 5000,
|
|
69
|
+
});
|
|
70
|
+
const headMatch = remoteInfo.match(/HEAD branch:\s*(.+)/);
|
|
71
|
+
if (headMatch) {
|
|
72
|
+
return `origin/${headMatch[1].trim()}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// fallback to current branch
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
80
|
+
cwd: resolve(appPath),
|
|
81
|
+
encoding: 'utf-8',
|
|
82
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
|
+
timeout: 5000,
|
|
84
|
+
}).trim();
|
|
85
|
+
return `origin/${result}`;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return 'origin/main';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Detect the project root by walking up to find package.json or .git.
|
|
93
|
+
*/
|
|
94
|
+
export function detectProjectRoot(startDir) {
|
|
95
|
+
let current = resolve(startDir);
|
|
96
|
+
while (true) {
|
|
97
|
+
if (existsSync(join(current, 'package.json')) || existsSync(join(current, '.git'))) {
|
|
98
|
+
return current;
|
|
99
|
+
}
|
|
100
|
+
const parent = resolve(current, '..');
|
|
101
|
+
if (parent === current) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
current = parent;
|
|
105
|
+
}
|
|
106
|
+
return startDir;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve defaults for CLI commands that need path/testsRoot/framework/since.
|
|
110
|
+
* Explicit values from CLI flags take precedence over detected values.
|
|
111
|
+
*/
|
|
112
|
+
export function resolveDefaults(explicit) {
|
|
113
|
+
const path = explicit.path || detectProjectRoot(process.cwd());
|
|
114
|
+
const testsRoot = explicit.testsRoot || detectTestsRoot(path) || '.';
|
|
115
|
+
const framework = explicit.framework || detectFramework(path);
|
|
116
|
+
const since = explicit.gitSince || detectGitDefaultBranch(path);
|
|
117
|
+
return { path, testsRoot, framework, since };
|
|
118
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
+
// See LICENSE.txt for license information.
|
|
3
|
+
/**
|
|
4
|
+
* CLI Error types with structured exit codes.
|
|
5
|
+
*
|
|
6
|
+
* Exit codes:
|
|
7
|
+
* 0 = success
|
|
8
|
+
* 1 = general/user error (bad args, missing config, invalid input)
|
|
9
|
+
* 2 = budget exceeded
|
|
10
|
+
* 3 = LLM provider unavailable (API down, auth failure)
|
|
11
|
+
* 4 = invalid manifest or config file
|
|
12
|
+
*/
|
|
13
|
+
export const EXIT_CODES = {
|
|
14
|
+
SUCCESS: 0,
|
|
15
|
+
GENERAL_ERROR: 1,
|
|
16
|
+
BUDGET_EXCEEDED: 2,
|
|
17
|
+
PROVIDER_UNAVAILABLE: 3,
|
|
18
|
+
INVALID_CONFIG: 4,
|
|
19
|
+
};
|
|
20
|
+
export class CliError extends Error {
|
|
21
|
+
constructor(message, exitCode = EXIT_CODES.GENERAL_ERROR) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.exitCode = exitCode;
|
|
24
|
+
this.name = 'CliError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Classify an unknown error into the appropriate exit code.
|
|
29
|
+
*/
|
|
30
|
+
export function classifyError(error) {
|
|
31
|
+
if (error instanceof CliError)
|
|
32
|
+
return error.exitCode;
|
|
33
|
+
const msg = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
34
|
+
// Budget errors
|
|
35
|
+
if (msg.includes('budget exceeded') || msg.includes('budget limit')) {
|
|
36
|
+
return EXIT_CODES.BUDGET_EXCEEDED;
|
|
37
|
+
}
|
|
38
|
+
// Provider/auth errors
|
|
39
|
+
if (msg.includes('api key') || msg.includes('authentication') ||
|
|
40
|
+
msg.includes('unauthorized') || msg.includes('403') ||
|
|
41
|
+
(msg.includes('provider') && msg.includes('unavailable')) ||
|
|
42
|
+
msg.includes('econnrefused') || msg.includes('econnreset')) {
|
|
43
|
+
return EXIT_CODES.PROVIDER_UNAVAILABLE;
|
|
44
|
+
}
|
|
45
|
+
// Config/manifest errors
|
|
46
|
+
if ((msg.includes('manifest') && (msg.includes('invalid') || msg.includes('not found') || msg.includes('parse'))) ||
|
|
47
|
+
(msg.includes('config') && msg.includes('invalid')) ||
|
|
48
|
+
(msg.includes('route-families') && msg.includes('invalid'))) {
|
|
49
|
+
return EXIT_CODES.INVALID_CONFIG;
|
|
50
|
+
}
|
|
51
|
+
return EXIT_CODES.GENERAL_ERROR;
|
|
52
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { dirname, join, resolve } from 'path';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
5
6
|
export const CONFIG_CANDIDATES = ['e2e-ai-agents.config.json', '.e2e-ai-agents.config.json'];
|
|
6
7
|
export function findConfigUpwards(startDir) {
|
|
7
8
|
if (!startDir) {
|
|
@@ -62,6 +63,7 @@ const FLAGS = {
|
|
|
62
63
|
'--generate': { key: 'analyzeGenerate', type: 'boolean' },
|
|
63
64
|
'--heal': { key: 'analyzeHeal', type: 'boolean' },
|
|
64
65
|
'--no-ai': { key: 'noAi', type: 'boolean' },
|
|
66
|
+
'--degraded-mode': { key: 'degradedMode', type: 'boolean' },
|
|
65
67
|
'--enrich': { key: 'trainEnrich', type: 'boolean' },
|
|
66
68
|
'--no-enrich': { key: 'trainEnrich', type: 'boolean-false' },
|
|
67
69
|
'--validate': { key: 'trainValidate', type: 'boolean' },
|
|
@@ -131,6 +133,13 @@ const FLAGS = {
|
|
|
131
133
|
type: 'csv',
|
|
132
134
|
transform: (v) => csvSplit(v).filter((s) => s === 'run-now' || s === 'must-add-tests' || s === 'safe-to-merge'),
|
|
133
135
|
},
|
|
136
|
+
// -- gate command --
|
|
137
|
+
'--threshold': { key: 'gateThreshold', type: 'number' },
|
|
138
|
+
// -- bootstrap command --
|
|
139
|
+
'--kg-path': { key: 'bootstrapKgPath', type: 'string' },
|
|
140
|
+
'--scaffold-framework': { key: 'bootstrapScaffoldFramework', type: 'boolean' },
|
|
141
|
+
'--test-mode': { key: 'bootstrapTestMode', type: 'enum', enumValues: ['ui', 'api', 'both'] },
|
|
142
|
+
'--max-families': { key: 'bootstrapMaxFamilies', type: 'number' },
|
|
134
143
|
};
|
|
135
144
|
// Build a lookup from alias -> canonical flag name
|
|
136
145
|
const ALIAS_MAP = {};
|
|
@@ -146,7 +155,8 @@ const COMMANDS = new Set([
|
|
|
146
155
|
'init', 'impact', 'plan', 'heal', 'suggest', 'generate',
|
|
147
156
|
'finalize-generated-tests', 'feedback',
|
|
148
157
|
'traceability-capture', 'traceability-ingest',
|
|
149
|
-
'analyze', 'llm-health', 'train', 'crew',
|
|
158
|
+
'analyze', 'llm-health', 'train', 'crew', 'cost-report', 'gate',
|
|
159
|
+
'bootstrap',
|
|
150
160
|
]);
|
|
151
161
|
// ---------------------------------------------------------------------------
|
|
152
162
|
// Parser
|
|
@@ -168,6 +178,9 @@ export function parseArgs(argv) {
|
|
|
168
178
|
const arg = argv[i];
|
|
169
179
|
const canonical = ALIAS_MAP[arg];
|
|
170
180
|
if (!canonical) {
|
|
181
|
+
if (arg.startsWith('--')) {
|
|
182
|
+
logger.warn(`Unknown flag "${arg}" (ignored)`);
|
|
183
|
+
}
|
|
171
184
|
continue;
|
|
172
185
|
}
|
|
173
186
|
const def = FLAGS[canonical];
|
|
@@ -196,7 +209,16 @@ export function parseArgs(argv) {
|
|
|
196
209
|
break;
|
|
197
210
|
case 'number-raw':
|
|
198
211
|
if (next) {
|
|
199
|
-
|
|
212
|
+
const rawValue = def.transform ? def.transform(next) : Number(next);
|
|
213
|
+
// Allow non-number transforms through; reject NaN/Infinity for numbers
|
|
214
|
+
if (typeof rawValue === 'number') {
|
|
215
|
+
if (Number.isFinite(rawValue)) {
|
|
216
|
+
setField(parsed, def.key, rawValue);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
setField(parsed, def.key, rawValue);
|
|
221
|
+
}
|
|
200
222
|
i += 1;
|
|
201
223
|
}
|
|
202
224
|
break;
|
package/dist/esm/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// See LICENSE.txt for license information.
|
|
4
4
|
import { resolveConfig } from './agent/config.js';
|
|
5
5
|
import { parseArgs, resolveAutoConfig } from './cli/parse_args.js';
|
|
6
|
+
import { resolveDefaults } from './cli/defaults.js';
|
|
6
7
|
import { printUsage } from './cli/usage.js';
|
|
7
8
|
import { runLlmHealth } from './cli/commands/llm_health.js';
|
|
8
9
|
import { runAnalyzeCommand } from './cli/commands/analyze.js';
|
|
@@ -16,14 +17,42 @@ import { runGenerateCommand } from './cli/commands/generate.js';
|
|
|
16
17
|
import { runInitCommand } from './cli/commands/init.js';
|
|
17
18
|
import { runTrainCommand } from './cli/commands/train.js';
|
|
18
19
|
import { runCrewCommand } from './cli/commands/crew.js';
|
|
20
|
+
import { runCostReportCommand } from './cli/commands/cost_report.js';
|
|
21
|
+
import { runGateCommand } from './cli/commands/gate.js';
|
|
22
|
+
import { runBootstrapCommand } from './cli/commands/bootstrap.js';
|
|
23
|
+
import { classifyError, EXIT_CODES } from './cli/errors.js';
|
|
24
|
+
// Commands that skip default resolution (they handle their own setup)
|
|
25
|
+
const SKIP_DEFAULTS_COMMANDS = new Set(['init', 'llm-health', 'cost-report', 'bootstrap']);
|
|
26
|
+
// Commands that need path/testsRoot/framework/since
|
|
27
|
+
const NEEDS_DEFAULTS_COMMANDS = new Set([
|
|
28
|
+
'impact', 'plan', 'suggest', 'crew', 'generate', 'heal', 'analyze', 'train',
|
|
29
|
+
'feedback', 'traceability-capture', 'traceability-ingest', 'finalize-generated-tests',
|
|
30
|
+
]);
|
|
19
31
|
async function main() {
|
|
20
32
|
const args = parseArgs(process.argv.slice(2));
|
|
21
33
|
const autoConfig = resolveAutoConfig(args);
|
|
34
|
+
// Auto-detect defaults for commands that need them (when no config file found)
|
|
35
|
+
if (args.command && NEEDS_DEFAULTS_COMMANDS.has(args.command) && !SKIP_DEFAULTS_COMMANDS.has(args.command)) {
|
|
36
|
+
const defaults = resolveDefaults({
|
|
37
|
+
path: args.path,
|
|
38
|
+
testsRoot: args.testsRoot,
|
|
39
|
+
framework: args.framework,
|
|
40
|
+
gitSince: args.gitSince,
|
|
41
|
+
});
|
|
42
|
+
args.path = args.path || defaults.path;
|
|
43
|
+
args.testsRoot = args.testsRoot || defaults.testsRoot;
|
|
44
|
+
args.framework = args.framework || defaults.framework;
|
|
45
|
+
args.gitSince = args.gitSince || defaults.since;
|
|
46
|
+
}
|
|
22
47
|
if (args.command === 'init') {
|
|
23
48
|
const hasYes = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
24
49
|
await runInitCommand(hasYes);
|
|
25
50
|
return;
|
|
26
51
|
}
|
|
52
|
+
if (args.command === 'bootstrap') {
|
|
53
|
+
await runBootstrapCommand(args);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
27
56
|
if (args.command === 'train') {
|
|
28
57
|
await runTrainCommand(args, autoConfig);
|
|
29
58
|
return;
|
|
@@ -64,6 +93,14 @@ async function main() {
|
|
|
64
93
|
await runCrewCommand(args, autoConfig);
|
|
65
94
|
return;
|
|
66
95
|
}
|
|
96
|
+
if (args.command === 'cost-report') {
|
|
97
|
+
runCostReportCommand(args);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (args.command === 'gate') {
|
|
101
|
+
await runGateCommand(args, autoConfig);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
67
104
|
if (!args.path && !autoConfig) {
|
|
68
105
|
console.error('Error: --path is required (or provide a config file with path set)');
|
|
69
106
|
printUsage();
|
|
@@ -138,6 +175,14 @@ async function main() {
|
|
|
138
175
|
process.exit(1);
|
|
139
176
|
}
|
|
140
177
|
main().catch((error) => {
|
|
141
|
-
|
|
142
|
-
|
|
178
|
+
const exitCode = classifyError(error);
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
console.error(message);
|
|
181
|
+
if (exitCode === EXIT_CODES.BUDGET_EXCEEDED) {
|
|
182
|
+
console.error('Hint: Increase --budget or use --degraded-mode to skip AI features.');
|
|
183
|
+
}
|
|
184
|
+
else if (exitCode === EXIT_CODES.PROVIDER_UNAVAILABLE) {
|
|
185
|
+
console.error('Hint: Check API key or use --degraded-mode for deterministic analysis only.');
|
|
186
|
+
}
|
|
187
|
+
process.exit(exitCode);
|
|
143
188
|
});
|