igel-qe-core 1.0.3 → 1.0.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/dist/cli/index.js +0 -0
- package/dist/cli/platform.js +127 -72
- package/dist/mcp/server.js +76 -10
- package/knowledge_base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/knowledge_base/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__init__.py +2 -5
- package/knowledge_base/agents/__pycache__/__init__.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/automation_design_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/automation_design_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/confidence_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/confidence_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/execution_validation_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/execution_validation_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/fusion_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/graph.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/intent_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/kg_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/memory_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/reflection_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/requirement_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/requirement_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/scenario_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/scenario_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/script_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/script_generator_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/script_generator_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/state.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/temporal_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/testcase_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/testcase_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/validation_agent.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/validation_agent.cpython-314.pyc +0 -0
- package/knowledge_base/agents/__pycache__/workflow_engine.cpython-313.pyc +0 -0
- package/knowledge_base/agents/__pycache__/workflow_engine.cpython-314.pyc +0 -0
- package/knowledge_base/agents/automation_design_agent.py +2 -2
- package/knowledge_base/agents/confidence_agent.py +2 -1
- package/knowledge_base/agents/feedback_agent.py +4 -0
- package/knowledge_base/agents/workflow_engine.py +1 -1
- package/knowledge_base/cli/__pycache__/__init__.cpython-313.pyc +0 -0
- package/knowledge_base/cli/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/cli/__pycache__/workflow_cli.cpython-313.pyc +0 -0
- package/knowledge_base/cli/__pycache__/workflow_cli.cpython-314.pyc +0 -0
- package/knowledge_base/cli/workflow_cli.py +125 -29
- package/knowledge_base/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/knowledge_base/config/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/config/__pycache__/settings.cpython-313.pyc +0 -0
- package/knowledge_base/config/__pycache__/settings.cpython-314.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/base.cpython-313.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/base.cpython-314.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/jira_connector.cpython-313.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/jira_connector.cpython-314.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/query_bridge.cpython-314.pyc +0 -0
- package/knowledge_base/connectors/jira_connector.py +1 -5
- package/knowledge_base/core/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/core/__pycache__/circuit_breaker.cpython-314.pyc +0 -0
- package/knowledge_base/core/__pycache__/confidence.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-313.pyc +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-313.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/igel_schema.cpython-313.pyc +0 -0
- package/knowledge_base/db/__pycache__/igel_schema.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/kg_introspect.cpython-314.pyc +0 -0
- package/knowledge_base/db/igel_schema.py +1 -1
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-313.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-313.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-314.pyc +0 -0
- package/knowledge_base/eval/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/eval/__pycache__/baseline.cpython-314.pyc +0 -0
- package/knowledge_base/eval/__pycache__/harness.cpython-314.pyc +0 -0
- package/knowledge_base/eval/__pycache__/scorers.cpython-314.pyc +0 -0
- package/knowledge_base/generation/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/generation/__pycache__/prompts.cpython-314.pyc +0 -0
- package/knowledge_base/generation/__pycache__/test_generator.cpython-314.pyc +0 -0
- package/knowledge_base/memory/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/memory/__pycache__/cognee_client.cpython-314.pyc +0 -0
- package/knowledge_base/memory/__pycache__/memory_types.cpython-314.pyc +0 -0
- package/knowledge_base/models/__pycache__/test_requirement.cpython-313.pyc +0 -0
- package/knowledge_base/models/__pycache__/test_requirement.cpython-314.pyc +0 -0
- package/knowledge_base/models/test_requirement.py +3 -0
- package/knowledge_base/retrieval/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retriever.cpython-314.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/unified_query.cpython-314.pyc +0 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
File without changes
|
package/dist/cli/platform.js
CHANGED
|
@@ -1,90 +1,145 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
4
5
|
import chalk from 'chalk';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
8
|
+
/** Resolve the full absolute path to the igel-qe-mcp binary. */
|
|
9
|
+
function resolveIgelMcpBin() {
|
|
10
|
+
try {
|
|
11
|
+
// Works on Linux/macOS; on Windows use 'where igel-qe-mcp'
|
|
12
|
+
const cmd = process.platform === 'win32' ? 'where igel-qe-mcp' : 'which igel-qe-mcp';
|
|
13
|
+
return execSync(cmd, { encoding: 'utf8' }).trim().split('\n')[0];
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return process.platform === 'win32' ? 'igel-qe-mcp.cmd' : 'igel-qe-mcp';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Detect the user-level settings.json for the running VS Code variant. */
|
|
20
|
+
function detectVscodeSettingsPath() {
|
|
21
|
+
const home = os.homedir();
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
// Windows native install
|
|
24
|
+
return path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'settings.json');
|
|
25
|
+
}
|
|
26
|
+
// Linux / macOS – try VS Code Server (remote-SSH / code-server) first
|
|
27
|
+
const vscodeServerUserSettings = path.join(home, '.vscode-server', 'data', 'User', 'settings.json');
|
|
28
|
+
if (fs.existsSync(path.dirname(vscodeServerUserSettings)) || process.env.VSCODE_SERVER_DATA_DIR) {
|
|
29
|
+
return vscodeServerUserSettings;
|
|
30
|
+
}
|
|
31
|
+
// Standard desktop VS Code on Linux
|
|
32
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
33
|
+
return path.join(xdgConfig, 'Code', 'User', 'settings.json');
|
|
34
|
+
}
|
|
35
|
+
/** Read JSON safely; return defaultValue on any error. */
|
|
36
|
+
function readJson(filePath, defaultValue = {}) {
|
|
37
|
+
if (!fs.existsSync(filePath))
|
|
38
|
+
return defaultValue;
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return defaultValue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Write JSON and ensure the parent directory exists. */
|
|
47
|
+
function writeJson(filePath, data) {
|
|
48
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
49
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
50
|
+
}
|
|
51
|
+
// ── Main export ────────────────────────────────────────────────────────────
|
|
5
52
|
export async function setupPlatform(platform) {
|
|
6
|
-
const
|
|
7
|
-
|
|
53
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
const serverJsPath = path.resolve(currentDir, '..', 'mcp', 'server.js');
|
|
55
|
+
const nodeExecutable = process.execPath || 'node';
|
|
56
|
+
// Standard MCP server entry used by most tools
|
|
8
57
|
const mcpServerConfig = {
|
|
9
|
-
command:
|
|
10
|
-
args: []
|
|
58
|
+
command: nodeExecutable,
|
|
59
|
+
args: [serverJsPath],
|
|
60
|
+
};
|
|
61
|
+
// For tools that expect { type, command, args } (e.g. VS Code .vscode/mcp.json)
|
|
62
|
+
const vscMcpServerConfig = {
|
|
63
|
+
type: 'stdio',
|
|
64
|
+
command: nodeExecutable,
|
|
65
|
+
args: [serverJsPath],
|
|
11
66
|
};
|
|
12
67
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
clineConfigPath = path.join(homeDir, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json');
|
|
43
|
-
}
|
|
44
|
-
let settings = { mcpServers: {} };
|
|
45
|
-
if (fs.existsSync(clineConfigPath)) {
|
|
46
|
-
const raw = fs.readFileSync(clineConfigPath, 'utf8');
|
|
47
|
-
try {
|
|
48
|
-
settings = JSON.parse(raw);
|
|
68
|
+
switch (platform) {
|
|
69
|
+
case 'copilot': {
|
|
70
|
+
const settingsPath = detectVscodeSettingsPath();
|
|
71
|
+
// 1. VS Code global user mcp.json (Standard for VS Code Copilot MCP)
|
|
72
|
+
const globalMcpPath = path.join(path.dirname(settingsPath), 'mcp.json');
|
|
73
|
+
const globalMcp = readJson(globalMcpPath, {});
|
|
74
|
+
if (!globalMcp.mcpServers)
|
|
75
|
+
globalMcp.mcpServers = {};
|
|
76
|
+
if (!globalMcp.servers)
|
|
77
|
+
globalMcp.servers = {};
|
|
78
|
+
globalMcp.mcpServers['igel-qe'] = mcpServerConfig;
|
|
79
|
+
globalMcp.servers['igel-qe'] = mcpServerConfig;
|
|
80
|
+
writeJson(globalMcpPath, globalMcp);
|
|
81
|
+
console.log(chalk.green(`✓ GitHub Copilot MCP (Global) → ${globalMcpPath}`));
|
|
82
|
+
// 2. Workspace .vscode/mcp.json (works in any VS Code ≥ 1.99)
|
|
83
|
+
const workspaceMcpPath = path.join(process.cwd(), '.vscode', 'mcp.json');
|
|
84
|
+
const workspaceMcp = readJson(workspaceMcpPath, {});
|
|
85
|
+
if (!workspaceMcp.mcpServers)
|
|
86
|
+
workspaceMcp.mcpServers = {};
|
|
87
|
+
if (!workspaceMcp.servers)
|
|
88
|
+
workspaceMcp.servers = {};
|
|
89
|
+
workspaceMcp.mcpServers['igel-qe'] = vscMcpServerConfig;
|
|
90
|
+
workspaceMcp.servers['igel-qe'] = vscMcpServerConfig;
|
|
91
|
+
writeJson(workspaceMcpPath, workspaceMcp);
|
|
92
|
+
console.log(chalk.green(`✓ Workspace MCP config → ${workspaceMcpPath}`));
|
|
93
|
+
// 3. Fallback: VS Code user settings.json (Legacy Copilot setting)
|
|
94
|
+
const settings = readJson(settingsPath);
|
|
95
|
+
if (!settings['github.copilot.chat.mcp.servers']) {
|
|
96
|
+
settings['github.copilot.chat.mcp.servers'] = {};
|
|
49
97
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
98
|
+
settings['github.copilot.chat.mcp.servers']['igel-qe'] = mcpServerConfig;
|
|
99
|
+
writeJson(settingsPath, settings);
|
|
100
|
+
console.log(chalk.green(`✓ GitHub Copilot legacy MCP config → ${settingsPath}`));
|
|
101
|
+
break;
|
|
54
102
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
103
|
+
// ── Cline (Claude Dev) ─────────────────────────────────────────
|
|
104
|
+
case 'cline': {
|
|
105
|
+
const home = os.homedir();
|
|
106
|
+
const clineConfigPath = process.platform === 'win32'
|
|
107
|
+
? path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json')
|
|
108
|
+
: path.join(home, '.vscode-server', 'data', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json');
|
|
109
|
+
const settings = readJson(clineConfigPath, { mcpServers: {} });
|
|
110
|
+
if (!settings.mcpServers)
|
|
111
|
+
settings.mcpServers = {};
|
|
112
|
+
settings.mcpServers['igel-qe'] = mcpServerConfig;
|
|
113
|
+
writeJson(clineConfigPath, settings);
|
|
114
|
+
console.log(chalk.green(`✓ Cline MCP → ${clineConfigPath}`));
|
|
115
|
+
break;
|
|
64
116
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
117
|
+
// ── Antigravity (Gemini Code Assist in VS Code) ────────────────
|
|
118
|
+
case 'antigravity': {
|
|
119
|
+
// 1. VS Code user settings (gemini.mcp.servers key)
|
|
120
|
+
const settingsPath = detectVscodeSettingsPath();
|
|
121
|
+
const settings = readJson(settingsPath);
|
|
122
|
+
if (!settings['gemini.mcp.servers']) {
|
|
123
|
+
settings['gemini.mcp.servers'] = {};
|
|
70
124
|
}
|
|
71
|
-
|
|
125
|
+
settings['gemini.mcp.servers']['igel-qe'] = mcpServerConfig;
|
|
126
|
+
writeJson(settingsPath, settings);
|
|
127
|
+
console.log(chalk.green(`✓ Antigravity MCP (settings.json) → ${settingsPath}`));
|
|
128
|
+
// 2. Workspace .mcp.json (the format Antigravity reads from the project root)
|
|
129
|
+
const workspaceMcpPath = path.join(process.cwd(), '.mcp.json');
|
|
130
|
+
const workspaceMcp = readJson(workspaceMcpPath, { mcpServers: {} });
|
|
131
|
+
if (!workspaceMcp.mcpServers)
|
|
132
|
+
workspaceMcp.mcpServers = {};
|
|
133
|
+
workspaceMcp.mcpServers['igel-qe'] = mcpServerConfig;
|
|
134
|
+
writeJson(workspaceMcpPath, workspaceMcp);
|
|
135
|
+
console.log(chalk.green(`✓ Antigravity workspace MCP (.mcp.json) → ${workspaceMcpPath}`));
|
|
136
|
+
break;
|
|
72
137
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
if (!settings['gemini.mcp.servers']) {
|
|
77
|
-
settings['gemini.mcp.servers'] = {};
|
|
78
|
-
}
|
|
79
|
-
settings['gemini.mcp.servers']['igel-qe'] = mcpServerConfig;
|
|
80
|
-
fs.writeFileSync(vscodeSettingsPath, JSON.stringify(settings, null, 2));
|
|
81
|
-
console.log(chalk.green(`✓ Successfully configured Antigravity MCP in ${vscodeSettingsPath}`));
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
console.log(chalk.yellow(`Platform '${platform}' is not currently supported for auto-setup.`));
|
|
138
|
+
default:
|
|
139
|
+
console.log(chalk.yellow(`Platform '${platform}' is not currently supported. Supported: copilot, cline, antigravity.`));
|
|
85
140
|
}
|
|
86
141
|
}
|
|
87
142
|
catch (err) {
|
|
88
|
-
console.error(chalk.red(
|
|
143
|
+
console.error(chalk.red(`✗ Failed to setup ${platform}: ${err.message}`));
|
|
89
144
|
}
|
|
90
145
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
4
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
5
|
import { spawn } from "child_process";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import fs from "fs";
|
|
7
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
// Project root: dist/mcp/server.js -> ../../ = project root
|
|
11
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
|
|
12
|
+
// Resolve the correct Python executable:
|
|
13
|
+
// 1. Check for a local .venv with a Linux bin/ (created by python3 -m venv)
|
|
14
|
+
// 2. Fall back to system python3 / python
|
|
15
|
+
function resolvePythonExecutable() {
|
|
16
|
+
const candidates = [
|
|
17
|
+
path.join(PROJECT_ROOT, '.venv', 'bin', 'python'),
|
|
18
|
+
path.join(PROJECT_ROOT, '.venv', 'bin', 'python3'),
|
|
19
|
+
];
|
|
20
|
+
for (const c of candidates) {
|
|
21
|
+
if (fs.existsSync(c))
|
|
22
|
+
return c;
|
|
23
|
+
}
|
|
24
|
+
// Check system python3 / python
|
|
25
|
+
return 'python3';
|
|
26
|
+
}
|
|
8
27
|
const server = new Server({
|
|
9
28
|
name: "igel-qe-mcp",
|
|
10
29
|
version: "1.0.0",
|
|
@@ -13,19 +32,38 @@ const server = new Server({
|
|
|
13
32
|
tools: {}
|
|
14
33
|
}
|
|
15
34
|
});
|
|
35
|
+
// Map MCP tool names to workflow_cli action names
|
|
36
|
+
const TOOL_TO_ACTION = {
|
|
37
|
+
generate_test_cases: 'generate_test_cases',
|
|
38
|
+
generate_automation: 'generate_automation',
|
|
39
|
+
sync_bitbucket: 'sync_bitbucket',
|
|
40
|
+
impact_analysis: 'impact_analysis',
|
|
41
|
+
submit_feedback: 'submit_feedback',
|
|
42
|
+
};
|
|
16
43
|
async function callPythonEngine(workflowName, args) {
|
|
17
|
-
return new Promise((resolve,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
44
|
+
return new Promise((resolve, _reject) => {
|
|
45
|
+
const action = TOOL_TO_ACTION[workflowName];
|
|
46
|
+
if (!action) {
|
|
47
|
+
resolve(JSON.stringify({ status: 'error', message: `Unknown tool: ${workflowName}` }));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const pythonExe = resolvePythonExecutable();
|
|
51
|
+
const pythonProcess = spawn(pythonExe, [
|
|
22
52
|
"-m", "knowledge_base.cli.workflow_cli",
|
|
23
|
-
"--action",
|
|
53
|
+
"--action", action,
|
|
24
54
|
"--args", JSON.stringify(args)
|
|
25
55
|
], {
|
|
26
|
-
cwd:
|
|
27
|
-
env: process.env
|
|
56
|
+
cwd: PROJECT_ROOT,
|
|
57
|
+
env: { ...process.env, PYTHONPATH: PROJECT_ROOT }
|
|
28
58
|
});
|
|
59
|
+
// Add 5-minute timeout
|
|
60
|
+
const timeout = setTimeout(() => {
|
|
61
|
+
pythonProcess.kill();
|
|
62
|
+
resolve(JSON.stringify({
|
|
63
|
+
status: "error",
|
|
64
|
+
message: "Timeout: Python process took longer than 5 minutes."
|
|
65
|
+
}));
|
|
66
|
+
}, 5 * 60 * 1000);
|
|
29
67
|
let stdoutData = "";
|
|
30
68
|
let stderrData = "";
|
|
31
69
|
pythonProcess.stdout.on("data", (data) => {
|
|
@@ -36,6 +74,7 @@ async function callPythonEngine(workflowName, args) {
|
|
|
36
74
|
console.error(`Python stderr: ${data}`); // MCP servers use stderr for logging
|
|
37
75
|
});
|
|
38
76
|
pythonProcess.on("close", (code) => {
|
|
77
|
+
clearTimeout(timeout);
|
|
39
78
|
if (code !== 0) {
|
|
40
79
|
// If it crashed entirely, return the stderr as a JSON error
|
|
41
80
|
resolve(JSON.stringify({
|
|
@@ -46,7 +85,8 @@ async function callPythonEngine(workflowName, args) {
|
|
|
46
85
|
}
|
|
47
86
|
else {
|
|
48
87
|
// Return the exact JSON stdout printed by workflow_cli.py
|
|
49
|
-
|
|
88
|
+
const output = stdoutData.trim();
|
|
89
|
+
resolve(output || JSON.stringify({ status: "error", message: "Empty output from Python engine" }));
|
|
50
90
|
}
|
|
51
91
|
});
|
|
52
92
|
});
|
|
@@ -119,8 +159,34 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
119
159
|
}));
|
|
120
160
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
121
161
|
const result = await callPythonEngine(request.params.name, request.params.arguments);
|
|
162
|
+
// Format JSON result into nice markdown for the IDE
|
|
163
|
+
let formattedResult = result;
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(result);
|
|
166
|
+
if (parsed.status === 'success') {
|
|
167
|
+
formattedResult = `✅ **Success**\n\n`;
|
|
168
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
169
|
+
if (k !== 'status') {
|
|
170
|
+
if (typeof v === 'object') {
|
|
171
|
+
formattedResult += `**${k}**:\n\`\`\`json\n${JSON.stringify(v, null, 2)}\n\`\`\`\n\n`;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
formattedResult += `**${k}**: ${v}\n`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
formattedResult = `❌ **Error**\n\n${parsed.message || 'Unknown error'}\n`;
|
|
181
|
+
if (parsed.stderr)
|
|
182
|
+
formattedResult += `\n\`\`\`\n${parsed.stderr}\n\`\`\``;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
// If it's not JSON, just return it directly (fallback)
|
|
187
|
+
}
|
|
122
188
|
return {
|
|
123
|
-
content: [{ type: "text", text:
|
|
189
|
+
content: [{ type: "text", text: formattedResult }]
|
|
124
190
|
};
|
|
125
191
|
});
|
|
126
192
|
async function main() {
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Agent orchestration package for IGEL QE."""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from knowledge_base.agents.state import AgenticState, initial_state
|
|
5
|
-
|
|
6
|
-
__all__ = ["AgenticState", "initial_state", "build_graph", "generate_agentic"]
|
|
3
|
+
__all__ = []
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -5,7 +5,7 @@ from pydantic import BaseModel, Field
|
|
|
5
5
|
from knowledge_base.config import cfg
|
|
6
6
|
from knowledge_base.agents.requirement_agent import _get_llm_client
|
|
7
7
|
from knowledge_base.db.client import get_conn
|
|
8
|
-
from knowledge_base.embeddings.azure_embedder import
|
|
8
|
+
from knowledge_base.embeddings.azure_embedder import embed_single
|
|
9
9
|
from knowledge_base.models.test_requirement import TestCaseWithConfidence
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
@@ -30,7 +30,7 @@ Output MUST be valid JSON matching this schema:
|
|
|
30
30
|
def _semantic_search_assets(query: str, limit: int = 20) -> Dict[str, List[str]]:
|
|
31
31
|
"""Retrieve relevant assets from the Asset Registry based on semantic similarity to the query."""
|
|
32
32
|
try:
|
|
33
|
-
embedding =
|
|
33
|
+
embedding = embed_single(query)
|
|
34
34
|
assets = {"page_object": [], "fixture": [], "utility": [], "base_class": [], "api_client": []}
|
|
35
35
|
|
|
36
36
|
with get_conn() as conn:
|
|
@@ -78,7 +78,8 @@ def compute_hybrid_confidence(tc: GeneratedTestCase, scenario: ScenarioWithRules
|
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
tc.confidence_score = total
|
|
81
|
-
|
|
81
|
+
# Support both Pydantic v1 (.dict()) and v2 (.model_dump())
|
|
82
|
+
tc.confidence_metrics = metrics.model_dump() if hasattr(metrics, 'model_dump') else metrics.dict()
|
|
82
83
|
|
|
83
84
|
is_approved = total >= 0.80
|
|
84
85
|
|
|
@@ -35,6 +35,10 @@ class FeedbackAgent:
|
|
|
35
35
|
logger.error(f"Failed to capture feedback: {e}")
|
|
36
36
|
return False
|
|
37
37
|
|
|
38
|
+
def submit(self, level: str, target_id: str, feedback_type: str, user_comment: str, corrected_data: Optional[Dict] = None) -> bool:
|
|
39
|
+
"""Alias for capture_feedback for CLI compatibility."""
|
|
40
|
+
return self.capture_feedback(level, target_id, feedback_type, user_comment, corrected_data)
|
|
41
|
+
|
|
38
42
|
def get_feedback_for_target(self, target_id: str) -> list:
|
|
39
43
|
"""Retrieve historical feedback for a specific target to use as generation context."""
|
|
40
44
|
feedback = []
|
|
@@ -21,7 +21,7 @@ class WorkflowEngine:
|
|
|
21
21
|
self.repo_path = repo_path
|
|
22
22
|
self.exec_validator = ExecutionValidationAgent(repo_path)
|
|
23
23
|
|
|
24
|
-
def run_requirement_flow(self, req: TestRequirement) -> TestRequirement:
|
|
24
|
+
def run_requirement_flow(self, req: TestRequirement) -> Tuple[TestRequirement, List]:
|
|
25
25
|
"""Requirement -> Requirement Understanding -> Scenario -> Business Rules"""
|
|
26
26
|
logger.info(f"Starting requirement flow for {req.jira_key}")
|
|
27
27
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -4,81 +4,123 @@ import logging
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# Bootstrap: ensure the project root is in PYTHONPATH so that
|
|
8
|
+
# `knowledge_base` is importable whether called via `python -m` or directly.
|
|
9
|
+
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
10
|
+
if str(_PROJECT_ROOT) not in sys.path:
|
|
11
|
+
sys.path.insert(0, str(_PROJECT_ROOT))
|
|
12
|
+
|
|
8
13
|
from knowledge_base.db.igel_schema import ensure_igel_tables
|
|
9
14
|
from knowledge_base.connectors.jira_connector import JiraConnector
|
|
10
15
|
from knowledge_base.agents.workflow_engine import WorkflowEngine
|
|
11
16
|
|
|
12
|
-
logging.basicConfig(level=logging.ERROR)
|
|
17
|
+
logging.basicConfig(level=logging.ERROR) # Only output strict JSON to stdout
|
|
18
|
+
|
|
19
|
+
ALL_ACTIONS = [
|
|
20
|
+
"generate_test_cases",
|
|
21
|
+
"generate_automation",
|
|
22
|
+
"sync_bitbucket",
|
|
23
|
+
"impact_analysis",
|
|
24
|
+
"submit_feedback",
|
|
25
|
+
]
|
|
26
|
+
|
|
13
27
|
|
|
14
28
|
def main():
|
|
15
29
|
parser = argparse.ArgumentParser(description="Workflow CLI for Node.js IPC")
|
|
16
|
-
parser.add_argument("--action", required=True, choices=
|
|
30
|
+
parser.add_argument("--action", required=True, choices=ALL_ACTIONS)
|
|
17
31
|
parser.add_argument("--args", required=True, help="JSON arguments for the action")
|
|
18
|
-
|
|
32
|
+
|
|
19
33
|
args = parser.parse_args()
|
|
20
|
-
|
|
34
|
+
|
|
21
35
|
try:
|
|
22
36
|
payload = json.loads(args.args)
|
|
23
37
|
except json.JSONDecodeError:
|
|
24
38
|
print(json.dumps({"status": "error", "message": "Invalid JSON args"}))
|
|
25
39
|
sys.exit(1)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
|
|
41
|
+
# ── DB init (skip for feedback which may not need it) ────────────────────
|
|
42
|
+
if args.action not in ("submit_feedback",):
|
|
43
|
+
try:
|
|
44
|
+
ensure_igel_tables()
|
|
45
|
+
except Exception as e:
|
|
46
|
+
print(json.dumps({"status": "error", "message": f"Database Init Failed: {e}"}))
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
|
|
49
|
+
# ── generate_test_cases ──────────────────────────────────────────────────
|
|
33
50
|
if args.action == "generate_test_cases":
|
|
34
51
|
jira_key = payload.get("jira_key")
|
|
35
52
|
if not jira_key:
|
|
36
53
|
print(json.dumps({"status": "error", "message": "Missing jira_key"}))
|
|
37
54
|
sys.exit(1)
|
|
38
|
-
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
connector = JiraConnector()
|
|
58
|
+
req = connector.get_test_requirement(jira_key)
|
|
59
|
+
|
|
60
|
+
repo_path = Path.cwd()
|
|
61
|
+
engine = WorkflowEngine(repo_path)
|
|
62
|
+
|
|
63
|
+
req, scenarios = engine.run_requirement_flow(req)
|
|
64
|
+
test_cases = engine.run_testcase_flow(req, scenarios)
|
|
65
|
+
|
|
66
|
+
result = {
|
|
67
|
+
"status": "success",
|
|
68
|
+
"jira_key": jira_key,
|
|
69
|
+
"generated_cases": len(test_cases),
|
|
70
|
+
"scenarios": [str(s) for s in scenarios],
|
|
71
|
+
}
|
|
72
|
+
print(json.dumps(result))
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(json.dumps({"status": "error", "message": str(e)}))
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
# ── generate_automation ──────────────────────────────────────────────────
|
|
79
|
+
elif args.action == "generate_automation":
|
|
80
|
+
jira_key = payload.get("jira_key")
|
|
81
|
+
if not jira_key:
|
|
82
|
+
print(json.dumps({"status": "error", "message": "Missing jira_key"}))
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
|
|
39
85
|
try:
|
|
40
|
-
# 1. Fetch from Jira
|
|
41
86
|
connector = JiraConnector()
|
|
42
87
|
req = connector.get_test_requirement(jira_key)
|
|
43
|
-
|
|
44
|
-
# 2. Run Workflow
|
|
88
|
+
|
|
45
89
|
repo_path = Path.cwd()
|
|
46
90
|
engine = WorkflowEngine(repo_path)
|
|
47
|
-
|
|
91
|
+
|
|
48
92
|
req, scenarios = engine.run_requirement_flow(req)
|
|
49
93
|
test_cases = engine.run_testcase_flow(req, scenarios)
|
|
50
|
-
|
|
51
|
-
#
|
|
94
|
+
|
|
95
|
+
# Auto-approve all test cases for CLI automation
|
|
52
96
|
for tc in test_cases:
|
|
53
97
|
tc.is_approved = True
|
|
54
|
-
|
|
98
|
+
|
|
55
99
|
output_script = repo_path / "tests" / f"test_{jira_key.replace('-', '_').lower()}.py"
|
|
56
100
|
ok, msg = engine.run_script_flow(req, test_cases, output_script)
|
|
57
|
-
|
|
101
|
+
|
|
58
102
|
if ok:
|
|
59
103
|
result = {
|
|
60
104
|
"status": "success",
|
|
61
105
|
"jira_key": jira_key,
|
|
62
106
|
"generated_cases": len(test_cases),
|
|
63
|
-
"script_path": str(output_script)
|
|
107
|
+
"script_path": str(output_script),
|
|
64
108
|
}
|
|
65
109
|
else:
|
|
66
|
-
result = {
|
|
67
|
-
"status": "error",
|
|
68
|
-
"message": msg
|
|
69
|
-
}
|
|
110
|
+
result = {"status": "error", "message": msg}
|
|
70
111
|
print(json.dumps(result))
|
|
71
|
-
|
|
112
|
+
|
|
72
113
|
except Exception as e:
|
|
73
114
|
print(json.dumps({"status": "error", "message": str(e)}))
|
|
74
115
|
sys.exit(1)
|
|
75
|
-
|
|
116
|
+
|
|
117
|
+
# ── sync_bitbucket ───────────────────────────────────────────────────────
|
|
76
118
|
elif args.action == "sync_bitbucket":
|
|
77
119
|
commit_hash = payload.get("commit_hash")
|
|
78
120
|
if not commit_hash:
|
|
79
121
|
print(json.dumps({"status": "error", "message": "Missing commit_hash"}))
|
|
80
122
|
sys.exit(1)
|
|
81
|
-
|
|
123
|
+
|
|
82
124
|
try:
|
|
83
125
|
from knowledge_base.agents.regression_agent import BitbucketSyncAgent
|
|
84
126
|
repo_path = Path.cwd()
|
|
@@ -89,5 +131,59 @@ def main():
|
|
|
89
131
|
print(json.dumps({"status": "error", "message": str(e)}))
|
|
90
132
|
sys.exit(1)
|
|
91
133
|
|
|
134
|
+
# ── impact_analysis ──────────────────────────────────────────────────────
|
|
135
|
+
elif args.action == "impact_analysis":
|
|
136
|
+
changed_files = payload.get("changed_files")
|
|
137
|
+
if not changed_files or not isinstance(changed_files, list):
|
|
138
|
+
print(json.dumps({"status": "error", "message": "Missing or invalid changed_files list"}))
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
from knowledge_base.agents.regression_agent import BitbucketSyncAgent
|
|
143
|
+
repo_path = Path.cwd()
|
|
144
|
+
agent = BitbucketSyncAgent(repo_path)
|
|
145
|
+
impact = agent.analyze_impact(changed_files)
|
|
146
|
+
print(json.dumps({"status": "success", "impact": impact}))
|
|
147
|
+
except AttributeError:
|
|
148
|
+
# Fallback if analyze_impact is not yet implemented
|
|
149
|
+
print(json.dumps({
|
|
150
|
+
"status": "success",
|
|
151
|
+
"impact": {
|
|
152
|
+
"changed_files": changed_files,
|
|
153
|
+
"affected_tests": [],
|
|
154
|
+
"note": "Full impact analysis requires BitbucketSyncAgent.analyze_impact implementation.",
|
|
155
|
+
},
|
|
156
|
+
}))
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(json.dumps({"status": "error", "message": str(e)}))
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
# ── submit_feedback ──────────────────────────────────────────────────────
|
|
162
|
+
elif args.action == "submit_feedback":
|
|
163
|
+
level = payload.get("level")
|
|
164
|
+
target_id = payload.get("target_id")
|
|
165
|
+
feedback_type = payload.get("feedback_type")
|
|
166
|
+
user_comment = payload.get("user_comment", "")
|
|
167
|
+
|
|
168
|
+
if not all([level, target_id, feedback_type]):
|
|
169
|
+
print(json.dumps({"status": "error", "message": "Missing required fields: level, target_id, feedback_type"}))
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
from knowledge_base.agents.feedback_agent import FeedbackAgent
|
|
174
|
+
agent = FeedbackAgent()
|
|
175
|
+
result = agent.submit(
|
|
176
|
+
level=level,
|
|
177
|
+
target_id=target_id,
|
|
178
|
+
feedback_type=feedback_type,
|
|
179
|
+
user_comment=user_comment,
|
|
180
|
+
)
|
|
181
|
+
print(json.dumps({"status": "success", "result": result}))
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(json.dumps({"status": "error", "message": str(e)}))
|
|
184
|
+
sys.exit(1)
|
|
185
|
+
|
|
186
|
+
|
|
92
187
|
if __name__ == "__main__":
|
|
93
188
|
main()
|
|
189
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -179,10 +179,6 @@ class JiraConnector(BaseConnector):
|
|
|
179
179
|
except Exception as exc:
|
|
180
180
|
logger.error(f"Failed to get test requirement {jira_key}: {exc}")
|
|
181
181
|
raise
|
|
182
|
-
except Exception as exc:
|
|
183
|
-
logger.warning("JIRA sync failed: %s", exc)
|
|
184
|
-
raise
|
|
185
|
-
return items
|
|
186
182
|
|
|
187
183
|
def ingest_to_kg(self, items: list[dict]) -> int:
|
|
188
184
|
if not items:
|
|
@@ -220,7 +216,7 @@ class JiraConnector(BaseConnector):
|
|
|
220
216
|
resp = client.get(
|
|
221
217
|
f"{cfg.JIRA_URL.rstrip('/')}/rest/api/2/issue/{key}",
|
|
222
218
|
params={"fields": "summary,description,status,issuetype,comment"},
|
|
223
|
-
|
|
219
|
+
**self._get_auth_kwargs(),
|
|
224
220
|
)
|
|
225
221
|
resp.raise_for_status()
|
|
226
222
|
data = resp.json()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -67,6 +67,9 @@ class GeneratedTestCase(BaseModel):
|
|
|
67
67
|
priority: Literal["critical", "high", "medium", "low"]
|
|
68
68
|
preconditions: List[str] = Field(default_factory=list)
|
|
69
69
|
steps: List[ManualTestStep] = Field(default_factory=list)
|
|
70
|
+
# Set by the confidence agent after generation
|
|
71
|
+
confidence_score: float = 0.0
|
|
72
|
+
confidence_metrics: Dict[str, Any] = Field(default_factory=dict)
|
|
70
73
|
|
|
71
74
|
class ConfidenceMetrics(BaseModel):
|
|
72
75
|
"""Deterministic hybrid confidence score breakdown."""
|
|
Binary file
|
|
Binary file
|