igel-qe-core 1.0.16 → 1.0.18
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 +52 -14
- package/package.json +1 -1
- package/src/cli/index.ts +50 -14
- package/src/cli/workflow_cli.py +48 -12
- package/src/db/client.py +20 -3
- package/src/deploy/requirements.txt +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { spawn, spawnSync, execSync } from "child_process";
|
|
5
|
-
import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
6
6
|
import { join, dirname } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { setupPlatform } from "./platform.js";
|
|
@@ -263,24 +263,45 @@ function enrichWithCopilotCli(workspaceRoot, contextFiles) {
|
|
|
263
263
|
const total = filesToProcess.length;
|
|
264
264
|
for (let i = 0; i < filesToProcess.length; i++) {
|
|
265
265
|
const absPath = filesToProcess[i];
|
|
266
|
-
|
|
266
|
+
// Normalise to forward slashes so the prompt is readable on all platforms.
|
|
267
|
+
const relPath = absPath
|
|
268
|
+
.replace(`${workspaceRoot}\\`, "")
|
|
269
|
+
.replace(`${workspaceRoot}/`, "")
|
|
270
|
+
.replace(/\\/g, "/");
|
|
267
271
|
// Show per-file progress so the user knows work is happening.
|
|
268
272
|
process.stdout.write(` [${i + 1}/${total}] Enriching ${relPath}…`);
|
|
273
|
+
// Read the current (template) content so Copilot can see the structure.
|
|
274
|
+
let currentContent = "";
|
|
275
|
+
try {
|
|
276
|
+
currentContent = readFileSync(absPath, "utf-8");
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// If we can't read, skip this file.
|
|
280
|
+
process.stdout.write(" ✗ (unreadable)\n");
|
|
281
|
+
failed += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
// Build a prompt that asks Copilot to RETURN the improved markdown as plain
|
|
285
|
+
// text to stdout. We write the file ourselves so we know it was updated.
|
|
269
286
|
const prompt = [
|
|
270
|
-
`
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
`You are enriching a context.md file for an automation test framework.`,
|
|
288
|
+
`The file is located at: ${relPath} (relative to the workspace root).`,
|
|
289
|
+
``,
|
|
290
|
+
`Current content:`,
|
|
291
|
+
`\`\`\`markdown`,
|
|
292
|
+
currentContent,
|
|
293
|
+
`\`\`\``,
|
|
294
|
+
``,
|
|
295
|
+
`Rewrite the content above to be concrete, human-understandable, and LLM-ready.`,
|
|
296
|
+
`Requirements:`,
|
|
297
|
+
`- Keep the same heading structure (# Context, ## Purpose, ## Key Assets, ## Conventions, ## Risks and Dependencies).`,
|
|
298
|
+
`- Replace placeholder text with specific details inferred from the folder path and framework context.`,
|
|
299
|
+
`- Be concise but informative. Each section: 2–5 bullet points or short sentences.`,
|
|
300
|
+
`- Output ONLY the improved markdown. Do NOT add commentary, code fences, or extra text around it.`,
|
|
278
301
|
].join("\n");
|
|
279
302
|
const result = spawnSync("copilot", [
|
|
280
303
|
"-C", workspaceRoot,
|
|
281
304
|
"-p", prompt,
|
|
282
|
-
"--allow-all-tools",
|
|
283
|
-
"--allow-all-paths",
|
|
284
305
|
"--no-color",
|
|
285
306
|
"-s",
|
|
286
307
|
], {
|
|
@@ -288,10 +309,27 @@ function enrichWithCopilotCli(workspaceRoot, contextFiles) {
|
|
|
288
309
|
shell: process.platform === "win32",
|
|
289
310
|
timeout: 120_000, // 2 min per file — prevents indefinite hangs
|
|
290
311
|
input: "", // close stdin immediately
|
|
312
|
+
maxBuffer: 4 * 1024 * 1024, // 4 MB stdout buffer
|
|
291
313
|
});
|
|
292
314
|
if (result.status === 0 && !result.error) {
|
|
293
|
-
|
|
294
|
-
|
|
315
|
+
const enriched = (result.stdout || "").trim();
|
|
316
|
+
if (enriched.length > 50) {
|
|
317
|
+
// Only write back if Copilot returned meaningful content (>50 chars).
|
|
318
|
+
try {
|
|
319
|
+
writeFileSync(absPath, enriched, "utf-8");
|
|
320
|
+
process.stdout.write(" ✓\n");
|
|
321
|
+
updated += 1;
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
process.stdout.write(" ✗ (write failed)\n");
|
|
325
|
+
failed += 1;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// Copilot ran but returned empty/trivial output.
|
|
330
|
+
process.stdout.write(" ✗ (empty response)\n");
|
|
331
|
+
failed += 1;
|
|
332
|
+
}
|
|
295
333
|
}
|
|
296
334
|
else {
|
|
297
335
|
process.stdout.write(" ✗\n");
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { spawn, spawnSync, execSync } from "child_process";
|
|
6
|
-
import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
7
7
|
import { join, dirname } from "path";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { setupPlatform } from "./platform.js";
|
|
@@ -286,20 +286,43 @@ function enrichWithCopilotCli(
|
|
|
286
286
|
|
|
287
287
|
for (let i = 0; i < filesToProcess.length; i++) {
|
|
288
288
|
const absPath = filesToProcess[i];
|
|
289
|
-
|
|
289
|
+
// Normalise to forward slashes so the prompt is readable on all platforms.
|
|
290
|
+
const relPath = absPath
|
|
291
|
+
.replace(`${workspaceRoot}\\`, "")
|
|
292
|
+
.replace(`${workspaceRoot}/`, "")
|
|
293
|
+
.replace(/\\/g, "/");
|
|
290
294
|
|
|
291
295
|
// Show per-file progress so the user knows work is happening.
|
|
292
296
|
process.stdout.write(` [${i + 1}/${total}] Enriching ${relPath}…`);
|
|
293
297
|
|
|
298
|
+
// Read the current (template) content so Copilot can see the structure.
|
|
299
|
+
let currentContent = "";
|
|
300
|
+
try {
|
|
301
|
+
currentContent = readFileSync(absPath, "utf-8");
|
|
302
|
+
} catch {
|
|
303
|
+
// If we can't read, skip this file.
|
|
304
|
+
process.stdout.write(" ✗ (unreadable)\n");
|
|
305
|
+
failed += 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Build a prompt that asks Copilot to RETURN the improved markdown as plain
|
|
310
|
+
// text to stdout. We write the file ourselves so we know it was updated.
|
|
294
311
|
const prompt = [
|
|
295
|
-
`
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
312
|
+
`You are enriching a context.md file for an automation test framework.`,
|
|
313
|
+
`The file is located at: ${relPath} (relative to the workspace root).`,
|
|
314
|
+
``,
|
|
315
|
+
`Current content:`,
|
|
316
|
+
`\`\`\`markdown`,
|
|
317
|
+
currentContent,
|
|
318
|
+
`\`\`\``,
|
|
319
|
+
``,
|
|
320
|
+
`Rewrite the content above to be concrete, human-understandable, and LLM-ready.`,
|
|
321
|
+
`Requirements:`,
|
|
322
|
+
`- Keep the same heading structure (# Context, ## Purpose, ## Key Assets, ## Conventions, ## Risks and Dependencies).`,
|
|
323
|
+
`- Replace placeholder text with specific details inferred from the folder path and framework context.`,
|
|
324
|
+
`- Be concise but informative. Each section: 2–5 bullet points or short sentences.`,
|
|
325
|
+
`- Output ONLY the improved markdown. Do NOT add commentary, code fences, or extra text around it.`,
|
|
303
326
|
].join("\n");
|
|
304
327
|
|
|
305
328
|
const result = spawnSync(
|
|
@@ -307,8 +330,6 @@ function enrichWithCopilotCli(
|
|
|
307
330
|
[
|
|
308
331
|
"-C", workspaceRoot,
|
|
309
332
|
"-p", prompt,
|
|
310
|
-
"--allow-all-tools",
|
|
311
|
-
"--allow-all-paths",
|
|
312
333
|
"--no-color",
|
|
313
334
|
"-s",
|
|
314
335
|
],
|
|
@@ -317,12 +338,27 @@ function enrichWithCopilotCli(
|
|
|
317
338
|
shell: process.platform === "win32",
|
|
318
339
|
timeout: 120_000, // 2 min per file — prevents indefinite hangs
|
|
319
340
|
input: "", // close stdin immediately
|
|
341
|
+
maxBuffer: 4 * 1024 * 1024, // 4 MB stdout buffer
|
|
320
342
|
},
|
|
321
343
|
);
|
|
322
344
|
|
|
323
345
|
if (result.status === 0 && !result.error) {
|
|
324
|
-
|
|
325
|
-
|
|
346
|
+
const enriched = (result.stdout || "").trim();
|
|
347
|
+
if (enriched.length > 50) {
|
|
348
|
+
// Only write back if Copilot returned meaningful content (>50 chars).
|
|
349
|
+
try {
|
|
350
|
+
writeFileSync(absPath, enriched, "utf-8");
|
|
351
|
+
process.stdout.write(" ✓\n");
|
|
352
|
+
updated += 1;
|
|
353
|
+
} catch {
|
|
354
|
+
process.stdout.write(" ✗ (write failed)\n");
|
|
355
|
+
failed += 1;
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
// Copilot ran but returned empty/trivial output.
|
|
359
|
+
process.stdout.write(" ✗ (empty response)\n");
|
|
360
|
+
failed += 1;
|
|
361
|
+
}
|
|
326
362
|
} else {
|
|
327
363
|
process.stdout.write(" ✗\n");
|
|
328
364
|
failed += 1;
|
package/src/cli/workflow_cli.py
CHANGED
|
@@ -27,18 +27,32 @@ _PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
|
27
27
|
if str(_PROJECT_ROOT) not in sys.path:
|
|
28
28
|
sys.path.insert(0, str(_PROJECT_ROOT))
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
from src.
|
|
32
|
-
from src.
|
|
33
|
-
from src.
|
|
34
|
-
from src.intelligence.
|
|
35
|
-
from src.
|
|
36
|
-
from src.agents.
|
|
37
|
-
from src.agents.
|
|
38
|
-
from src.
|
|
39
|
-
from src.
|
|
40
|
-
from src.project.
|
|
41
|
-
from src.project.
|
|
30
|
+
try:
|
|
31
|
+
from src.db.igel_schema import ensure_igel_tables
|
|
32
|
+
from src.connectors.jira_connector import JiraConnector
|
|
33
|
+
from src.agents.workflow_engine import WorkflowEngine
|
|
34
|
+
from src.intelligence.diff_analyzer import analyze_diff_scope
|
|
35
|
+
from src.intelligence.routing_engine import RouteInputs, compute_weighted_score, classify_route
|
|
36
|
+
from src.agents.framework_analyzer_agent import FrameworkAnalyzerAgent
|
|
37
|
+
from src.agents.orchestrator.orchestrator import Orchestrator
|
|
38
|
+
from src.agents.internal.approval_manager import ApprovalError
|
|
39
|
+
from src.config import cfg
|
|
40
|
+
from src.project.context_projection_builder import ContextProjectionBuilder
|
|
41
|
+
from src.project.context_requirement_sync import sync_requirement_context
|
|
42
|
+
from src.project.context_enricher import enrich_context_files
|
|
43
|
+
_DEPS_AVAILABLE = True
|
|
44
|
+
_DEPS_ERROR: str | None = None
|
|
45
|
+
except ImportError as _ie:
|
|
46
|
+
_DEPS_AVAILABLE = False
|
|
47
|
+
_DEPS_ERROR = (
|
|
48
|
+
f"Missing Python dependency: {_ie}.\n"
|
|
49
|
+
"Run: igel-qe init --with-copilot to install required packages."
|
|
50
|
+
)
|
|
51
|
+
# Define stubs so references below don't cause NameErrors
|
|
52
|
+
ensure_igel_tables = JiraConnector = WorkflowEngine = None # type: ignore[assignment,misc]
|
|
53
|
+
analyze_diff_scope = RouteInputs = compute_weighted_score = classify_route = None # type: ignore[assignment]
|
|
54
|
+
FrameworkAnalyzerAgent = Orchestrator = ApprovalError = cfg = None # type: ignore[assignment,misc]
|
|
55
|
+
ContextProjectionBuilder = sync_requirement_context = enrich_context_files = None # type: ignore[assignment]
|
|
42
56
|
|
|
43
57
|
logging.basicConfig(level=logging.ERROR) # Only output strict JSON to stdout
|
|
44
58
|
|
|
@@ -99,6 +113,28 @@ def main():
|
|
|
99
113
|
except json.JSONDecodeError:
|
|
100
114
|
_err("Invalid JSON args")
|
|
101
115
|
|
|
116
|
+
# ── Dependency guard ───────────────────────────────────────────────────────
|
|
117
|
+
# If core Python packages (e.g. psycopg2) are missing, return a structured
|
|
118
|
+
# warning for init_check so the CLI can surface a helpful message rather
|
|
119
|
+
# than crashing with a raw traceback.
|
|
120
|
+
if not _DEPS_AVAILABLE:
|
|
121
|
+
if args.action in ("init_project", "init_check"):
|
|
122
|
+
print(json.dumps({
|
|
123
|
+
"status": "success",
|
|
124
|
+
"ready": False,
|
|
125
|
+
"required_failed": ["python_dependencies"],
|
|
126
|
+
"warnings": [],
|
|
127
|
+
"checks": {
|
|
128
|
+
"python_dependencies": {
|
|
129
|
+
"status": "error",
|
|
130
|
+
"message": _DEPS_ERROR or "Required Python packages not installed. Run: igel-qe init --with-copilot",
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}))
|
|
134
|
+
sys.exit(0)
|
|
135
|
+
else:
|
|
136
|
+
_err(_DEPS_ERROR or "Required Python packages not installed. Run: igel-qe init --with-copilot")
|
|
137
|
+
|
|
102
138
|
# ── DB init ────────────────────────────────────────────────────────────────
|
|
103
139
|
if args.action not in ("submit_feedback",):
|
|
104
140
|
try:
|
package/src/db/client.py
CHANGED
|
@@ -3,15 +3,31 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
import psycopg2
|
|
6
|
+
try:
|
|
7
|
+
import psycopg2
|
|
8
|
+
import psycopg2.pool
|
|
9
|
+
_PSYCOPG2_AVAILABLE = True
|
|
10
|
+
except ImportError:
|
|
11
|
+
psycopg2 = None # type: ignore[assignment]
|
|
12
|
+
_PSYCOPG2_AVAILABLE = False
|
|
13
|
+
|
|
8
14
|
from contextlib import contextmanager
|
|
9
15
|
|
|
10
16
|
from src.config import cfg
|
|
11
17
|
|
|
12
18
|
logger = logging.getLogger(__name__)
|
|
13
19
|
|
|
14
|
-
_pool: psycopg2.pool.ThreadedConnectionPool | None = None
|
|
20
|
+
_pool: "psycopg2.pool.ThreadedConnectionPool | None" = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _require_psycopg2() -> None:
|
|
24
|
+
"""Raise a clear error if psycopg2 is not installed."""
|
|
25
|
+
if not _PSYCOPG2_AVAILABLE:
|
|
26
|
+
raise ImportError(
|
|
27
|
+
"psycopg2 is not installed. Run: pip install psycopg2-binary\n"
|
|
28
|
+
"or re-run: igel-qe init --with-copilot (which will retry the install)"
|
|
29
|
+
)
|
|
30
|
+
|
|
15
31
|
|
|
16
32
|
|
|
17
33
|
def _pool_kwargs() -> dict:
|
|
@@ -32,6 +48,7 @@ def _pool_kwargs() -> dict:
|
|
|
32
48
|
|
|
33
49
|
|
|
34
50
|
def init_pool(minconn: int = 2, maxconn: int = 10) -> None:
|
|
51
|
+
_require_psycopg2()
|
|
35
52
|
global _pool
|
|
36
53
|
if _pool is not None:
|
|
37
54
|
return
|
|
@@ -49,7 +49,8 @@ beautifulsoup4>=4.12.0
|
|
|
49
49
|
lxml>=5.0.0
|
|
50
50
|
duckduckgo-search>=6.0.0,<7.0.0; python_version < "3.9"
|
|
51
51
|
firecrawl-py>=1.0.0
|
|
52
|
-
playwright>=
|
|
52
|
+
# playwright: no upper bound — crawl4ai>=0.4 requires playwright>=1.49
|
|
53
|
+
playwright>=1.49.0
|
|
53
54
|
crawl4ai>=0.4.0,<0.5.0; python_version >= "3.9"
|
|
54
55
|
|
|
55
56
|
# REST API server (always-running service)
|