igel-qe-core 1.0.14 → 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 +107 -44
- package/package.json +2 -1
- package/src/cli/index.ts +108 -41
- 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";
|
|
@@ -229,49 +229,79 @@ function isCopilotCliAvailable() {
|
|
|
229
229
|
});
|
|
230
230
|
return check.status === 0 && !check.error;
|
|
231
231
|
}
|
|
232
|
-
function isCopilotAuthenticated(
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
232
|
+
function isCopilotAuthenticated() {
|
|
233
|
+
// Use a lightweight auth-status check instead of spawning a full agent session.
|
|
234
|
+
// "copilot auth status" exits 0 when authenticated, non-zero otherwise.
|
|
235
|
+
// Fallback: if the sub-command doesn't exist, treat as unauthenticated.
|
|
236
|
+
const probe = spawnSync("copilot", ["auth", "status"], {
|
|
236
237
|
encoding: "utf-8",
|
|
237
238
|
shell: process.platform === "win32",
|
|
238
|
-
timeout:
|
|
239
|
-
input: "", // close stdin immediately so it doesn't wait for user input
|
|
239
|
+
timeout: 10_000, // 10 s max — simple status call should be instant
|
|
240
240
|
});
|
|
241
241
|
return probe.status === 0 && !probe.error;
|
|
242
242
|
}
|
|
243
243
|
function ensureCopilotLogin(workspaceRoot) {
|
|
244
244
|
if (!isCopilotCliAvailable())
|
|
245
245
|
return false;
|
|
246
|
-
if (isCopilotAuthenticated(
|
|
246
|
+
if (isCopilotAuthenticated())
|
|
247
247
|
return true;
|
|
248
248
|
console.log(chalk.yellow(" ⚠ GitHub Copilot CLI is not authenticated."));
|
|
249
249
|
console.log(chalk.yellow(" → Please complete Copilot login to continue context enrichment."));
|
|
250
250
|
const login = spawnSync("copilot", ["login"], { stdio: "inherit", encoding: "utf-8", shell: process.platform === "win32" });
|
|
251
251
|
if (login.status !== 0)
|
|
252
252
|
return false;
|
|
253
|
-
return isCopilotAuthenticated(
|
|
253
|
+
return isCopilotAuthenticated();
|
|
254
254
|
}
|
|
255
|
+
// Maximum number of context.md files to enrich per init run.
|
|
256
|
+
// Prevents extremely long waits on repos with many folders.
|
|
257
|
+
const MAX_ENRICHMENT_FILES = 15;
|
|
255
258
|
function enrichWithCopilotCli(workspaceRoot, contextFiles) {
|
|
256
259
|
let updated = 0;
|
|
257
260
|
let failed = 0;
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
// Limit the number of files processed in one init run.
|
|
262
|
+
const filesToProcess = contextFiles.slice(0, MAX_ENRICHMENT_FILES);
|
|
263
|
+
const total = filesToProcess.length;
|
|
264
|
+
for (let i = 0; i < filesToProcess.length; i++) {
|
|
265
|
+
const absPath = filesToProcess[i];
|
|
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, "/");
|
|
271
|
+
// Show per-file progress so the user knows work is happening.
|
|
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.
|
|
260
286
|
const prompt = [
|
|
261
|
-
`
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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.`,
|
|
269
301
|
].join("\n");
|
|
270
302
|
const result = spawnSync("copilot", [
|
|
271
303
|
"-C", workspaceRoot,
|
|
272
304
|
"-p", prompt,
|
|
273
|
-
"--allow-all-tools",
|
|
274
|
-
"--allow-all-paths",
|
|
275
305
|
"--no-color",
|
|
276
306
|
"-s",
|
|
277
307
|
], {
|
|
@@ -279,11 +309,30 @@ function enrichWithCopilotCli(workspaceRoot, contextFiles) {
|
|
|
279
309
|
shell: process.platform === "win32",
|
|
280
310
|
timeout: 120_000, // 2 min per file — prevents indefinite hangs
|
|
281
311
|
input: "", // close stdin immediately
|
|
312
|
+
maxBuffer: 4 * 1024 * 1024, // 4 MB stdout buffer
|
|
282
313
|
});
|
|
283
|
-
if (result.status === 0) {
|
|
284
|
-
|
|
314
|
+
if (result.status === 0 && !result.error) {
|
|
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
|
+
}
|
|
285
333
|
}
|
|
286
334
|
else {
|
|
335
|
+
process.stdout.write(" ✗\n");
|
|
287
336
|
failed += 1;
|
|
288
337
|
}
|
|
289
338
|
}
|
|
@@ -409,7 +458,9 @@ program
|
|
|
409
458
|
// Wrap both the python path and requirements path in quotes so
|
|
410
459
|
// Windows paths that contain spaces (e.g. "IGEL Automation July 2026")
|
|
411
460
|
// are treated as a single argument by the shell.
|
|
412
|
-
|
|
461
|
+
// --prefer-binary: use pre-built wheels instead of compiling from source
|
|
462
|
+
// (avoids Rust/Cargo requirement for packages like tokenizers, crawl4ai)
|
|
463
|
+
execSync(`"${pyBin}" -m pip install -q --prefer-binary -r "${reqPath}"`, {
|
|
413
464
|
cwd: workspaceRoot, stdio: "inherit",
|
|
414
465
|
});
|
|
415
466
|
console.log(chalk.green(" ✓ Dependencies installed"));
|
|
@@ -436,29 +487,41 @@ program
|
|
|
436
487
|
else {
|
|
437
488
|
console.log(chalk.green(" ✓ Framework context files already up to date"));
|
|
438
489
|
}
|
|
439
|
-
// Step
|
|
490
|
+
// Step 3d — Copilot CLI enrichment pass.
|
|
491
|
+
// Only run when --with-copilot / --with-all is explicitly requested.
|
|
492
|
+
// Skipped silently on plain `igel-qe init` to avoid unexpected long waits.
|
|
493
|
+
const runEnrichment = options.withCopilot || options.withAll;
|
|
440
494
|
console.log(chalk.yellow("3d. Enriching context via GitHub Copilot CLI…"));
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
495
|
+
if (!runEnrichment) {
|
|
496
|
+
console.log(chalk.gray(" - Skipped (pass --with-copilot to enable Copilot context enrichment)"));
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
try {
|
|
500
|
+
const allContextFiles = listFrameworkContextFiles(workspaceRoot);
|
|
501
|
+
if (allContextFiles.length === 0) {
|
|
502
|
+
console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
|
|
503
|
+
}
|
|
504
|
+
else if (!isCopilotCliAvailable()) {
|
|
505
|
+
console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
|
|
506
|
+
}
|
|
507
|
+
else if (!ensureCopilotLogin(workspaceRoot)) {
|
|
508
|
+
console.log(chalk.yellow(" ⚠ Copilot login was not completed; skipping Copilot enrichment"));
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
const cap = Math.min(allContextFiles.length, MAX_ENRICHMENT_FILES);
|
|
512
|
+
if (allContextFiles.length > MAX_ENRICHMENT_FILES) {
|
|
513
|
+
console.log(chalk.gray(` - ${allContextFiles.length} context files found; enriching first ${MAX_ENRICHMENT_FILES} (re-run to continue)`));
|
|
514
|
+
}
|
|
515
|
+
const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
|
|
516
|
+
console.log(chalk.green(` ✓ Copilot updated ${run.updated}/${cap} context.md file(s)`));
|
|
517
|
+
if (run.failed > 0) {
|
|
518
|
+
console.log(chalk.yellow(` ⚠ Copilot failed to update ${run.failed} context.md file(s)`));
|
|
519
|
+
}
|
|
457
520
|
}
|
|
458
521
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
522
|
+
catch (e) {
|
|
523
|
+
console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
|
|
524
|
+
}
|
|
462
525
|
}
|
|
463
526
|
// Step 3 — write MCP config (default: Copilot when no explicit platform flags)
|
|
464
527
|
console.log(chalk.yellow("4. Configuring MCP server…"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "igel-qe-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "IGEL QE Developer Experience Layer (CLI & MCP)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
|
+
"prepublishOnly": "tsc",
|
|
19
20
|
"start:mcp": "node ./dist/mcp/server.js"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
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";
|
|
@@ -241,17 +241,17 @@ function isCopilotCliAvailable(): boolean {
|
|
|
241
241
|
return check.status === 0 && !check.error;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
function isCopilotAuthenticated(
|
|
245
|
-
//
|
|
246
|
-
//
|
|
244
|
+
function isCopilotAuthenticated(): boolean {
|
|
245
|
+
// Use a lightweight auth-status check instead of spawning a full agent session.
|
|
246
|
+
// "copilot auth status" exits 0 when authenticated, non-zero otherwise.
|
|
247
|
+
// Fallback: if the sub-command doesn't exist, treat as unauthenticated.
|
|
247
248
|
const probe = spawnSync(
|
|
248
249
|
"copilot",
|
|
249
|
-
["
|
|
250
|
+
["auth", "status"],
|
|
250
251
|
{
|
|
251
252
|
encoding: "utf-8",
|
|
252
253
|
shell: process.platform === "win32",
|
|
253
|
-
timeout:
|
|
254
|
-
input: "", // close stdin immediately so it doesn't wait for user input
|
|
254
|
+
timeout: 10_000, // 10 s max — simple status call should be instant
|
|
255
255
|
},
|
|
256
256
|
);
|
|
257
257
|
return probe.status === 0 && !probe.error;
|
|
@@ -259,31 +259,70 @@ function isCopilotAuthenticated(workspaceRoot: string): boolean {
|
|
|
259
259
|
|
|
260
260
|
function ensureCopilotLogin(workspaceRoot: string): boolean {
|
|
261
261
|
if (!isCopilotCliAvailable()) return false;
|
|
262
|
-
if (isCopilotAuthenticated(
|
|
262
|
+
if (isCopilotAuthenticated()) return true;
|
|
263
263
|
|
|
264
264
|
console.log(chalk.yellow(" ⚠ GitHub Copilot CLI is not authenticated."));
|
|
265
265
|
console.log(chalk.yellow(" → Please complete Copilot login to continue context enrichment."));
|
|
266
266
|
|
|
267
267
|
const login = spawnSync("copilot", ["login"], { stdio: "inherit", encoding: "utf-8", shell: process.platform === "win32" });
|
|
268
268
|
if (login.status !== 0) return false;
|
|
269
|
-
return isCopilotAuthenticated(
|
|
269
|
+
return isCopilotAuthenticated();
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
|
|
272
|
+
// Maximum number of context.md files to enrich per init run.
|
|
273
|
+
// Prevents extremely long waits on repos with many folders.
|
|
274
|
+
const MAX_ENRICHMENT_FILES = 15;
|
|
275
|
+
|
|
276
|
+
function enrichWithCopilotCli(
|
|
277
|
+
workspaceRoot: string,
|
|
278
|
+
contextFiles: string[],
|
|
279
|
+
): { updated: number; failed: number } {
|
|
273
280
|
let updated = 0;
|
|
274
281
|
let failed = 0;
|
|
275
282
|
|
|
276
|
-
|
|
277
|
-
|
|
283
|
+
// Limit the number of files processed in one init run.
|
|
284
|
+
const filesToProcess = contextFiles.slice(0, MAX_ENRICHMENT_FILES);
|
|
285
|
+
const total = filesToProcess.length;
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < filesToProcess.length; i++) {
|
|
288
|
+
const absPath = filesToProcess[i];
|
|
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, "/");
|
|
294
|
+
|
|
295
|
+
// Show per-file progress so the user knows work is happening.
|
|
296
|
+
process.stdout.write(` [${i + 1}/${total}] Enriching ${relPath}…`);
|
|
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.
|
|
278
311
|
const prompt = [
|
|
279
|
-
`
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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.`,
|
|
287
326
|
].join("\n");
|
|
288
327
|
|
|
289
328
|
const result = spawnSync(
|
|
@@ -291,8 +330,6 @@ function enrichWithCopilotCli(workspaceRoot: string, contextFiles: string[]): {
|
|
|
291
330
|
[
|
|
292
331
|
"-C", workspaceRoot,
|
|
293
332
|
"-p", prompt,
|
|
294
|
-
"--allow-all-tools",
|
|
295
|
-
"--allow-all-paths",
|
|
296
333
|
"--no-color",
|
|
297
334
|
"-s",
|
|
298
335
|
],
|
|
@@ -301,12 +338,29 @@ function enrichWithCopilotCli(workspaceRoot: string, contextFiles: string[]): {
|
|
|
301
338
|
shell: process.platform === "win32",
|
|
302
339
|
timeout: 120_000, // 2 min per file — prevents indefinite hangs
|
|
303
340
|
input: "", // close stdin immediately
|
|
341
|
+
maxBuffer: 4 * 1024 * 1024, // 4 MB stdout buffer
|
|
304
342
|
},
|
|
305
343
|
);
|
|
306
344
|
|
|
307
|
-
if (result.status === 0) {
|
|
308
|
-
|
|
345
|
+
if (result.status === 0 && !result.error) {
|
|
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
|
+
}
|
|
309
362
|
} else {
|
|
363
|
+
process.stdout.write(" ✗\n");
|
|
310
364
|
failed += 1;
|
|
311
365
|
}
|
|
312
366
|
}
|
|
@@ -439,7 +493,9 @@ program
|
|
|
439
493
|
// Wrap both the python path and requirements path in quotes so
|
|
440
494
|
// Windows paths that contain spaces (e.g. "IGEL Automation July 2026")
|
|
441
495
|
// are treated as a single argument by the shell.
|
|
442
|
-
|
|
496
|
+
// --prefer-binary: use pre-built wheels instead of compiling from source
|
|
497
|
+
// (avoids Rust/Cargo requirement for packages like tokenizers, crawl4ai)
|
|
498
|
+
execSync(`"${pyBin}" -m pip install -q --prefer-binary -r "${reqPath}"`, {
|
|
443
499
|
cwd: workspaceRoot, stdio: "inherit",
|
|
444
500
|
});
|
|
445
501
|
console.log(chalk.green(" ✓ Dependencies installed"));
|
|
@@ -467,25 +523,36 @@ program
|
|
|
467
523
|
}
|
|
468
524
|
|
|
469
525
|
|
|
470
|
-
// Step
|
|
526
|
+
// Step 3d — Copilot CLI enrichment pass.
|
|
527
|
+
// Only run when --with-copilot / --with-all is explicitly requested.
|
|
528
|
+
// Skipped silently on plain `igel-qe init` to avoid unexpected long waits.
|
|
529
|
+
const runEnrichment = options.withCopilot || options.withAll;
|
|
471
530
|
console.log(chalk.yellow("3d. Enriching context via GitHub Copilot CLI…"));
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
531
|
+
if (!runEnrichment) {
|
|
532
|
+
console.log(chalk.gray(" - Skipped (pass --with-copilot to enable Copilot context enrichment)"));
|
|
533
|
+
} else {
|
|
534
|
+
try {
|
|
535
|
+
const allContextFiles = listFrameworkContextFiles(workspaceRoot);
|
|
536
|
+
if (allContextFiles.length === 0) {
|
|
537
|
+
console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
|
|
538
|
+
} else if (!isCopilotCliAvailable()) {
|
|
539
|
+
console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
|
|
540
|
+
} else if (!ensureCopilotLogin(workspaceRoot)) {
|
|
541
|
+
console.log(chalk.yellow(" ⚠ Copilot login was not completed; skipping Copilot enrichment"));
|
|
542
|
+
} else {
|
|
543
|
+
const cap = Math.min(allContextFiles.length, MAX_ENRICHMENT_FILES);
|
|
544
|
+
if (allContextFiles.length > MAX_ENRICHMENT_FILES) {
|
|
545
|
+
console.log(chalk.gray(` - ${allContextFiles.length} context files found; enriching first ${MAX_ENRICHMENT_FILES} (re-run to continue)`));
|
|
546
|
+
}
|
|
547
|
+
const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
|
|
548
|
+
console.log(chalk.green(` ✓ Copilot updated ${run.updated}/${cap} context.md file(s)`));
|
|
549
|
+
if (run.failed > 0) {
|
|
550
|
+
console.log(chalk.yellow(` ⚠ Copilot failed to update ${run.failed} context.md file(s)`));
|
|
551
|
+
}
|
|
485
552
|
}
|
|
553
|
+
} catch (e: unknown) {
|
|
554
|
+
console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
|
|
486
555
|
}
|
|
487
|
-
} catch (e: unknown) {
|
|
488
|
-
console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
|
|
489
556
|
}
|
|
490
557
|
|
|
491
558
|
// Step 3 — write MCP config (default: Copilot when no explicit platform flags)
|
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)
|