igel-qe-core 1.0.14 → 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
@@ -229,34 +229,43 @@ function isCopilotCliAvailable() {
229
229
  });
230
230
  return check.status === 0 && !check.error;
231
231
  }
232
- function isCopilotAuthenticated(workspaceRoot) {
233
- // A strict timeout prevents the init command from hanging forever when
234
- // Copilot waits for interactive input or a network response.
235
- const probe = spawnSync("copilot", ["-C", workspaceRoot, "-p", "Reply with exactly OK.", "--allow-all-tools", "--no-color", "-s"], {
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: 30_000, // 30 s max — return false if Copilot doesn't answer
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(workspaceRoot))
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(workspaceRoot);
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
- for (const absPath of contextFiles) {
259
- 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}…`);
260
269
  const prompt = [
261
270
  `Update the markdown file at path '${relPath}' in place.`,
262
271
  "Goal: make it both human-understandable and LLM-ready for automation framework reasoning.",
@@ -280,10 +289,12 @@ function enrichWithCopilotCli(workspaceRoot, contextFiles) {
280
289
  timeout: 120_000, // 2 min per file — prevents indefinite hangs
281
290
  input: "", // close stdin immediately
282
291
  });
283
- if (result.status === 0) {
292
+ if (result.status === 0 && !result.error) {
293
+ process.stdout.write(" ✓\n");
284
294
  updated += 1;
285
295
  }
286
296
  else {
297
+ process.stdout.write(" ✗\n");
287
298
  failed += 1;
288
299
  }
289
300
  }
@@ -409,7 +420,9 @@ program
409
420
  // Wrap both the python path and requirements path in quotes so
410
421
  // Windows paths that contain spaces (e.g. "IGEL Automation July 2026")
411
422
  // are treated as a single argument by the shell.
412
- execSync(`"${pyBin}" -m pip install -q -r "${reqPath}"`, {
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}"`, {
413
426
  cwd: workspaceRoot, stdio: "inherit",
414
427
  });
415
428
  console.log(chalk.green(" ✓ Dependencies installed"));
@@ -436,29 +449,41 @@ program
436
449
  else {
437
450
  console.log(chalk.green(" ✓ Framework context files already up to date"));
438
451
  }
439
- // 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;
440
456
  console.log(chalk.yellow("3d. Enriching context via GitHub Copilot CLI…"));
441
- try {
442
- const allContextFiles = listFrameworkContextFiles(workspaceRoot);
443
- if (allContextFiles.length === 0) {
444
- console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
445
- }
446
- else if (!isCopilotCliAvailable()) {
447
- console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
448
- }
449
- else if (!ensureCopilotLogin(workspaceRoot)) {
450
- console.log(chalk.yellow(" ⚠ Copilot login was not completed; skipping Copilot enrichment"));
451
- }
452
- else {
453
- const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
454
- console.log(chalk.green(` Copilot updated ${run.updated} context.md file(s)`));
455
- if (run.failed > 0) {
456
- 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
+ }
457
482
  }
458
483
  }
459
- }
460
- catch (e) {
461
- 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
+ }
462
487
  }
463
488
  // Step 3 — write MCP config (default: Copilot when no explicit platform flags)
464
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.14",
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
@@ -241,17 +241,17 @@ function isCopilotCliAvailable(): boolean {
241
241
  return check.status === 0 && !check.error;
242
242
  }
243
243
 
244
- function isCopilotAuthenticated(workspaceRoot: string): boolean {
245
- // A strict timeout prevents the init command from hanging forever when
246
- // Copilot waits for interactive input or a network response.
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
- ["-C", workspaceRoot, "-p", "Reply with exactly OK.", "--allow-all-tools", "--no-color", "-s"],
250
+ ["auth", "status"],
250
251
  {
251
252
  encoding: "utf-8",
252
253
  shell: process.platform === "win32",
253
- timeout: 30_000, // 30 s max — return false if Copilot doesn't answer
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,22 +259,38 @@ function isCopilotAuthenticated(workspaceRoot: string): boolean {
259
259
 
260
260
  function ensureCopilotLogin(workspaceRoot: string): boolean {
261
261
  if (!isCopilotCliAvailable()) return false;
262
- if (isCopilotAuthenticated(workspaceRoot)) return true;
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(workspaceRoot);
269
+ return isCopilotAuthenticated();
270
270
  }
271
271
 
272
- 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 } {
273
280
  let updated = 0;
274
281
  let failed = 0;
275
282
 
276
- for (const absPath of contextFiles) {
277
- 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
+
278
294
  const prompt = [
279
295
  `Update the markdown file at path '${relPath}' in place.`,
280
296
  "Goal: make it both human-understandable and LLM-ready for automation framework reasoning.",
@@ -304,9 +320,11 @@ function enrichWithCopilotCli(workspaceRoot: string, contextFiles: string[]): {
304
320
  },
305
321
  );
306
322
 
307
- if (result.status === 0) {
323
+ if (result.status === 0 && !result.error) {
324
+ process.stdout.write(" ✓\n");
308
325
  updated += 1;
309
326
  } else {
327
+ process.stdout.write(" ✗\n");
310
328
  failed += 1;
311
329
  }
312
330
  }
@@ -439,7 +457,9 @@ program
439
457
  // Wrap both the python path and requirements path in quotes so
440
458
  // Windows paths that contain spaces (e.g. "IGEL Automation July 2026")
441
459
  // are treated as a single argument by the shell.
442
- execSync(`"${pyBin}" -m pip install -q -r "${reqPath}"`, {
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}"`, {
443
463
  cwd: workspaceRoot, stdio: "inherit",
444
464
  });
445
465
  console.log(chalk.green(" ✓ Dependencies installed"));
@@ -467,25 +487,36 @@ program
467
487
  }
468
488
 
469
489
 
470
- // 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;
471
494
  console.log(chalk.yellow("3d. Enriching context via GitHub Copilot CLI…"));
472
- try {
473
- const allContextFiles = listFrameworkContextFiles(workspaceRoot);
474
- if (allContextFiles.length === 0) {
475
- console.log(chalk.yellow(" ⚠ No context.md files found for Copilot enrichment"));
476
- } else if (!isCopilotCliAvailable()) {
477
- console.log(chalk.yellow(" ⚠ Copilot CLI not installed; skipping Copilot enrichment"));
478
- } else if (!ensureCopilotLogin(workspaceRoot)) {
479
- console.log(chalk.yellow(" ⚠ Copilot login was not completed; skipping Copilot enrichment"));
480
- } else {
481
- const run = enrichWithCopilotCli(workspaceRoot, allContextFiles);
482
- console.log(chalk.green(` Copilot updated ${run.updated} context.md file(s)`));
483
- if (run.failed > 0) {
484
- 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
+ }
485
516
  }
517
+ } catch (e: unknown) {
518
+ console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
486
519
  }
487
- } catch (e: unknown) {
488
- console.log(chalk.yellow(` ⚠ Copilot context enrichment skipped: ${e instanceof Error ? e.message : String(e)}`));
489
520
  }
490
521
 
491
522
  // Step 3 — write MCP config (default: Copilot when no explicit platform flags)