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.
Files changed (90) hide show
  1. package/dist/cli/index.js +0 -0
  2. package/dist/cli/platform.js +127 -72
  3. package/dist/mcp/server.js +76 -10
  4. package/knowledge_base/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/knowledge_base/__pycache__/__init__.cpython-314.pyc +0 -0
  6. package/knowledge_base/agents/__init__.py +2 -5
  7. package/knowledge_base/agents/__pycache__/__init__.cpython-313.pyc +0 -0
  8. package/knowledge_base/agents/__pycache__/__init__.cpython-314.pyc +0 -0
  9. package/knowledge_base/agents/__pycache__/automation_design_agent.cpython-313.pyc +0 -0
  10. package/knowledge_base/agents/__pycache__/automation_design_agent.cpython-314.pyc +0 -0
  11. package/knowledge_base/agents/__pycache__/confidence_agent.cpython-313.pyc +0 -0
  12. package/knowledge_base/agents/__pycache__/confidence_agent.cpython-314.pyc +0 -0
  13. package/knowledge_base/agents/__pycache__/execution_validation_agent.cpython-313.pyc +0 -0
  14. package/knowledge_base/agents/__pycache__/execution_validation_agent.cpython-314.pyc +0 -0
  15. package/knowledge_base/agents/__pycache__/fusion_agent.cpython-314.pyc +0 -0
  16. package/knowledge_base/agents/__pycache__/graph.cpython-314.pyc +0 -0
  17. package/knowledge_base/agents/__pycache__/intent_agent.cpython-314.pyc +0 -0
  18. package/knowledge_base/agents/__pycache__/kg_agent.cpython-314.pyc +0 -0
  19. package/knowledge_base/agents/__pycache__/memory_agent.cpython-314.pyc +0 -0
  20. package/knowledge_base/agents/__pycache__/reflection_agent.cpython-314.pyc +0 -0
  21. package/knowledge_base/agents/__pycache__/requirement_agent.cpython-313.pyc +0 -0
  22. package/knowledge_base/agents/__pycache__/requirement_agent.cpython-314.pyc +0 -0
  23. package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-314.pyc +0 -0
  24. package/knowledge_base/agents/__pycache__/scenario_agent.cpython-313.pyc +0 -0
  25. package/knowledge_base/agents/__pycache__/scenario_agent.cpython-314.pyc +0 -0
  26. package/knowledge_base/agents/__pycache__/script_agent.cpython-314.pyc +0 -0
  27. package/knowledge_base/agents/__pycache__/script_generator_agent.cpython-313.pyc +0 -0
  28. package/knowledge_base/agents/__pycache__/script_generator_agent.cpython-314.pyc +0 -0
  29. package/knowledge_base/agents/__pycache__/state.cpython-314.pyc +0 -0
  30. package/knowledge_base/agents/__pycache__/temporal_agent.cpython-314.pyc +0 -0
  31. package/knowledge_base/agents/__pycache__/testcase_agent.cpython-313.pyc +0 -0
  32. package/knowledge_base/agents/__pycache__/testcase_agent.cpython-314.pyc +0 -0
  33. package/knowledge_base/agents/__pycache__/validation_agent.cpython-313.pyc +0 -0
  34. package/knowledge_base/agents/__pycache__/validation_agent.cpython-314.pyc +0 -0
  35. package/knowledge_base/agents/__pycache__/workflow_engine.cpython-313.pyc +0 -0
  36. package/knowledge_base/agents/__pycache__/workflow_engine.cpython-314.pyc +0 -0
  37. package/knowledge_base/agents/automation_design_agent.py +2 -2
  38. package/knowledge_base/agents/confidence_agent.py +2 -1
  39. package/knowledge_base/agents/feedback_agent.py +4 -0
  40. package/knowledge_base/agents/workflow_engine.py +1 -1
  41. package/knowledge_base/cli/__pycache__/__init__.cpython-313.pyc +0 -0
  42. package/knowledge_base/cli/__pycache__/__init__.cpython-314.pyc +0 -0
  43. package/knowledge_base/cli/__pycache__/workflow_cli.cpython-313.pyc +0 -0
  44. package/knowledge_base/cli/__pycache__/workflow_cli.cpython-314.pyc +0 -0
  45. package/knowledge_base/cli/workflow_cli.py +125 -29
  46. package/knowledge_base/config/__pycache__/__init__.cpython-313.pyc +0 -0
  47. package/knowledge_base/config/__pycache__/__init__.cpython-314.pyc +0 -0
  48. package/knowledge_base/config/__pycache__/settings.cpython-313.pyc +0 -0
  49. package/knowledge_base/config/__pycache__/settings.cpython-314.pyc +0 -0
  50. package/knowledge_base/connectors/__pycache__/__init__.cpython-313.pyc +0 -0
  51. package/knowledge_base/connectors/__pycache__/__init__.cpython-314.pyc +0 -0
  52. package/knowledge_base/connectors/__pycache__/base.cpython-313.pyc +0 -0
  53. package/knowledge_base/connectors/__pycache__/base.cpython-314.pyc +0 -0
  54. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-313.pyc +0 -0
  55. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-314.pyc +0 -0
  56. package/knowledge_base/connectors/__pycache__/query_bridge.cpython-314.pyc +0 -0
  57. package/knowledge_base/connectors/jira_connector.py +1 -5
  58. package/knowledge_base/core/__pycache__/__init__.cpython-314.pyc +0 -0
  59. package/knowledge_base/core/__pycache__/circuit_breaker.cpython-314.pyc +0 -0
  60. package/knowledge_base/core/__pycache__/confidence.cpython-314.pyc +0 -0
  61. package/knowledge_base/db/__pycache__/__init__.cpython-313.pyc +0 -0
  62. package/knowledge_base/db/__pycache__/__init__.cpython-314.pyc +0 -0
  63. package/knowledge_base/db/__pycache__/client.cpython-313.pyc +0 -0
  64. package/knowledge_base/db/__pycache__/client.cpython-314.pyc +0 -0
  65. package/knowledge_base/db/__pycache__/igel_schema.cpython-313.pyc +0 -0
  66. package/knowledge_base/db/__pycache__/igel_schema.cpython-314.pyc +0 -0
  67. package/knowledge_base/db/__pycache__/kg_introspect.cpython-314.pyc +0 -0
  68. package/knowledge_base/db/igel_schema.py +1 -1
  69. package/knowledge_base/embeddings/__pycache__/__init__.cpython-313.pyc +0 -0
  70. package/knowledge_base/embeddings/__pycache__/__init__.cpython-314.pyc +0 -0
  71. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-313.pyc +0 -0
  72. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-314.pyc +0 -0
  73. package/knowledge_base/eval/__pycache__/__init__.cpython-314.pyc +0 -0
  74. package/knowledge_base/eval/__pycache__/baseline.cpython-314.pyc +0 -0
  75. package/knowledge_base/eval/__pycache__/harness.cpython-314.pyc +0 -0
  76. package/knowledge_base/eval/__pycache__/scorers.cpython-314.pyc +0 -0
  77. package/knowledge_base/generation/__pycache__/__init__.cpython-314.pyc +0 -0
  78. package/knowledge_base/generation/__pycache__/prompts.cpython-314.pyc +0 -0
  79. package/knowledge_base/generation/__pycache__/test_generator.cpython-314.pyc +0 -0
  80. package/knowledge_base/memory/__pycache__/__init__.cpython-314.pyc +0 -0
  81. package/knowledge_base/memory/__pycache__/cognee_client.cpython-314.pyc +0 -0
  82. package/knowledge_base/memory/__pycache__/memory_types.cpython-314.pyc +0 -0
  83. package/knowledge_base/models/__pycache__/test_requirement.cpython-313.pyc +0 -0
  84. package/knowledge_base/models/__pycache__/test_requirement.cpython-314.pyc +0 -0
  85. package/knowledge_base/models/test_requirement.py +3 -0
  86. package/knowledge_base/retrieval/__pycache__/__init__.cpython-314.pyc +0 -0
  87. package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  88. package/knowledge_base/retrieval/__pycache__/retriever.cpython-314.pyc +0 -0
  89. package/knowledge_base/retrieval/__pycache__/unified_query.cpython-314.pyc +0 -0
  90. package/package.json +1 -1
package/dist/cli/index.js CHANGED
File without changes
@@ -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 homeDir = os.homedir();
7
- // The command we want to inject
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: process.platform === 'win32' ? 'igel-qe-mcp.cmd' : 'igel-qe-mcp',
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
- if (platform === 'copilot') {
14
- // Setup for GitHub Copilot in VS Code
15
- // VS Code user settings on Windows usually live in AppData/Roaming/Code/User
16
- let vscodeSettingsPath = path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'settings.json');
17
- if (process.platform !== 'win32') {
18
- vscodeSettingsPath = path.join(homeDir, '.config', 'Code', 'User', 'settings.json');
19
- }
20
- let settings = {};
21
- if (fs.existsSync(vscodeSettingsPath)) {
22
- const raw = fs.readFileSync(vscodeSettingsPath, 'utf8');
23
- try {
24
- settings = JSON.parse(raw);
25
- }
26
- catch (e) { /* ignore parse error for now */ }
27
- }
28
- else {
29
- fs.mkdirSync(path.dirname(vscodeSettingsPath), { recursive: true });
30
- }
31
- if (!settings['github.copilot.chat.mcp.servers']) {
32
- settings['github.copilot.chat.mcp.servers'] = {};
33
- }
34
- settings['github.copilot.chat.mcp.servers']['igel-qe'] = mcpServerConfig;
35
- fs.writeFileSync(vscodeSettingsPath, JSON.stringify(settings, null, 2));
36
- console.log(chalk.green(`✓ Successfully configured GitHub Copilot MCP in ${vscodeSettingsPath}`));
37
- }
38
- else if (platform === 'cline') {
39
- // Setup for Cline (Claude Dev)
40
- let clineConfigPath = path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json');
41
- if (process.platform !== 'win32') {
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
- catch (e) { /* ignore parse error */ }
51
- }
52
- else {
53
- fs.mkdirSync(path.dirname(clineConfigPath), { recursive: true });
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
- settings.mcpServers['igel-qe'] = mcpServerConfig;
56
- fs.writeFileSync(clineConfigPath, JSON.stringify(settings, null, 2));
57
- console.log(chalk.green(`✓ Successfully configured Cline MCP in ${clineConfigPath}`));
58
- }
59
- else if (platform === 'antigravity') {
60
- // Setup for Antigravity (Gemini Code Assist / AI)
61
- let vscodeSettingsPath = path.join(homeDir, 'AppData', 'Roaming', 'Code', 'User', 'settings.json');
62
- if (process.platform !== 'win32') {
63
- vscodeSettingsPath = path.join(homeDir, '.config', 'Code', 'User', 'settings.json');
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
- let settings = {};
66
- if (fs.existsSync(vscodeSettingsPath)) {
67
- const raw = fs.readFileSync(vscodeSettingsPath, 'utf8');
68
- try {
69
- settings = JSON.parse(raw);
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
- catch (e) { /* ignore */ }
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
- else {
74
- fs.mkdirSync(path.dirname(vscodeSettingsPath), { recursive: true });
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(`Failed to setup ${platform}: ${err.message}`));
143
+ console.error(chalk.red(`✗ Failed to setup ${platform}: ${err.message}`));
89
144
  }
90
145
  }
@@ -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, reject) => {
18
- // Run the Python CLI in the project root
19
- // We assume the environment is set up and `python` refers to the venv
20
- const projectRoot = path.join(__dirname, '..', '..', '..');
21
- const pythonProcess = spawn("python", [
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", workflowName,
53
+ "--action", action,
24
54
  "--args", JSON.stringify(args)
25
55
  ], {
26
- cwd: projectRoot,
27
- env: process.env // Inherit the environment (.env vars)
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
- resolve(stdoutData.trim());
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: result }]
189
+ content: [{ type: "text", text: formattedResult }]
124
190
  };
125
191
  });
126
192
  async function main() {
@@ -1,6 +1,3 @@
1
- """LangGraph agents package Phase 5+."""
1
+ """Agent orchestration package for IGEL QE."""
2
2
 
3
- from knowledge_base.agents.graph import build_graph, generate_agentic
4
- from knowledge_base.agents.state import AgenticState, initial_state
5
-
6
- __all__ = ["AgenticState", "initial_state", "build_graph", "generate_agentic"]
3
+ __all__ = []
@@ -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 get_embedding
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 = get_embedding(query)
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
- tc.confidence_metrics = metrics.dict()
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
 
@@ -4,81 +4,123 @@ import logging
4
4
  import sys
5
5
  from pathlib import Path
6
6
 
7
- from knowledge_base.db.client import get_conn
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) # Only output strict JSON to stdout
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=["generate_test_cases", "sync_bitbucket"])
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
- try:
28
- ensure_igel_tables()
29
- except Exception as e:
30
- print(json.dumps({"status": "error", "message": f"Database Init Failed: {e}"}))
31
- sys.exit(1)
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
- # Since we skip Human Review in CLI mode, we auto-approve all for scripting
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
+
@@ -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
- auth=(cfg.JIRA_USERNAME, cfg.JIRA_API_TOKEN),
219
+ **self._get_auth_kwargs(),
224
220
  )
225
221
  resp.raise_for_status()
226
222
  data = resp.json()
@@ -146,5 +146,5 @@ def ensure_igel_tables():
146
146
  conn.commit()
147
147
  logger.info("IGEL Enterprise DB schema verified.")
148
148
  except Exception as e:
149
- logger.error(f"Failed to create IGEL schema: {{e}}")
149
+ logger.error(f"Failed to create IGEL schema: {e}")
150
150
  raise
@@ -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."""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "igel-qe-core",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "IGEL QE Developer Experience Layer (CLI & MCP)",
5
5
  "type": "module",
6
6
  "publishConfig": {