igel-qe-core 1.0.12 → 1.0.16

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 CHANGED
@@ -221,30 +221,51 @@ function listFrameworkContextFiles(workspaceRoot) {
221
221
  return Array.from(new Set(found)).sort();
222
222
  }
223
223
  function isCopilotCliAvailable() {
224
- const check = spawnSync("copilot", ["--version"], { encoding: "utf-8", shell: process.platform === "win32" });
225
- return check.status === 0;
224
+ // Use a short timeout so this never blocks if copilot hangs on startup.
225
+ const check = spawnSync("copilot", ["--version"], {
226
+ encoding: "utf-8",
227
+ shell: process.platform === "win32",
228
+ timeout: 10_000,
229
+ });
230
+ return check.status === 0 && !check.error;
226
231
  }
227
- function isCopilotAuthenticated(workspaceRoot) {
228
- const probe = spawnSync("copilot", ["-C", workspaceRoot, "-p", "Reply with exactly OK.", "--allow-all-tools", "--no-color", "-s"], { encoding: "utf-8", shell: process.platform === "win32" });
229
- return probe.status === 0;
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"], {
237
+ encoding: "utf-8",
238
+ shell: process.platform === "win32",
239
+ timeout: 10_000, // 10 s max — simple status call should be instant
240
+ });
241
+ return probe.status === 0 && !probe.error;
230
242
  }
231
243
  function ensureCopilotLogin(workspaceRoot) {
232
244
  if (!isCopilotCliAvailable())
233
245
  return false;
234
- if (isCopilotAuthenticated(workspaceRoot))
246
+ if (isCopilotAuthenticated())
235
247
  return true;
236
248
  console.log(chalk.yellow(" ⚠ GitHub Copilot CLI is not authenticated."));
237
249
  console.log(chalk.yellow(" → Please complete Copilot login to continue context enrichment."));
238
250
  const login = spawnSync("copilot", ["login"], { stdio: "inherit", encoding: "utf-8", shell: process.platform === "win32" });
239
251
  if (login.status !== 0)
240
252
  return false;
241
- return isCopilotAuthenticated(workspaceRoot);
253
+ return isCopilotAuthenticated();
242
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;
243
258
  function enrichWithCopilotCli(workspaceRoot, contextFiles) {
244
259
  let updated = 0;
245
260
  let failed = 0;
246
- for (const absPath of contextFiles) {
247
- const relPath = absPath.replace(`${workspaceRoot}/`, "");
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
+ const relPath = absPath.replace(`${workspaceRoot}/`, "").replace(`${workspaceRoot}\\`, "");
267
+ // Show per-file progress so the user knows work is happening.
268
+ process.stdout.write(` [${i + 1}/${total}] Enriching ${relPath}…`);
248
269
  const prompt = [
249
270
  `Update the markdown file at path '${relPath}' in place.`,
250
271
  "Goal: make it both human-understandable and LLM-ready for automation framework reasoning.",
@@ -262,11 +283,18 @@ function enrichWithCopilotCli(workspaceRoot, contextFiles) {
262
283
  "--allow-all-paths",
263
284
  "--no-color",
264
285
  "-s",
265
- ], { encoding: "utf-8", shell: process.platform === "win32" });
266
- if (result.status === 0) {
286
+ ], {
287
+ encoding: "utf-8",
288
+ shell: process.platform === "win32",
289
+ timeout: 120_000, // 2 min per file — prevents indefinite hangs
290
+ input: "", // close stdin immediately
291
+ });
292
+ if (result.status === 0 && !result.error) {
293
+ process.stdout.write(" ✓\n");
267
294
  updated += 1;
268
295
  }
269
296
  else {
297
+ process.stdout.write(" ✗\n");
270
298
  failed += 1;
271
299
  }
272
300
  }
@@ -388,7 +416,13 @@ program
388
416
  }
389
417
  else {
390
418
  try {
391
- execSync(`${getPythonBin(workspaceRoot)} -m pip install -q -r \"${reqPath}\"`, {
419
+ const pyBin = getPythonBin(workspaceRoot);
420
+ // Wrap both the python path and requirements path in quotes so
421
+ // Windows paths that contain spaces (e.g. "IGEL Automation July 2026")
422
+ // are treated as a single argument by the shell.
423
+ // --prefer-binary: use pre-built wheels instead of compiling from source
424
+ // (avoids Rust/Cargo requirement for packages like tokenizers, crawl4ai)
425
+ execSync(`"${pyBin}" -m pip install -q --prefer-binary -r "${reqPath}"`, {
392
426
  cwd: workspaceRoot, stdio: "inherit",
393
427
  });
394
428
  console.log(chalk.green(" ✓ Dependencies installed"));
@@ -415,29 +449,41 @@ program
415
449
  else {
416
450
  console.log(chalk.green(" ✓ Framework context files already up to date"));
417
451
  }
418
- // Step 2d — Copilot CLI enrichment pass (asks login if needed), then DB sync.
452
+ // Step 3d — Copilot CLI enrichment pass.
453
+ // Only run when --with-copilot / --with-all is explicitly requested.
454
+ // Skipped silently on plain `igel-qe init` to avoid unexpected long waits.
455
+ const runEnrichment = options.withCopilot || options.withAll;
419
456
  console.log(chalk.yellow("3d. Enriching context via GitHub Copilot CLI…"));
420
- try {
421
- const allContextFiles = listFrameworkContextFiles(workspaceRoot);
422
- if (allContextFiles.length === 0) {
423
- console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
424
- }
425
- else if (!isCopilotCliAvailable()) {
426
- console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
427
- }
428
- else if (!ensureCopilotLogin(workspaceRoot)) {
429
- console.log(chalk.yellow(" ⚠ Copilot login was not completed; skipping Copilot enrichment"));
430
- }
431
- else {
432
- const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
433
- console.log(chalk.green(` Copilot updated ${run.updated} context.md file(s)`));
434
- if (run.failed > 0) {
435
- console.log(chalk.yellow(` ⚠ Copilot failed to update ${run.failed} context.md file(s)`));
457
+ if (!runEnrichment) {
458
+ console.log(chalk.gray(" - Skipped (pass --with-copilot to enable Copilot context enrichment)"));
459
+ }
460
+ else {
461
+ try {
462
+ const allContextFiles = listFrameworkContextFiles(workspaceRoot);
463
+ if (allContextFiles.length === 0) {
464
+ console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
465
+ }
466
+ else if (!isCopilotCliAvailable()) {
467
+ console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
468
+ }
469
+ else if (!ensureCopilotLogin(workspaceRoot)) {
470
+ console.log(chalk.yellow(" Copilot login was not completed; skipping Copilot enrichment"));
471
+ }
472
+ else {
473
+ const cap = Math.min(allContextFiles.length, MAX_ENRICHMENT_FILES);
474
+ if (allContextFiles.length > MAX_ENRICHMENT_FILES) {
475
+ console.log(chalk.gray(` - ${allContextFiles.length} context files found; enriching first ${MAX_ENRICHMENT_FILES} (re-run to continue)`));
476
+ }
477
+ const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
478
+ console.log(chalk.green(` ✓ Copilot updated ${run.updated}/${cap} context.md file(s)`));
479
+ if (run.failed > 0) {
480
+ console.log(chalk.yellow(` ⚠ Copilot failed to update ${run.failed} context.md file(s)`));
481
+ }
436
482
  }
437
483
  }
438
- }
439
- catch (e) {
440
- console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
484
+ catch (e) {
485
+ console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
486
+ }
441
487
  }
442
488
  // Step 3 — write MCP config (default: Copilot when no explicit platform flags)
443
489
  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.12",
3
+ "version": "1.0.16",
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
@@ -232,37 +232,65 @@ function listFrameworkContextFiles(workspaceRoot: string): string[] {
232
232
  }
233
233
 
234
234
  function isCopilotCliAvailable(): boolean {
235
- const check = spawnSync("copilot", ["--version"], { encoding: "utf-8", shell: process.platform === "win32" });
236
- return check.status === 0;
235
+ // Use a short timeout so this never blocks if copilot hangs on startup.
236
+ const check = spawnSync("copilot", ["--version"], {
237
+ encoding: "utf-8",
238
+ shell: process.platform === "win32",
239
+ timeout: 10_000,
240
+ });
241
+ return check.status === 0 && !check.error;
237
242
  }
238
243
 
239
- function isCopilotAuthenticated(workspaceRoot: string): boolean {
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.
240
248
  const probe = spawnSync(
241
249
  "copilot",
242
- ["-C", workspaceRoot, "-p", "Reply with exactly OK.", "--allow-all-tools", "--no-color", "-s"],
243
- { encoding: "utf-8", shell: process.platform === "win32" },
250
+ ["auth", "status"],
251
+ {
252
+ encoding: "utf-8",
253
+ shell: process.platform === "win32",
254
+ timeout: 10_000, // 10 s max — simple status call should be instant
255
+ },
244
256
  );
245
- return probe.status === 0;
257
+ return probe.status === 0 && !probe.error;
246
258
  }
247
259
 
248
260
  function ensureCopilotLogin(workspaceRoot: string): boolean {
249
261
  if (!isCopilotCliAvailable()) return false;
250
- if (isCopilotAuthenticated(workspaceRoot)) return true;
262
+ if (isCopilotAuthenticated()) return true;
251
263
 
252
264
  console.log(chalk.yellow(" ⚠ GitHub Copilot CLI is not authenticated."));
253
265
  console.log(chalk.yellow(" → Please complete Copilot login to continue context enrichment."));
254
266
 
255
267
  const login = spawnSync("copilot", ["login"], { stdio: "inherit", encoding: "utf-8", shell: process.platform === "win32" });
256
268
  if (login.status !== 0) return false;
257
- return isCopilotAuthenticated(workspaceRoot);
269
+ return isCopilotAuthenticated();
258
270
  }
259
271
 
260
- function enrichWithCopilotCli(workspaceRoot: string, contextFiles: string[]): { updated: number; failed: number } {
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 } {
261
280
  let updated = 0;
262
281
  let failed = 0;
263
282
 
264
- for (const absPath of contextFiles) {
265
- const relPath = absPath.replace(`${workspaceRoot}/`, "");
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
+ const relPath = absPath.replace(`${workspaceRoot}/`, "").replace(`${workspaceRoot}\\`, "");
290
+
291
+ // Show per-file progress so the user knows work is happening.
292
+ process.stdout.write(` [${i + 1}/${total}] Enriching ${relPath}…`);
293
+
266
294
  const prompt = [
267
295
  `Update the markdown file at path '${relPath}' in place.`,
268
296
  "Goal: make it both human-understandable and LLM-ready for automation framework reasoning.",
@@ -284,12 +312,19 @@ function enrichWithCopilotCli(workspaceRoot: string, contextFiles: string[]): {
284
312
  "--no-color",
285
313
  "-s",
286
314
  ],
287
- { encoding: "utf-8", shell: process.platform === "win32" },
315
+ {
316
+ encoding: "utf-8",
317
+ shell: process.platform === "win32",
318
+ timeout: 120_000, // 2 min per file — prevents indefinite hangs
319
+ input: "", // close stdin immediately
320
+ },
288
321
  );
289
322
 
290
- if (result.status === 0) {
323
+ if (result.status === 0 && !result.error) {
324
+ process.stdout.write(" ✓\n");
291
325
  updated += 1;
292
326
  } else {
327
+ process.stdout.write(" ✗\n");
293
328
  failed += 1;
294
329
  }
295
330
  }
@@ -418,7 +453,13 @@ program
418
453
  console.log(chalk.yellow(" ⚠ requirements file not found; skipping Python dependency install"));
419
454
  } else {
420
455
  try {
421
- execSync(`${getPythonBin(workspaceRoot)} -m pip install -q -r \"${reqPath}\"`, {
456
+ const pyBin = getPythonBin(workspaceRoot);
457
+ // Wrap both the python path and requirements path in quotes so
458
+ // Windows paths that contain spaces (e.g. "IGEL Automation July 2026")
459
+ // are treated as a single argument by the shell.
460
+ // --prefer-binary: use pre-built wheels instead of compiling from source
461
+ // (avoids Rust/Cargo requirement for packages like tokenizers, crawl4ai)
462
+ execSync(`"${pyBin}" -m pip install -q --prefer-binary -r "${reqPath}"`, {
422
463
  cwd: workspaceRoot, stdio: "inherit",
423
464
  });
424
465
  console.log(chalk.green(" ✓ Dependencies installed"));
@@ -446,25 +487,36 @@ program
446
487
  }
447
488
 
448
489
 
449
- // Step 2d — Copilot CLI enrichment pass (asks login if needed), then DB sync.
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;
450
494
  console.log(chalk.yellow("3d. Enriching context via GitHub Copilot CLI…"));
451
- try {
452
- const allContextFiles = listFrameworkContextFiles(workspaceRoot);
453
- if (allContextFiles.length === 0) {
454
- console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
455
- } else if (!isCopilotCliAvailable()) {
456
- console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
457
- } else if (!ensureCopilotLogin(workspaceRoot)) {
458
- console.log(chalk.yellow(" ⚠ Copilot login was not completed; skipping Copilot enrichment"));
459
- } else {
460
- const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
461
- console.log(chalk.green(` Copilot updated ${run.updated} context.md file(s)`));
462
- if (run.failed > 0) {
463
- console.log(chalk.yellow(` ⚠ Copilot failed to update ${run.failed} context.md file(s)`));
495
+ if (!runEnrichment) {
496
+ console.log(chalk.gray(" - Skipped (pass --with-copilot to enable Copilot context enrichment)"));
497
+ } else {
498
+ try {
499
+ const allContextFiles = listFrameworkContextFiles(workspaceRoot);
500
+ if (allContextFiles.length === 0) {
501
+ console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
502
+ } else if (!isCopilotCliAvailable()) {
503
+ console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
504
+ } else if (!ensureCopilotLogin(workspaceRoot)) {
505
+ console.log(chalk.yellow(" Copilot login was not completed; skipping Copilot enrichment"));
506
+ } else {
507
+ const cap = Math.min(allContextFiles.length, MAX_ENRICHMENT_FILES);
508
+ if (allContextFiles.length > MAX_ENRICHMENT_FILES) {
509
+ console.log(chalk.gray(` - ${allContextFiles.length} context files found; enriching first ${MAX_ENRICHMENT_FILES} (re-run to continue)`));
510
+ }
511
+ const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
512
+ console.log(chalk.green(` ✓ Copilot updated ${run.updated}/${cap} context.md file(s)`));
513
+ if (run.failed > 0) {
514
+ console.log(chalk.yellow(` ⚠ Copilot failed to update ${run.failed} context.md file(s)`));
515
+ }
464
516
  }
517
+ } catch (e: unknown) {
518
+ console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
465
519
  }
466
- } catch (e: unknown) {
467
- console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
468
520
  }
469
521
 
470
522
  // Step 3 — write MCP config (default: Copilot when no explicit platform flags)