@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
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,oBAAY,QAAQ;IAChB,KAAK,IAAI;IACT,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;CACZ;AAqCD,qBAAa,MAAM;IACf,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,QAAQ,CAAU;gBAEd,QAAQ,CAAC,EAAE,QAAQ;IAK/B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAM/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAM9D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAM9D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAM/D,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAInC;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG;QAAC,GAAG,EAAE,MAAM,MAAM,CAAA;KAAC;IAWzC,OAAO,CAAC,GAAG;CAoBd;AAGD,eAAO,MAAM,MAAM,QAAe,CAAC"}
|
package/dist/logger.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.logger = exports.Logger = exports.LogLevel = void 0;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Replaces 18 console.log statements with configurable logging
|
|
7
|
+
* Structured logging system.
|
|
9
8
|
* Environment variable: LOG_LEVEL (ERROR, WARN, INFO, DEBUG)
|
|
10
9
|
*/
|
|
11
10
|
var LogLevel;
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
interface Tool {
|
|
2
3
|
name: string;
|
|
3
4
|
description: string;
|
|
@@ -31,5 +32,37 @@ export declare class E2EAgentsMCPServer {
|
|
|
31
32
|
*/
|
|
32
33
|
getTools(): Tool[];
|
|
33
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Encode a JSON-RPC message with Content-Length framing.
|
|
37
|
+
* Exported for testability.
|
|
38
|
+
*/
|
|
39
|
+
export declare function encodeJsonRpcMessage(message: Record<string, unknown>): string;
|
|
40
|
+
/**
|
|
41
|
+
* Parse Content-Length framed JSON-RPC messages from a buffer.
|
|
42
|
+
* Returns parsed messages and the remaining (unconsumed) buffer.
|
|
43
|
+
* Exported for testability.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseJsonRpcFrames(input: Buffer): {
|
|
46
|
+
messages: Array<{
|
|
47
|
+
id?: unknown;
|
|
48
|
+
method?: string;
|
|
49
|
+
params?: Record<string, unknown>;
|
|
50
|
+
}>;
|
|
51
|
+
remainder: Buffer<ArrayBuffer>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Handle a single JSON-RPC message against the server.
|
|
55
|
+
* Returns the response message (or null for notifications).
|
|
56
|
+
* Exported for testability.
|
|
57
|
+
*/
|
|
58
|
+
export declare function handleJsonRpcMessage(server: E2EAgentsMCPServer, message: {
|
|
59
|
+
id?: unknown;
|
|
60
|
+
method?: string;
|
|
61
|
+
params?: Record<string, unknown>;
|
|
62
|
+
}): Promise<Record<string, unknown> | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Start MCP server over stdio using Content-Length framed JSON-RPC messages.
|
|
65
|
+
*/
|
|
66
|
+
export declare function startStdioServer(repoRoot?: string): void;
|
|
34
67
|
export default E2EAgentsMCPServer;
|
|
35
68
|
//# sourceMappingURL=mcp-server.d.ts.map
|
package/dist/mcp-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AAeA,UAAU,IAAI;IACV,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AA4GD;;;GAGG;AACH,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAc;gBAErB,QAAQ,GAAE,MAAsB;IAM5C,OAAO,CAAC,WAAW;IAmGnB;;;OAGG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB5E,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,QAAQ;IAyDhB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;IACH,QAAQ,IAAI,IAAI,EAAE;CAGrB;AAgBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAC,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;CAAC,CA0BtK;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACtC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAC,GAC3E,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAmEzC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAsB,GAAG,IAAI,CAmCvE;AAMD,eAAe,kBAAkB,CAAC"}
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
4
|
// See LICENSE.txt for license information.
|
|
4
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
6
|
exports.E2EAgentsMCPServer = void 0;
|
|
7
|
+
exports.encodeJsonRpcMessage = encodeJsonRpcMessage;
|
|
8
|
+
exports.parseJsonRpcFrames = parseJsonRpcFrames;
|
|
9
|
+
exports.handleJsonRpcMessage = handleJsonRpcMessage;
|
|
10
|
+
exports.startStdioServer = startStdioServer;
|
|
6
11
|
/**
|
|
7
12
|
* MCP Server for E2E Agents - SECURITY HARDENED
|
|
8
13
|
* Exposes tools for Claude and Playwright agents to discover, generate, and heal tests
|
|
@@ -475,12 +480,152 @@ class E2EAgentsMCPServer {
|
|
|
475
480
|
}
|
|
476
481
|
exports.E2EAgentsMCPServer = E2EAgentsMCPServer;
|
|
477
482
|
/**
|
|
478
|
-
*
|
|
479
|
-
*
|
|
483
|
+
* Read the package version at runtime so the MCP initialize response
|
|
484
|
+
* always reflects the installed version.
|
|
480
485
|
*/
|
|
486
|
+
function getPackageVersion() {
|
|
487
|
+
try {
|
|
488
|
+
const pkgPath = (0, path_1.join)((0, path_1.dirname)(__dirname), 'package.json');
|
|
489
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
490
|
+
return pkg.version || '0.0.0';
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
return '0.0.0';
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Encode a JSON-RPC message with Content-Length framing.
|
|
498
|
+
* Exported for testability.
|
|
499
|
+
*/
|
|
500
|
+
function encodeJsonRpcMessage(message) {
|
|
501
|
+
const body = JSON.stringify(message);
|
|
502
|
+
return `Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n${body}`;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Parse Content-Length framed JSON-RPC messages from a buffer.
|
|
506
|
+
* Returns parsed messages and the remaining (unconsumed) buffer.
|
|
507
|
+
* Exported for testability.
|
|
508
|
+
*/
|
|
509
|
+
function parseJsonRpcFrames(input) {
|
|
510
|
+
const messages = [];
|
|
511
|
+
let buffer = Buffer.from(input);
|
|
512
|
+
while (true) {
|
|
513
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
514
|
+
if (headerEnd === -1)
|
|
515
|
+
break;
|
|
516
|
+
const headerText = buffer.slice(0, headerEnd).toString('utf8');
|
|
517
|
+
const match = headerText.match(/Content-Length:\s*(\d+)/i);
|
|
518
|
+
if (!match) {
|
|
519
|
+
buffer = Buffer.alloc(0);
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
const contentLength = Number(match[1]);
|
|
523
|
+
const messageEnd = headerEnd + 4 + contentLength;
|
|
524
|
+
if (buffer.length < messageEnd)
|
|
525
|
+
break;
|
|
526
|
+
const body = buffer.slice(headerEnd + 4, messageEnd).toString('utf8');
|
|
527
|
+
buffer = buffer.slice(messageEnd);
|
|
528
|
+
messages.push(JSON.parse(body));
|
|
529
|
+
}
|
|
530
|
+
return { messages, remainder: buffer };
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Handle a single JSON-RPC message against the server.
|
|
534
|
+
* Returns the response message (or null for notifications).
|
|
535
|
+
* Exported for testability.
|
|
536
|
+
*/
|
|
537
|
+
async function handleJsonRpcMessage(server, message) {
|
|
538
|
+
const { id, method, params } = message;
|
|
539
|
+
const version = getPackageVersion();
|
|
540
|
+
if (method === 'initialize') {
|
|
541
|
+
return {
|
|
542
|
+
jsonrpc: '2.0',
|
|
543
|
+
id,
|
|
544
|
+
result: {
|
|
545
|
+
protocolVersion: typeof params?.protocolVersion === 'string' ? params.protocolVersion : '2024-11-05',
|
|
546
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
547
|
+
serverInfo: { name: 'e2e-agents-mcp', version },
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (method === 'notifications/initialized' || method === 'initialized') {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
if (method === 'tools/list') {
|
|
555
|
+
return {
|
|
556
|
+
jsonrpc: '2.0',
|
|
557
|
+
id,
|
|
558
|
+
result: {
|
|
559
|
+
tools: server.getTools().map((tool) => ({
|
|
560
|
+
name: tool.name,
|
|
561
|
+
description: tool.description,
|
|
562
|
+
inputSchema: tool.inputSchema,
|
|
563
|
+
})),
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (method === 'tools/call') {
|
|
568
|
+
const resultText = await server.callTool(typeof params?.name === 'string' ? params.name : '', typeof params?.arguments === 'object' && params.arguments !== null ? params.arguments : {});
|
|
569
|
+
let isError = false;
|
|
570
|
+
try {
|
|
571
|
+
const parsed = JSON.parse(resultText);
|
|
572
|
+
isError = Boolean(parsed.error);
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
isError = false;
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
jsonrpc: '2.0',
|
|
579
|
+
id,
|
|
580
|
+
result: { content: [{ type: 'text', text: resultText }], isError },
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (method === 'resources/list') {
|
|
584
|
+
return { jsonrpc: '2.0', id, result: { resources: [] } };
|
|
585
|
+
}
|
|
586
|
+
if (method === 'prompts/list') {
|
|
587
|
+
return { jsonrpc: '2.0', id, result: { prompts: [] } };
|
|
588
|
+
}
|
|
589
|
+
if (method === 'ping') {
|
|
590
|
+
return { jsonrpc: '2.0', id, result: {} };
|
|
591
|
+
}
|
|
592
|
+
return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Start MCP server over stdio using Content-Length framed JSON-RPC messages.
|
|
596
|
+
*/
|
|
597
|
+
function startStdioServer(repoRoot = process.cwd()) {
|
|
598
|
+
const server = new E2EAgentsMCPServer(repoRoot);
|
|
599
|
+
let buffer = Buffer.alloc(0);
|
|
600
|
+
const sendMessage = (message) => {
|
|
601
|
+
process.stdout.write(encodeJsonRpcMessage(message));
|
|
602
|
+
};
|
|
603
|
+
const sendError = (id, code, msg) => {
|
|
604
|
+
sendMessage({ jsonrpc: '2.0', id, error: { code, message: msg } });
|
|
605
|
+
};
|
|
606
|
+
const processBuffer = () => {
|
|
607
|
+
const { messages, remainder } = parseJsonRpcFrames(buffer);
|
|
608
|
+
buffer = remainder;
|
|
609
|
+
for (const parsed of messages) {
|
|
610
|
+
void handleJsonRpcMessage(server, parsed)
|
|
611
|
+
.then((response) => {
|
|
612
|
+
if (response)
|
|
613
|
+
sendMessage(response);
|
|
614
|
+
})
|
|
615
|
+
.catch((error) => {
|
|
616
|
+
sendError(parsed.id ?? null, -32603, error instanceof Error ? error.message : String(error));
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
process.stdin.on('data', (chunk) => {
|
|
621
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
622
|
+
processBuffer();
|
|
623
|
+
});
|
|
624
|
+
process.stdin.on('end', () => {
|
|
625
|
+
process.exit(0);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
481
628
|
if (require.main === module) {
|
|
482
|
-
|
|
483
|
-
console.log('E2E Agents MCP Server started');
|
|
484
|
-
console.log('Tools:', server.getTools().map((t) => t.name).join(', '));
|
|
629
|
+
startStdioServer();
|
|
485
630
|
}
|
|
486
631
|
exports.default = E2EAgentsMCPServer;
|
package/dist/ollama_provider.js
CHANGED
|
@@ -110,7 +110,7 @@ class OllamaProvider extends base_provider_js_1.BaseProvider {
|
|
|
110
110
|
// SECURITY: Validate and sanitize URL
|
|
111
111
|
const urlValidation = validateOllamaUrl(config.baseUrl);
|
|
112
112
|
if (!urlValidation.valid && urlValidation.warning) {
|
|
113
|
-
|
|
113
|
+
logger_js_1.logger.warn(urlValidation.warning);
|
|
114
114
|
}
|
|
115
115
|
// SECURITY: Validate timeout
|
|
116
116
|
const timeout = validateTimeout(config.timeout);
|
|
@@ -55,6 +55,11 @@ export declare class LLMProviderFactory {
|
|
|
55
55
|
* 5. Error (no provider available)
|
|
56
56
|
*/
|
|
57
57
|
static createFromEnv(): Promise<LLMProvider>;
|
|
58
|
+
/**
|
|
59
|
+
* Create provider from an explicit preference when supplied, otherwise
|
|
60
|
+
* fall back to environment auto-detection.
|
|
61
|
+
*/
|
|
62
|
+
static createFromPreference(providerPreference?: string): Promise<LLMProvider>;
|
|
58
63
|
/**
|
|
59
64
|
* Create provider from simple string format
|
|
60
65
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider_factory.d.ts","sourceRoot":"","sources":["../src/provider_factory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"provider_factory.d.ts","sourceRoot":"","sources":["../src/provider_factory.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAKR,WAAW,EAIX,cAAc,EAEjB,MAAM,yBAAyB,CAAC;AAGjC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,kBAAkB;IAC3B;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW;IAmBlD;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAWtD;;;;;;;;;OASG;WACU,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC;IAyElD;;;OAGG;WACU,oBAAoB,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IASpF;;;;;;;;;;OAUG;IACH,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW;CAkC/D;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,OAAO,EAAE,cAAc,CAAC;IAExB;;;OAGG;IACH,QAAQ,EAAE,cAAc,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,mBAAmB,GAAG,wBAAwB,CAAC,CAAC;CACrF;AA+ID;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC;IACxE,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;CAC1B,CAAC,CAsCD"}
|
package/dist/provider_factory.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.LLMProviderFactory = void 0;
|
|
6
6
|
exports.validateProviderSetup = validateProviderSetup;
|
|
7
7
|
const anthropic_provider_js_1 = require("./anthropic_provider.js");
|
|
8
|
+
const logger_js_1 = require("./logger.js");
|
|
8
9
|
const custom_provider_js_1 = require("./custom_provider.js");
|
|
9
10
|
const ollama_provider_js_1 = require("./ollama_provider.js");
|
|
10
11
|
const openai_provider_js_1 = require("./openai_provider.js");
|
|
@@ -135,8 +136,7 @@ class LLMProviderFactory {
|
|
|
135
136
|
const ollama = new ollama_provider_js_1.OllamaProvider({});
|
|
136
137
|
const health = await ollama.checkHealth();
|
|
137
138
|
if (health.healthy) {
|
|
138
|
-
|
|
139
|
-
console.log('Auto-detected Ollama provider (free, local)');
|
|
139
|
+
logger_js_1.logger.info('Auto-detected Ollama provider (free, local)');
|
|
140
140
|
return ollama;
|
|
141
141
|
}
|
|
142
142
|
throw new Error('No LLM provider available. Please either:\n' +
|
|
@@ -145,6 +145,17 @@ class LLMProviderFactory {
|
|
|
145
145
|
'3. Set OPENAI_API_KEY environment variable\n' +
|
|
146
146
|
'4. Set LLM_PROVIDER environment variable');
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Create provider from an explicit preference when supplied, otherwise
|
|
150
|
+
* fall back to environment auto-detection.
|
|
151
|
+
*/
|
|
152
|
+
static async createFromPreference(providerPreference) {
|
|
153
|
+
const normalized = providerPreference?.trim().toLowerCase();
|
|
154
|
+
if (!normalized || normalized === 'auto') {
|
|
155
|
+
return this.createFromEnv();
|
|
156
|
+
}
|
|
157
|
+
return this.createFromString(normalized);
|
|
158
|
+
}
|
|
148
159
|
/**
|
|
149
160
|
* Create provider from simple string format
|
|
150
161
|
*
|
|
@@ -223,8 +234,7 @@ class HybridProvider {
|
|
|
223
234
|
}
|
|
224
235
|
async generateText(prompt, options) {
|
|
225
236
|
// Use primary for text generation (free)
|
|
226
|
-
|
|
227
|
-
console.log(`[Hybrid] Using ${this.primary.name} for text generation`);
|
|
237
|
+
logger_js_1.logger.debug(`[Hybrid] Using ${this.primary.name} for text generation`);
|
|
228
238
|
return await this.primary.generateText(prompt, options);
|
|
229
239
|
}
|
|
230
240
|
async analyzeImage(images, prompt, options) {
|
|
@@ -232,8 +242,7 @@ class HybridProvider {
|
|
|
232
242
|
if (this.useFallbackFor.has('vision')) {
|
|
233
243
|
// Use fallback if primary doesn't support vision
|
|
234
244
|
if (!this.primary.capabilities.vision) {
|
|
235
|
-
|
|
236
|
-
console.log(`[Hybrid] Using ${this.fallback.name} for vision analysis (primary doesn't support vision)`);
|
|
245
|
+
logger_js_1.logger.debug(`[Hybrid] Using ${this.fallback.name} for vision analysis (primary doesn't support vision)`);
|
|
237
246
|
if (!this.fallback.analyzeImage) {
|
|
238
247
|
throw new provider_interface_js_1.UnsupportedCapabilityError(this.name, 'vision');
|
|
239
248
|
}
|
|
@@ -242,8 +251,7 @@ class HybridProvider {
|
|
|
242
251
|
}
|
|
243
252
|
// Try primary first
|
|
244
253
|
if (this.primary.analyzeImage) {
|
|
245
|
-
|
|
246
|
-
console.log(`[Hybrid] Using ${this.primary.name} for vision analysis`);
|
|
254
|
+
logger_js_1.logger.debug(`[Hybrid] Using ${this.primary.name} for vision analysis`);
|
|
247
255
|
return await this.primary.analyzeImage(images, prompt, options);
|
|
248
256
|
}
|
|
249
257
|
throw new provider_interface_js_1.UnsupportedCapabilityError(this.name, 'vision');
|
|
@@ -253,8 +261,7 @@ class HybridProvider {
|
|
|
253
261
|
if (!this.primary.streamText) {
|
|
254
262
|
throw new provider_interface_js_1.UnsupportedCapabilityError(this.primary.name, 'streaming');
|
|
255
263
|
}
|
|
256
|
-
|
|
257
|
-
console.log(`[Hybrid] Using ${this.primary.name} for streaming`);
|
|
264
|
+
logger_js_1.logger.debug(`[Hybrid] Using ${this.primary.name} for streaming`);
|
|
258
265
|
yield* this.primary.streamText(prompt, options);
|
|
259
266
|
}
|
|
260
267
|
getUsageStats() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enricher.d.ts","sourceRoot":"","sources":["../../src/training/enricher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"enricher.d.ts","sourceRoot":"","sources":["../../src/training/enricher.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAGhE,OAAO,KAAK,EAAC,gBAAgB,EAAE,aAAa,EAAC,MAAM,YAAY,CAAC;AAkLhE,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,EAAE,CAmBlE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAwBrE;AAkCD,wBAAsB,cAAc,CAChC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,aAAa,EAAE,EACxB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAuF3B"}
|
|
@@ -7,6 +7,7 @@ exports.parseEnrichResponse = parseEnrichResponse;
|
|
|
7
7
|
exports.enrichFamilies = enrichFamilies;
|
|
8
8
|
const fs_1 = require("fs");
|
|
9
9
|
const path_1 = require("path");
|
|
10
|
+
const logger_js_1 = require("../logger.js");
|
|
10
11
|
const types_js_1 = require("./types.js");
|
|
11
12
|
const MAX_FILES_PER_FAMILY = 20;
|
|
12
13
|
const MAX_LINES_PER_FILE = 50;
|
|
@@ -294,11 +295,11 @@ async function enrichFamilies(families, scanned, projectRoot, provider, budgetUS
|
|
|
294
295
|
// Truncate at the last complete section boundary to avoid malformed input
|
|
295
296
|
const lastSectionEnd = prompt.lastIndexOf('\n---\n', MAX_PROMPT_CHARS);
|
|
296
297
|
if (lastSectionEnd > 0) {
|
|
297
|
-
|
|
298
|
+
logger_js_1.logger.warn(`[train] Prompt truncated from ${prompt.length} chars at section boundary`);
|
|
298
299
|
prompt = prompt.slice(0, lastSectionEnd);
|
|
299
300
|
}
|
|
300
301
|
else {
|
|
301
|
-
|
|
302
|
+
logger_js_1.logger.warn(`[train] Prompt truncated from ${prompt.length} to ${MAX_PROMPT_CHARS} chars`);
|
|
302
303
|
prompt = prompt.slice(0, MAX_PROMPT_CHARS);
|
|
303
304
|
}
|
|
304
305
|
}
|
|
@@ -330,7 +331,7 @@ async function enrichFamilies(families, scanned, projectRoot, provider, budgetUS
|
|
|
330
331
|
}
|
|
331
332
|
catch (error) {
|
|
332
333
|
// On LLM failure, keep families unchanged
|
|
333
|
-
|
|
334
|
+
logger_js_1.logger.warn(`[train] LLM enrichment failed for chunk: ${error instanceof Error ? error.message : String(error)}`);
|
|
334
335
|
enriched.push(...chunk);
|
|
335
336
|
}
|
|
336
337
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/training/validator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/training/validator.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AAExE,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,YAAY,CAAC;AAoBnE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CA6BrD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC,CA6BhG;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC,CAgB1H;AA+CD,wBAAgB,cAAc,CAC1B,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,MAAM,EAAE,GACxB,gBAAgB,CA+BlB;AAED,wBAAgB,qBAAqB,CACjC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,mBAAmB,GAC9B,gBAAgB,CAkDlB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAgCvE"}
|
|
@@ -10,6 +10,7 @@ exports.buildValidationReport = buildValidationReport;
|
|
|
10
10
|
exports.formatValidationReport = formatValidationReport;
|
|
11
11
|
const child_process_1 = require("child_process");
|
|
12
12
|
const path_1 = require("path");
|
|
13
|
+
const logger_js_1 = require("../logger.js");
|
|
13
14
|
const route_families_js_1 = require("../knowledge/route_families.js");
|
|
14
15
|
/**
|
|
15
16
|
* Glob-style patterns for infrastructure / cross-cutting files that will never
|
|
@@ -111,7 +112,7 @@ function getCommitFiles(projectRoot, since) {
|
|
|
111
112
|
});
|
|
112
113
|
}
|
|
113
114
|
catch (error) {
|
|
114
|
-
|
|
115
|
+
logger_js_1.logger.warn(`[train] git log failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
115
116
|
return [];
|
|
116
117
|
}
|
|
117
118
|
return parseGitLog(log);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "AI-powered E2E test impact analysis, generation, and healing. Analyzes code changes to identify affected Playwright tests, detects coverage gaps, and generates or repairs specs using pluggable LLM providers (Claude, OpenAI, Ollama). Includes MCP server, traceability, and CI/CD integration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"bin": {
|
|
30
30
|
"e2e-ai-agents": "./dist/cli.js",
|
|
31
|
-
"e2e-qa-agent": "./dist/qa-agent/cli.js"
|
|
31
|
+
"e2e-qa-agent": "./dist/qa-agent/cli.js",
|
|
32
|
+
"e2e-agents-mcp": "./dist/mcp-server.js"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
34
35
|
"dist",
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
],
|
|
39
40
|
"scripts": {
|
|
40
41
|
"build": "npm run build:cjs && npm run build:esm",
|
|
41
|
-
"postbuild": "chmod +x dist/cli.js dist/qa-agent/cli.js",
|
|
42
|
+
"postbuild": "chmod +x dist/cli.js dist/qa-agent/cli.js dist/mcp-server.js",
|
|
42
43
|
"build:cjs": "tsc -p tsconfig.json",
|
|
43
44
|
"build:esm": "tsc -p tsconfig.esm.json && node scripts/write-esm-package.js",
|
|
44
45
|
"clean": "rm -rf dist",
|
package/schemas/plan.schema.json
CHANGED
|
@@ -280,6 +280,68 @@
|
|
|
280
280
|
},
|
|
281
281
|
"additionalProperties": false
|
|
282
282
|
},
|
|
283
|
+
"crew": {
|
|
284
|
+
"type": "object",
|
|
285
|
+
"required": [
|
|
286
|
+
"workflow",
|
|
287
|
+
"providerOverride",
|
|
288
|
+
"summary",
|
|
289
|
+
"impactedFlows",
|
|
290
|
+
"strategyEntries",
|
|
291
|
+
"testDesigns",
|
|
292
|
+
"crossImpacts",
|
|
293
|
+
"regressionRisks",
|
|
294
|
+
"findings",
|
|
295
|
+
"warnings",
|
|
296
|
+
"timings"
|
|
297
|
+
],
|
|
298
|
+
"properties": {
|
|
299
|
+
"workflow": {"type": "string"},
|
|
300
|
+
"providerOverride": {"type": "string"},
|
|
301
|
+
"summary": {
|
|
302
|
+
"type": "object",
|
|
303
|
+
"required": [
|
|
304
|
+
"impactedFlows",
|
|
305
|
+
"strategyEntries",
|
|
306
|
+
"testDesigns",
|
|
307
|
+
"crossImpacts",
|
|
308
|
+
"highRiskCrossImpacts",
|
|
309
|
+
"regressionRisks",
|
|
310
|
+
"findings",
|
|
311
|
+
"generatedSpecs",
|
|
312
|
+
"manualReviewEntries",
|
|
313
|
+
"totalCostUSD",
|
|
314
|
+
"totalTokens"
|
|
315
|
+
],
|
|
316
|
+
"properties": {
|
|
317
|
+
"impactedFlows": {"type": "number"},
|
|
318
|
+
"strategyEntries": {"type": "number"},
|
|
319
|
+
"testDesigns": {"type": "number"},
|
|
320
|
+
"crossImpacts": {"type": "number"},
|
|
321
|
+
"highRiskCrossImpacts": {"type": "number"},
|
|
322
|
+
"regressionRisks": {"type": "number"},
|
|
323
|
+
"findings": {"type": "number"},
|
|
324
|
+
"generatedSpecs": {"type": "number"},
|
|
325
|
+
"manualReviewEntries": {"type": "number"},
|
|
326
|
+
"totalCostUSD": {"type": "number"},
|
|
327
|
+
"totalTokens": {"type": "number"}
|
|
328
|
+
},
|
|
329
|
+
"additionalProperties": false
|
|
330
|
+
},
|
|
331
|
+
"impactedFlows": {"type": "array", "items": {"$ref": "#/$defs/flowDecision"}},
|
|
332
|
+
"strategyEntries": {"type": "array", "items": {"$ref": "#/$defs/strategyEntry"}},
|
|
333
|
+
"testDesigns": {"type": "array", "items": {"$ref": "#/$defs/testDesign"}},
|
|
334
|
+
"crossImpacts": {"type": "array", "items": {"$ref": "#/$defs/crossImpact"}},
|
|
335
|
+
"regressionRisks": {"type": "array", "items": {"$ref": "#/$defs/regressionRisk"}},
|
|
336
|
+
"findings": {"type": "array", "items": {"$ref": "#/$defs/finding"}},
|
|
337
|
+
"warnings": {"type": "array", "items": {"type": "string"}},
|
|
338
|
+
"timings": {
|
|
339
|
+
"type": "object",
|
|
340
|
+
"additionalProperties": {"type": "number"}
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"additionalProperties": false
|
|
344
|
+
},
|
|
283
345
|
"metrics": {
|
|
284
346
|
"type": "object",
|
|
285
347
|
"required": [
|
|
@@ -327,6 +389,102 @@
|
|
|
327
389
|
"details": {"type": "string"}
|
|
328
390
|
},
|
|
329
391
|
"additionalProperties": false
|
|
392
|
+
},
|
|
393
|
+
"flowDecision": {
|
|
394
|
+
"type": "object",
|
|
395
|
+
"required": ["flowId", "flowName", "routeFamily", "changedFiles", "evidence", "evidenceSource", "confidence", "existingSpecs", "action", "priority"],
|
|
396
|
+
"properties": {
|
|
397
|
+
"flowId": {"type": "string"},
|
|
398
|
+
"flowName": {"type": "string"},
|
|
399
|
+
"routeFamily": {"type": "string"},
|
|
400
|
+
"changedFiles": {"type": "array", "items": {"type": "string"}},
|
|
401
|
+
"evidence": {"type": "string"},
|
|
402
|
+
"evidenceSource": {"enum": ["deterministic", "ai", "hybrid"]},
|
|
403
|
+
"confidence": {"type": "number"},
|
|
404
|
+
"existingSpecs": {"type": "array", "items": {"type": "string"}},
|
|
405
|
+
"action": {"enum": ["create_spec", "update_spec", "skip"]},
|
|
406
|
+
"priority": {"enum": ["P0", "P1", "P2"]},
|
|
407
|
+
"userActions": {"type": "array", "items": {"type": "string"}}
|
|
408
|
+
},
|
|
409
|
+
"additionalProperties": true
|
|
410
|
+
},
|
|
411
|
+
"strategyEntry": {
|
|
412
|
+
"type": "object",
|
|
413
|
+
"required": ["flowId", "flowName", "priority", "approach", "rationale", "testCategories", "crossImpactRisk"],
|
|
414
|
+
"properties": {
|
|
415
|
+
"flowId": {"type": "string"},
|
|
416
|
+
"flowName": {"type": "string"},
|
|
417
|
+
"priority": {"enum": ["P0", "P1", "P2"]},
|
|
418
|
+
"approach": {"enum": ["full-test", "smoke-test", "skip", "manual-review"]},
|
|
419
|
+
"rationale": {"type": "string"},
|
|
420
|
+
"testCategories": {"type": "array", "items": {"type": "string"}},
|
|
421
|
+
"crossImpactRisk": {"enum": ["high", "medium", "low", "none"]}
|
|
422
|
+
},
|
|
423
|
+
"additionalProperties": false
|
|
424
|
+
},
|
|
425
|
+
"testDesign": {
|
|
426
|
+
"type": "object",
|
|
427
|
+
"required": ["flowId", "flowName", "testCases"],
|
|
428
|
+
"properties": {
|
|
429
|
+
"flowId": {"type": "string"},
|
|
430
|
+
"flowName": {"type": "string"},
|
|
431
|
+
"testCases": {
|
|
432
|
+
"type": "array",
|
|
433
|
+
"items": {
|
|
434
|
+
"type": "object",
|
|
435
|
+
"required": ["name", "type", "preconditions", "steps", "expectedOutcome", "priority", "rationale"],
|
|
436
|
+
"properties": {
|
|
437
|
+
"name": {"type": "string"},
|
|
438
|
+
"type": {"enum": ["happy-path", "edge-case", "boundary", "negative", "state-transition", "race-condition", "permission", "accessibility", "performance"]},
|
|
439
|
+
"preconditions": {"type": "array", "items": {"type": "string"}},
|
|
440
|
+
"steps": {"type": "array", "items": {"type": "string"}},
|
|
441
|
+
"expectedOutcome": {"type": "string"},
|
|
442
|
+
"priority": {"enum": ["P0", "P1", "P2"]},
|
|
443
|
+
"rationale": {"type": "string"}
|
|
444
|
+
},
|
|
445
|
+
"additionalProperties": false
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
"additionalProperties": false
|
|
450
|
+
},
|
|
451
|
+
"crossImpact": {
|
|
452
|
+
"type": "object",
|
|
453
|
+
"required": ["sourceFamily", "affectedFamily", "sharedDependency", "riskLevel", "evidence"],
|
|
454
|
+
"properties": {
|
|
455
|
+
"sourceFamily": {"type": "string"},
|
|
456
|
+
"affectedFamily": {"type": "string"},
|
|
457
|
+
"sharedDependency": {"type": "string"},
|
|
458
|
+
"riskLevel": {"enum": ["high", "medium", "low"]},
|
|
459
|
+
"evidence": {"type": "string"}
|
|
460
|
+
},
|
|
461
|
+
"additionalProperties": false
|
|
462
|
+
},
|
|
463
|
+
"regressionRisk": {
|
|
464
|
+
"type": "object",
|
|
465
|
+
"required": ["familyId", "filePattern", "riskScore", "reason", "historicalFailures"],
|
|
466
|
+
"properties": {
|
|
467
|
+
"familyId": {"type": "string"},
|
|
468
|
+
"filePattern": {"type": "string"},
|
|
469
|
+
"riskScore": {"type": "number"},
|
|
470
|
+
"reason": {"type": "string"},
|
|
471
|
+
"historicalFailures": {"type": "number"}
|
|
472
|
+
},
|
|
473
|
+
"additionalProperties": false
|
|
474
|
+
},
|
|
475
|
+
"finding": {
|
|
476
|
+
"type": "object",
|
|
477
|
+
"required": ["id", "type", "severity", "source", "summary", "details", "relatedFlows"],
|
|
478
|
+
"properties": {
|
|
479
|
+
"id": {"type": "string"},
|
|
480
|
+
"type": {"enum": ["bug", "gap", "risk", "flaky"]},
|
|
481
|
+
"severity": {"enum": ["critical", "high", "medium", "low"]},
|
|
482
|
+
"source": {"enum": ["strategist", "test-designer", "cross-impact", "regression-advisor", "impact-analyst", "coverage-evaluator", "generator", "executor", "healer", "explorer"]},
|
|
483
|
+
"summary": {"type": "string"},
|
|
484
|
+
"details": {"type": "string"},
|
|
485
|
+
"relatedFlows": {"type": "array", "items": {"type": "string"}}
|
|
486
|
+
},
|
|
487
|
+
"additionalProperties": false
|
|
330
488
|
}
|
|
331
489
|
},
|
|
332
490
|
"additionalProperties": false
|