@yasserkhanorg/e2e-agents 1.8.0 → 1.8.2
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 +55 -0
- package/dist/agent/plan.d.ts +29 -0
- package/dist/agent/plan.d.ts.map +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +15 -4
- package/dist/cli/commands/generate.js +1 -1
- package/dist/cli/commands/plan.d.ts.map +1 -1
- package/dist/cli/commands/plan.js +51 -18
- package/dist/cli/commands/plan_crew.d.ts +11 -0
- package/dist/cli/commands/plan_crew.d.ts.map +1 -0
- package/dist/cli/commands/plan_crew.js +149 -0
- package/dist/cli/parse_args.d.ts.map +1 -1
- package/dist/cli/parse_args.js +2 -0
- package/dist/cli/types.d.ts +1 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/usage.d.ts.map +1 -1
- package/dist/cli/usage.js +2 -0
- package/dist/crew/context.d.ts.map +1 -1
- package/dist/esm/api.js +15 -4
- package/dist/esm/cli/commands/generate.js +1 -1
- package/dist/esm/cli/commands/plan.js +52 -19
- package/dist/esm/cli/commands/plan_crew.js +143 -0
- package/dist/esm/cli/parse_args.js +2 -0
- package/dist/esm/cli/usage.js +2 -0
- package/dist/esm/knowledge/route_families.js +2 -2
- package/dist/esm/logger.js +1 -2
- package/dist/esm/mcp-server.js +147 -6
- package/dist/esm/ollama_provider.js +1 -1
- package/dist/esm/provider_factory.js +17 -10
- package/dist/esm/training/enricher.js +4 -3
- package/dist/esm/training/validator.js +2 -1
- package/dist/knowledge/route_families.d.ts.map +1 -1
- package/dist/knowledge/route_families.js +2 -2
- package/dist/logger.d.ts +1 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -2
- package/dist/mcp-server.d.ts +33 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +150 -5
- package/dist/ollama_provider.js +1 -1
- package/dist/provider_factory.d.ts +5 -0
- package/dist/provider_factory.d.ts.map +1 -1
- package/dist/provider_factory.js +17 -10
- package/dist/training/enricher.d.ts.map +1 -1
- package/dist/training/enricher.js +4 -3
- package/dist/training/validator.d.ts.map +1 -1
- package/dist/training/validator.js +2 -1
- package/package.json +4 -3
- package/schemas/plan.schema.json +158 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
|
-
import { appendFileSync } from 'fs';
|
|
3
|
+
import { appendFileSync, writeFileSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { writeCiSummary } from '../../engine/plan_builder.js';
|
|
6
6
|
import { recommendTestsAI, recommendTestsDeterministic } from '../../api.js';
|
|
7
|
+
import { appendCrewToSummary, runPlanCrewAnalysis, writeCrewArtifacts } from './plan_crew.js';
|
|
7
8
|
export async function runPlanCommand(args, autoConfig, config) {
|
|
8
9
|
const reportRoot = config.testsRoot || config.path;
|
|
9
10
|
const apiOptions = {
|
|
@@ -40,41 +41,73 @@ export async function runPlanCommand(args, autoConfig, config) {
|
|
|
40
41
|
const { aiEnrichment } = result;
|
|
41
42
|
console.log(`AI enrichment: ${aiEnrichment.enrichedFeatures.length} features enriched (${aiEnrichment.tokenUsage.input + aiEnrichment.tokenUsage.output} tokens)`);
|
|
42
43
|
}
|
|
43
|
-
else if (!process.env.ANTHROPIC_API_KEY) {
|
|
44
|
-
console.log('Tip:
|
|
44
|
+
else if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY && !process.env.LLM_PROVIDER) {
|
|
45
|
+
console.log('Tip: configure ANTHROPIC_API_KEY, OPENAI_API_KEY, or LLM_PROVIDER to enable AI-powered enrichment');
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
const { plan, planPath, ciSummaryMarkdown, ciSummaryPath } = result;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
let planReport = plan;
|
|
50
|
+
let combinedSummaryMarkdown = ciSummaryMarkdown;
|
|
51
|
+
let crewSummaryPath = '';
|
|
52
|
+
let crewMarkdownPath = '';
|
|
53
|
+
if (args.crew) {
|
|
54
|
+
try {
|
|
55
|
+
const crew = await runPlanCrewAnalysis(plan, config, args);
|
|
56
|
+
planReport = {
|
|
57
|
+
...plan,
|
|
58
|
+
crew,
|
|
59
|
+
};
|
|
60
|
+
combinedSummaryMarkdown = appendCrewToSummary(ciSummaryMarkdown, crew);
|
|
61
|
+
const artifacts = writeCrewArtifacts(reportRoot, crew);
|
|
62
|
+
crewSummaryPath = artifacts.crewSummaryPath;
|
|
63
|
+
crewMarkdownPath = artifacts.crewMarkdownPath;
|
|
64
|
+
writeFileSync(planPath, JSON.stringify(planReport, null, 2), 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
console.warn(`Crew analysis unavailable: ${message}`);
|
|
69
|
+
}
|
|
51
70
|
}
|
|
52
|
-
|
|
71
|
+
writeCiSummary(reportRoot, combinedSummaryMarkdown);
|
|
72
|
+
const summaryPath = args.ciCommentPath
|
|
73
|
+
? writeCiSummary(reportRoot, combinedSummaryMarkdown, args.ciCommentPath)
|
|
74
|
+
: ciSummaryPath;
|
|
53
75
|
// Compute metrics paths (api already wrote metrics; derive paths for GHA output)
|
|
54
76
|
const metricsEventsPath = join(reportRoot, '.e2e-ai-agents/metrics.jsonl');
|
|
55
77
|
const metricsSummaryPath = join(reportRoot, '.e2e-ai-agents/metrics-summary.json');
|
|
56
78
|
const ghaOutput = args.githubOutputPath || process.env.GITHUB_OUTPUT;
|
|
57
79
|
if (ghaOutput) {
|
|
58
|
-
appendFileSync(ghaOutput, `run_set=${
|
|
59
|
-
appendFileSync(ghaOutput, `action=${
|
|
60
|
-
appendFileSync(ghaOutput, `confidence=${
|
|
61
|
-
appendFileSync(ghaOutput, `enforcement_mode=${
|
|
62
|
-
appendFileSync(ghaOutput, `enforcement_should_fail=${
|
|
63
|
-
appendFileSync(ghaOutput, `recommended_tests_count=${
|
|
64
|
-
appendFileSync(ghaOutput, `required_new_tests_count=${
|
|
80
|
+
appendFileSync(ghaOutput, `run_set=${planReport.runSet}\n`);
|
|
81
|
+
appendFileSync(ghaOutput, `action=${planReport.decision.action}\n`);
|
|
82
|
+
appendFileSync(ghaOutput, `confidence=${planReport.confidence}\n`);
|
|
83
|
+
appendFileSync(ghaOutput, `enforcement_mode=${planReport.enforcement.mode}\n`);
|
|
84
|
+
appendFileSync(ghaOutput, `enforcement_should_fail=${planReport.enforcement.shouldFail}\n`);
|
|
85
|
+
appendFileSync(ghaOutput, `recommended_tests_count=${planReport.recommendedTests.length}\n`);
|
|
86
|
+
appendFileSync(ghaOutput, `required_new_tests_count=${planReport.requiredNewTests.length}\n`);
|
|
65
87
|
appendFileSync(ghaOutput, `plan_path=${planPath}\n`);
|
|
66
88
|
appendFileSync(ghaOutput, `summary_path=${summaryPath}\n`);
|
|
67
89
|
appendFileSync(ghaOutput, `metrics_events_path=${metricsEventsPath}\n`);
|
|
68
90
|
appendFileSync(ghaOutput, `metrics_summary_path=${metricsSummaryPath}\n`);
|
|
91
|
+
appendFileSync(ghaOutput, `crew_enabled=${planReport.crew ? 'true' : 'false'}\n`);
|
|
92
|
+
appendFileSync(ghaOutput, `crew_workflow=${planReport.crew?.workflow || ''}\n`);
|
|
93
|
+
appendFileSync(ghaOutput, `crew_summary_path=${crewSummaryPath}\n`);
|
|
94
|
+
appendFileSync(ghaOutput, `crew_markdown_path=${crewMarkdownPath}\n`);
|
|
95
|
+
appendFileSync(ghaOutput, `crew_impacted_flows=${planReport.crew?.summary.impactedFlows || 0}\n`);
|
|
96
|
+
appendFileSync(ghaOutput, `crew_strategy_entries=${planReport.crew?.summary.strategyEntries || 0}\n`);
|
|
97
|
+
appendFileSync(ghaOutput, `crew_test_designs=${planReport.crew?.summary.testDesigns || 0}\n`);
|
|
69
98
|
}
|
|
70
|
-
console.log(`Suggested run set: ${
|
|
71
|
-
console.log(`Decision: ${
|
|
72
|
-
console.log(`Enforcement: ${
|
|
99
|
+
console.log(`Suggested run set: ${planReport.runSet} (confidence ${planReport.confidence})`);
|
|
100
|
+
console.log(`Decision: ${planReport.decision.action} - ${planReport.decision.summary}`);
|
|
101
|
+
console.log(`Enforcement: ${planReport.enforcement.mode} (shouldFail=${planReport.enforcement.shouldFail})`);
|
|
73
102
|
console.log(`Plan data: ${planPath}`);
|
|
74
103
|
console.log(`CI summary: ${summaryPath}`);
|
|
104
|
+
if (planReport.crew) {
|
|
105
|
+
console.log(`Crew workflow: ${planReport.crew.workflow} (impactedFlows=${planReport.crew.summary.impactedFlows}, strategyEntries=${planReport.crew.summary.strategyEntries}, testDesigns=${planReport.crew.summary.testDesigns})`);
|
|
106
|
+
console.log(`Crew summary: ${crewSummaryPath}`);
|
|
107
|
+
}
|
|
75
108
|
console.log(`Plan metrics: ${metricsSummaryPath}`);
|
|
76
|
-
const failOnLegacyFlag = args.failOnMustAddTests &&
|
|
77
|
-
if (failOnLegacyFlag ||
|
|
109
|
+
const failOnLegacyFlag = args.failOnMustAddTests && planReport.decision.action === 'must-add-tests';
|
|
110
|
+
if (failOnLegacyFlag || planReport.enforcement.shouldFail) {
|
|
78
111
|
process.exit(2);
|
|
79
112
|
}
|
|
80
113
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
+
// See LICENSE.txt for license information.
|
|
3
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { CrossImpactAgent } from '../../agents/cross-impact.js';
|
|
6
|
+
import { ImpactAnalystAgent } from '../../agents/impact-analyst.js';
|
|
7
|
+
import { RegressionAdvisorAgent } from '../../agents/regression-advisor.js';
|
|
8
|
+
import { StrategistAgent } from '../../agents/strategist.js';
|
|
9
|
+
import { TestDesignerAgent } from '../../agents/test-designer.js';
|
|
10
|
+
import { CrewOrchestrator } from '../../crew/orchestrator.js';
|
|
11
|
+
const VALID_WORKFLOWS = new Set(['full-qa', 'quick-check', 'design-only']);
|
|
12
|
+
function uniqueStrings(values) {
|
|
13
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
14
|
+
}
|
|
15
|
+
function singleLine(value) {
|
|
16
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
17
|
+
}
|
|
18
|
+
function chooseCrewWorkflow(explicitWorkflow, plan) {
|
|
19
|
+
if (explicitWorkflow && VALID_WORKFLOWS.has(explicitWorkflow)) {
|
|
20
|
+
return explicitWorkflow;
|
|
21
|
+
}
|
|
22
|
+
if (plan.decision.action === 'must-add-tests' || plan.metrics.uncoveredP0P1Flows > 0) {
|
|
23
|
+
return 'design-only';
|
|
24
|
+
}
|
|
25
|
+
return 'quick-check';
|
|
26
|
+
}
|
|
27
|
+
function registerCrewAgents(orchestrator) {
|
|
28
|
+
orchestrator.registerAgent(new ImpactAnalystAgent());
|
|
29
|
+
orchestrator.registerAgent(new StrategistAgent());
|
|
30
|
+
orchestrator.registerAgent(new TestDesignerAgent());
|
|
31
|
+
orchestrator.registerAgent(new CrossImpactAgent());
|
|
32
|
+
orchestrator.registerAgent(new RegressionAdvisorAgent());
|
|
33
|
+
}
|
|
34
|
+
export async function runPlanCrewAnalysis(plan, config, args) {
|
|
35
|
+
const reportRoot = config.testsRoot || config.path;
|
|
36
|
+
const workflow = chooseCrewWorkflow(args.crewWorkflow, plan);
|
|
37
|
+
const normalizedProvider = config.llm.provider?.trim().toLowerCase();
|
|
38
|
+
const providerOverride = normalizedProvider && normalizedProvider !== 'auto' ? normalizedProvider : 'auto';
|
|
39
|
+
const orchestrator = new CrewOrchestrator();
|
|
40
|
+
registerCrewAgents(orchestrator);
|
|
41
|
+
const result = await orchestrator.run({
|
|
42
|
+
appPath: config.path,
|
|
43
|
+
testsRoot: reportRoot,
|
|
44
|
+
gitSince: args.gitSince || config.git.since,
|
|
45
|
+
routeFamilies: config.routeFamilies,
|
|
46
|
+
apiSurface: config.apiSurface,
|
|
47
|
+
workflow,
|
|
48
|
+
providerOverride: providerOverride === 'auto' ? undefined : providerOverride,
|
|
49
|
+
budgetUSD: args.budgetUSD,
|
|
50
|
+
dryRun: args.dryRun,
|
|
51
|
+
});
|
|
52
|
+
const ctx = result.context;
|
|
53
|
+
const highRiskCrossImpacts = ctx.crossImpacts.filter((entry) => entry.riskLevel === 'high');
|
|
54
|
+
const manualReviewEntries = ctx.strategyEntries.filter((entry) => entry.approach === 'manual-review');
|
|
55
|
+
return {
|
|
56
|
+
workflow,
|
|
57
|
+
providerOverride,
|
|
58
|
+
summary: {
|
|
59
|
+
impactedFlows: ctx.impactedFlows.length,
|
|
60
|
+
strategyEntries: ctx.strategyEntries.length,
|
|
61
|
+
testDesigns: ctx.testDesigns.length,
|
|
62
|
+
crossImpacts: ctx.crossImpacts.length,
|
|
63
|
+
highRiskCrossImpacts: highRiskCrossImpacts.length,
|
|
64
|
+
regressionRisks: ctx.regressionRisks.length,
|
|
65
|
+
findings: ctx.findings.length,
|
|
66
|
+
generatedSpecs: ctx.generatedSpecs.length,
|
|
67
|
+
manualReviewEntries: manualReviewEntries.length,
|
|
68
|
+
totalCostUSD: Number(ctx.usage.totalCost.toFixed(4)),
|
|
69
|
+
totalTokens: ctx.usage.totalTokens,
|
|
70
|
+
},
|
|
71
|
+
impactedFlows: ctx.impactedFlows,
|
|
72
|
+
strategyEntries: ctx.strategyEntries,
|
|
73
|
+
testDesigns: ctx.testDesigns,
|
|
74
|
+
crossImpacts: ctx.crossImpacts,
|
|
75
|
+
regressionRisks: ctx.regressionRisks,
|
|
76
|
+
findings: ctx.findings,
|
|
77
|
+
warnings: uniqueStrings([...ctx.warnings, ...result.warnings]),
|
|
78
|
+
timings: result.timings,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function buildCrewMarkdown(crew) {
|
|
82
|
+
const lines = [
|
|
83
|
+
'### Crew Insights',
|
|
84
|
+
'',
|
|
85
|
+
`Workflow: \`${crew.workflow}\``,
|
|
86
|
+
`Provider override: \`${crew.providerOverride}\``,
|
|
87
|
+
`Impacted flows: **${crew.summary.impactedFlows}**`,
|
|
88
|
+
`Strategy entries: **${crew.summary.strategyEntries}**`,
|
|
89
|
+
`Structured test designs: **${crew.summary.testDesigns}**`,
|
|
90
|
+
`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`,
|
|
91
|
+
`Findings: **${crew.summary.findings}**`,
|
|
92
|
+
`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`,
|
|
93
|
+
];
|
|
94
|
+
if (crew.strategyEntries.length > 0) {
|
|
95
|
+
lines.push('');
|
|
96
|
+
lines.push('Top Strategy Recommendations:');
|
|
97
|
+
for (const entry of crew.strategyEntries.slice(0, 5)) {
|
|
98
|
+
lines.push(`- ${entry.priority} ${entry.flowName} -> ${entry.approach} (${entry.crossImpactRisk} cross-impact risk)`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (crew.testDesigns.length > 0) {
|
|
102
|
+
lines.push('');
|
|
103
|
+
lines.push('Structured Test Designs:');
|
|
104
|
+
for (const design of crew.testDesigns.slice(0, 3)) {
|
|
105
|
+
lines.push(`- ${design.flowName}: ${design.testCases.length} designed test case(s)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const riskyCrossImpacts = crew.crossImpacts.filter((entry) => entry.riskLevel === 'high');
|
|
109
|
+
if (riskyCrossImpacts.length > 0) {
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push('High-Risk Cross-Impacts:');
|
|
112
|
+
for (const entry of riskyCrossImpacts.slice(0, 5)) {
|
|
113
|
+
lines.push(`- ${entry.sourceFamily} -> ${entry.affectedFamily}: ${entry.sharedDependency}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (crew.findings.length > 0) {
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push('Crew Findings:');
|
|
119
|
+
for (const finding of crew.findings.slice(0, 5)) {
|
|
120
|
+
lines.push(`- ${finding.severity} ${finding.type}: ${finding.summary}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (crew.warnings.length > 0) {
|
|
124
|
+
lines.push('');
|
|
125
|
+
lines.push('Crew Warnings:');
|
|
126
|
+
for (const warning of crew.warnings.slice(0, 5)) {
|
|
127
|
+
lines.push(`- ${singleLine(warning)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
export function appendCrewToSummary(baseMarkdown, crew) {
|
|
133
|
+
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
|
|
134
|
+
}
|
|
135
|
+
export function writeCrewArtifacts(reportRoot, crew) {
|
|
136
|
+
const outputDir = join(reportRoot, '.e2e-ai-agents');
|
|
137
|
+
mkdirSync(outputDir, { recursive: true });
|
|
138
|
+
const crewSummaryPath = join(outputDir, 'crew-summary.json');
|
|
139
|
+
const crewMarkdownPath = join(outputDir, 'crew-summary.md');
|
|
140
|
+
writeFileSync(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
|
|
141
|
+
writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
|
|
142
|
+
return { crewSummaryPath, crewMarkdownPath };
|
|
143
|
+
}
|
|
@@ -56,6 +56,7 @@ const FLAGS = {
|
|
|
56
56
|
'--pipeline-parallel': { key: 'pipelineParallel', type: 'boolean' },
|
|
57
57
|
'--pipeline-dry-run': { key: 'pipelineDryRun', type: 'boolean' },
|
|
58
58
|
'--fail-on-must-add-tests': { key: 'failOnMustAddTests', type: 'boolean' },
|
|
59
|
+
'--crew': { key: 'crew', type: 'boolean' },
|
|
59
60
|
'--create-pr': { key: 'createPr', type: 'boolean' },
|
|
60
61
|
'--dry-run': { key: 'dryRun', type: 'boolean' },
|
|
61
62
|
'--generate': { key: 'analyzeGenerate', type: 'boolean' },
|
|
@@ -99,6 +100,7 @@ const FLAGS = {
|
|
|
99
100
|
'--output': { key: 'trainOutput', type: 'string' },
|
|
100
101
|
'--server-path': { key: 'serverPath', type: 'string' },
|
|
101
102
|
'--workflow': { key: 'crewWorkflow', type: 'string' },
|
|
103
|
+
'--crew-workflow': { key: 'crewWorkflow', type: 'string' },
|
|
102
104
|
// -- number flags (with isFinite guard) --
|
|
103
105
|
'--pipeline-scenarios': { key: 'pipelineScenarios', type: 'number' },
|
|
104
106
|
'--time': { key: 'timeLimitMinutes', type: 'number' },
|
package/dist/esm/cli/usage.js
CHANGED
|
@@ -56,6 +56,8 @@ export function printUsage() {
|
|
|
56
56
|
' --policy-risky-patterns <globs> Comma-separated risky file globs',
|
|
57
57
|
' --policy-enforcement-mode <mode> advisory | warn | block',
|
|
58
58
|
' --policy-block-actions <actions> Comma-separated CI actions to block/warn',
|
|
59
|
+
' --crew Run Crew enrichment and attach insights to plan output',
|
|
60
|
+
' --crew-workflow <name> full-qa | quick-check | design-only',
|
|
59
61
|
' --ci-comment-path <path> Write CI markdown summary',
|
|
60
62
|
' --github-output <path> Write GitHub Actions outputs',
|
|
61
63
|
' --fail-on-must-add-tests Exit non-zero on must-add-tests decision',
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
3
|
import { existsSync, readFileSync, statSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
5
6
|
const manifestCache = new Map();
|
|
6
7
|
export function matchesGlob(filePath, pattern) {
|
|
7
8
|
const normalized = filePath.replace(/\\/g, '/');
|
|
@@ -154,8 +155,7 @@ export function loadRouteFamilyManifest(testsRoot, config) {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
if (config?.strict) {
|
|
157
|
-
|
|
158
|
-
console.warn('[e2e-agents] Route family manifest not found. The manifest is optional context for AI enrichment — create .e2e-ai-agents/route-families.json to enable family-level routing hints.');
|
|
158
|
+
logger.warn('Route family manifest not found. Create .e2e-ai-agents/route-families.json to enable family-level routing hints.');
|
|
159
159
|
}
|
|
160
160
|
return null;
|
|
161
161
|
}
|
package/dist/esm/logger.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Replaces 18 console.log statements with configurable logging
|
|
4
|
+
* Structured logging system.
|
|
6
5
|
* Environment variable: LOG_LEVEL (ERROR, WARN, INFO, DEBUG)
|
|
7
6
|
*/
|
|
8
7
|
export var LogLevel;
|
package/dist/esm/mcp-server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
3
|
// See LICENSE.txt for license information.
|
|
3
4
|
/**
|
|
@@ -6,7 +7,7 @@
|
|
|
6
7
|
*/
|
|
7
8
|
import { spawnSync } from 'child_process';
|
|
8
9
|
import { readFileSync, writeFileSync, existsSync, realpathSync } from 'fs';
|
|
9
|
-
import { resolve } from 'path';
|
|
10
|
+
import { join, resolve, dirname } from 'path';
|
|
10
11
|
import { globSync } from 'glob';
|
|
11
12
|
/**
|
|
12
13
|
* SECURITY: Path validation helper
|
|
@@ -471,12 +472,152 @@ export class E2EAgentsMCPServer {
|
|
|
471
472
|
}
|
|
472
473
|
}
|
|
473
474
|
/**
|
|
474
|
-
*
|
|
475
|
-
*
|
|
475
|
+
* Read the package version at runtime so the MCP initialize response
|
|
476
|
+
* always reflects the installed version.
|
|
476
477
|
*/
|
|
478
|
+
function getPackageVersion() {
|
|
479
|
+
try {
|
|
480
|
+
const pkgPath = join(dirname(__dirname), 'package.json');
|
|
481
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
482
|
+
return pkg.version || '0.0.0';
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return '0.0.0';
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Encode a JSON-RPC message with Content-Length framing.
|
|
490
|
+
* Exported for testability.
|
|
491
|
+
*/
|
|
492
|
+
export function encodeJsonRpcMessage(message) {
|
|
493
|
+
const body = JSON.stringify(message);
|
|
494
|
+
return `Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n${body}`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Parse Content-Length framed JSON-RPC messages from a buffer.
|
|
498
|
+
* Returns parsed messages and the remaining (unconsumed) buffer.
|
|
499
|
+
* Exported for testability.
|
|
500
|
+
*/
|
|
501
|
+
export function parseJsonRpcFrames(input) {
|
|
502
|
+
const messages = [];
|
|
503
|
+
let buffer = Buffer.from(input);
|
|
504
|
+
while (true) {
|
|
505
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
506
|
+
if (headerEnd === -1)
|
|
507
|
+
break;
|
|
508
|
+
const headerText = buffer.slice(0, headerEnd).toString('utf8');
|
|
509
|
+
const match = headerText.match(/Content-Length:\s*(\d+)/i);
|
|
510
|
+
if (!match) {
|
|
511
|
+
buffer = Buffer.alloc(0);
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
const contentLength = Number(match[1]);
|
|
515
|
+
const messageEnd = headerEnd + 4 + contentLength;
|
|
516
|
+
if (buffer.length < messageEnd)
|
|
517
|
+
break;
|
|
518
|
+
const body = buffer.slice(headerEnd + 4, messageEnd).toString('utf8');
|
|
519
|
+
buffer = buffer.slice(messageEnd);
|
|
520
|
+
messages.push(JSON.parse(body));
|
|
521
|
+
}
|
|
522
|
+
return { messages, remainder: buffer };
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Handle a single JSON-RPC message against the server.
|
|
526
|
+
* Returns the response message (or null for notifications).
|
|
527
|
+
* Exported for testability.
|
|
528
|
+
*/
|
|
529
|
+
export async function handleJsonRpcMessage(server, message) {
|
|
530
|
+
const { id, method, params } = message;
|
|
531
|
+
const version = getPackageVersion();
|
|
532
|
+
if (method === 'initialize') {
|
|
533
|
+
return {
|
|
534
|
+
jsonrpc: '2.0',
|
|
535
|
+
id,
|
|
536
|
+
result: {
|
|
537
|
+
protocolVersion: typeof params?.protocolVersion === 'string' ? params.protocolVersion : '2024-11-05',
|
|
538
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
539
|
+
serverInfo: { name: 'e2e-agents-mcp', version },
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
if (method === 'notifications/initialized' || method === 'initialized') {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
if (method === 'tools/list') {
|
|
547
|
+
return {
|
|
548
|
+
jsonrpc: '2.0',
|
|
549
|
+
id,
|
|
550
|
+
result: {
|
|
551
|
+
tools: server.getTools().map((tool) => ({
|
|
552
|
+
name: tool.name,
|
|
553
|
+
description: tool.description,
|
|
554
|
+
inputSchema: tool.inputSchema,
|
|
555
|
+
})),
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (method === 'tools/call') {
|
|
560
|
+
const resultText = await server.callTool(typeof params?.name === 'string' ? params.name : '', typeof params?.arguments === 'object' && params.arguments !== null ? params.arguments : {});
|
|
561
|
+
let isError = false;
|
|
562
|
+
try {
|
|
563
|
+
const parsed = JSON.parse(resultText);
|
|
564
|
+
isError = Boolean(parsed.error);
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
isError = false;
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
jsonrpc: '2.0',
|
|
571
|
+
id,
|
|
572
|
+
result: { content: [{ type: 'text', text: resultText }], isError },
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
if (method === 'resources/list') {
|
|
576
|
+
return { jsonrpc: '2.0', id, result: { resources: [] } };
|
|
577
|
+
}
|
|
578
|
+
if (method === 'prompts/list') {
|
|
579
|
+
return { jsonrpc: '2.0', id, result: { prompts: [] } };
|
|
580
|
+
}
|
|
581
|
+
if (method === 'ping') {
|
|
582
|
+
return { jsonrpc: '2.0', id, result: {} };
|
|
583
|
+
}
|
|
584
|
+
return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Start MCP server over stdio using Content-Length framed JSON-RPC messages.
|
|
588
|
+
*/
|
|
589
|
+
export function startStdioServer(repoRoot = process.cwd()) {
|
|
590
|
+
const server = new E2EAgentsMCPServer(repoRoot);
|
|
591
|
+
let buffer = Buffer.alloc(0);
|
|
592
|
+
const sendMessage = (message) => {
|
|
593
|
+
process.stdout.write(encodeJsonRpcMessage(message));
|
|
594
|
+
};
|
|
595
|
+
const sendError = (id, code, msg) => {
|
|
596
|
+
sendMessage({ jsonrpc: '2.0', id, error: { code, message: msg } });
|
|
597
|
+
};
|
|
598
|
+
const processBuffer = () => {
|
|
599
|
+
const { messages, remainder } = parseJsonRpcFrames(buffer);
|
|
600
|
+
buffer = remainder;
|
|
601
|
+
for (const parsed of messages) {
|
|
602
|
+
void handleJsonRpcMessage(server, parsed)
|
|
603
|
+
.then((response) => {
|
|
604
|
+
if (response)
|
|
605
|
+
sendMessage(response);
|
|
606
|
+
})
|
|
607
|
+
.catch((error) => {
|
|
608
|
+
sendError(parsed.id ?? null, -32603, error instanceof Error ? error.message : String(error));
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
process.stdin.on('data', (chunk) => {
|
|
613
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
614
|
+
processBuffer();
|
|
615
|
+
});
|
|
616
|
+
process.stdin.on('end', () => {
|
|
617
|
+
process.exit(0);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
477
620
|
if (require.main === module) {
|
|
478
|
-
|
|
479
|
-
console.log('E2E Agents MCP Server started');
|
|
480
|
-
console.log('Tools:', server.getTools().map((t) => t.name).join(', '));
|
|
621
|
+
startStdioServer();
|
|
481
622
|
}
|
|
482
623
|
export default E2EAgentsMCPServer;
|
|
@@ -103,7 +103,7 @@ export class OllamaProvider extends BaseProvider {
|
|
|
103
103
|
// SECURITY: Validate and sanitize URL
|
|
104
104
|
const urlValidation = validateOllamaUrl(config.baseUrl);
|
|
105
105
|
if (!urlValidation.valid && urlValidation.warning) {
|
|
106
|
-
|
|
106
|
+
logger.warn(urlValidation.warning);
|
|
107
107
|
}
|
|
108
108
|
// SECURITY: Validate timeout
|
|
109
109
|
const timeout = validateTimeout(config.timeout);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
3
|
import { AnthropicProvider } from './anthropic_provider.js';
|
|
4
|
+
import { logger } from './logger.js';
|
|
4
5
|
import { CustomProvider } from './custom_provider.js';
|
|
5
6
|
import { OllamaProvider } from './ollama_provider.js';
|
|
6
7
|
import { OpenAIProvider } from './openai_provider.js';
|
|
@@ -131,8 +132,7 @@ export class LLMProviderFactory {
|
|
|
131
132
|
const ollama = new OllamaProvider({});
|
|
132
133
|
const health = await ollama.checkHealth();
|
|
133
134
|
if (health.healthy) {
|
|
134
|
-
|
|
135
|
-
console.log('Auto-detected Ollama provider (free, local)');
|
|
135
|
+
logger.info('Auto-detected Ollama provider (free, local)');
|
|
136
136
|
return ollama;
|
|
137
137
|
}
|
|
138
138
|
throw new Error('No LLM provider available. Please either:\n' +
|
|
@@ -141,6 +141,17 @@ export class LLMProviderFactory {
|
|
|
141
141
|
'3. Set OPENAI_API_KEY environment variable\n' +
|
|
142
142
|
'4. Set LLM_PROVIDER environment variable');
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Create provider from an explicit preference when supplied, otherwise
|
|
146
|
+
* fall back to environment auto-detection.
|
|
147
|
+
*/
|
|
148
|
+
static async createFromPreference(providerPreference) {
|
|
149
|
+
const normalized = providerPreference?.trim().toLowerCase();
|
|
150
|
+
if (!normalized || normalized === 'auto') {
|
|
151
|
+
return this.createFromEnv();
|
|
152
|
+
}
|
|
153
|
+
return this.createFromString(normalized);
|
|
154
|
+
}
|
|
144
155
|
/**
|
|
145
156
|
* Create provider from simple string format
|
|
146
157
|
*
|
|
@@ -218,8 +229,7 @@ class HybridProvider {
|
|
|
218
229
|
}
|
|
219
230
|
async generateText(prompt, options) {
|
|
220
231
|
// Use primary for text generation (free)
|
|
221
|
-
|
|
222
|
-
console.log(`[Hybrid] Using ${this.primary.name} for text generation`);
|
|
232
|
+
logger.debug(`[Hybrid] Using ${this.primary.name} for text generation`);
|
|
223
233
|
return await this.primary.generateText(prompt, options);
|
|
224
234
|
}
|
|
225
235
|
async analyzeImage(images, prompt, options) {
|
|
@@ -227,8 +237,7 @@ class HybridProvider {
|
|
|
227
237
|
if (this.useFallbackFor.has('vision')) {
|
|
228
238
|
// Use fallback if primary doesn't support vision
|
|
229
239
|
if (!this.primary.capabilities.vision) {
|
|
230
|
-
|
|
231
|
-
console.log(`[Hybrid] Using ${this.fallback.name} for vision analysis (primary doesn't support vision)`);
|
|
240
|
+
logger.debug(`[Hybrid] Using ${this.fallback.name} for vision analysis (primary doesn't support vision)`);
|
|
232
241
|
if (!this.fallback.analyzeImage) {
|
|
233
242
|
throw new UnsupportedCapabilityError(this.name, 'vision');
|
|
234
243
|
}
|
|
@@ -237,8 +246,7 @@ class HybridProvider {
|
|
|
237
246
|
}
|
|
238
247
|
// Try primary first
|
|
239
248
|
if (this.primary.analyzeImage) {
|
|
240
|
-
|
|
241
|
-
console.log(`[Hybrid] Using ${this.primary.name} for vision analysis`);
|
|
249
|
+
logger.debug(`[Hybrid] Using ${this.primary.name} for vision analysis`);
|
|
242
250
|
return await this.primary.analyzeImage(images, prompt, options);
|
|
243
251
|
}
|
|
244
252
|
throw new UnsupportedCapabilityError(this.name, 'vision');
|
|
@@ -248,8 +256,7 @@ class HybridProvider {
|
|
|
248
256
|
if (!this.primary.streamText) {
|
|
249
257
|
throw new UnsupportedCapabilityError(this.primary.name, 'streaming');
|
|
250
258
|
}
|
|
251
|
-
|
|
252
|
-
console.log(`[Hybrid] Using ${this.primary.name} for streaming`);
|
|
259
|
+
logger.debug(`[Hybrid] Using ${this.primary.name} for streaming`);
|
|
253
260
|
yield* this.primary.streamText(prompt, options);
|
|
254
261
|
}
|
|
255
262
|
getUsageStats() {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
3
|
import { lstatSync, readdirSync, readFileSync } from 'fs';
|
|
4
4
|
import { join, relative, resolve } from 'path';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
5
6
|
import { isGuessedRoute } from './types.js';
|
|
6
7
|
const MAX_FILES_PER_FAMILY = 20;
|
|
7
8
|
const MAX_LINES_PER_FILE = 50;
|
|
@@ -289,11 +290,11 @@ export async function enrichFamilies(families, scanned, projectRoot, provider, b
|
|
|
289
290
|
// Truncate at the last complete section boundary to avoid malformed input
|
|
290
291
|
const lastSectionEnd = prompt.lastIndexOf('\n---\n', MAX_PROMPT_CHARS);
|
|
291
292
|
if (lastSectionEnd > 0) {
|
|
292
|
-
|
|
293
|
+
logger.warn(`[train] Prompt truncated from ${prompt.length} chars at section boundary`);
|
|
293
294
|
prompt = prompt.slice(0, lastSectionEnd);
|
|
294
295
|
}
|
|
295
296
|
else {
|
|
296
|
-
|
|
297
|
+
logger.warn(`[train] Prompt truncated from ${prompt.length} to ${MAX_PROMPT_CHARS} chars`);
|
|
297
298
|
prompt = prompt.slice(0, MAX_PROMPT_CHARS);
|
|
298
299
|
}
|
|
299
300
|
}
|
|
@@ -325,7 +326,7 @@ export async function enrichFamilies(families, scanned, projectRoot, provider, b
|
|
|
325
326
|
}
|
|
326
327
|
catch (error) {
|
|
327
328
|
// On LLM failure, keep families unchanged
|
|
328
|
-
|
|
329
|
+
logger.warn(`[train] LLM enrichment failed for chunk: ${error instanceof Error ? error.message : String(error)}`);
|
|
329
330
|
enriched.push(...chunk);
|
|
330
331
|
}
|
|
331
332
|
finally {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// See LICENSE.txt for license information.
|
|
3
3
|
import { execFileSync } from 'child_process';
|
|
4
4
|
import { resolve } from 'path';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
5
6
|
import { bindFilesToFamilies } from '../knowledge/route_families.js';
|
|
6
7
|
/**
|
|
7
8
|
* Glob-style patterns for infrastructure / cross-cutting files that will never
|
|
@@ -103,7 +104,7 @@ export function getCommitFiles(projectRoot, since) {
|
|
|
103
104
|
});
|
|
104
105
|
}
|
|
105
106
|
catch (error) {
|
|
106
|
-
|
|
107
|
+
logger.warn(`[train] git log failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
108
|
return [];
|
|
108
109
|
}
|
|
109
110
|
return parseGitLog(log);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route_families.d.ts","sourceRoot":"","sources":["../../src/knowledge/route_families.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route_families.d.ts","sourceRoot":"","sources":["../../src/knowledge/route_families.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@ exports.getRoutesForBinding = getRoutesForBinding;
|
|
|
16
16
|
exports.clearManifestCache = clearManifestCache;
|
|
17
17
|
const fs_1 = require("fs");
|
|
18
18
|
const path_1 = require("path");
|
|
19
|
+
const logger_js_1 = require("../logger.js");
|
|
19
20
|
const manifestCache = new Map();
|
|
20
21
|
function matchesGlob(filePath, pattern) {
|
|
21
22
|
const normalized = filePath.replace(/\\/g, '/');
|
|
@@ -168,8 +169,7 @@ function loadRouteFamilyManifest(testsRoot, config) {
|
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
if (config?.strict) {
|
|
171
|
-
|
|
172
|
-
console.warn('[e2e-agents] Route family manifest not found. The manifest is optional context for AI enrichment — create .e2e-ai-agents/route-families.json to enable family-level routing hints.');
|
|
172
|
+
logger_js_1.logger.warn('Route family manifest not found. Create .e2e-ai-agents/route-families.json to enable family-level routing hints.');
|
|
173
173
|
}
|
|
174
174
|
return null;
|
|
175
175
|
}
|
package/dist/logger.d.ts
CHANGED