@qubiit/lmagent 3.3.1 → 3.4.0

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/install.js CHANGED
@@ -29,13 +29,57 @@ const INIT_FILES = [
29
29
  // para evitar conflictos de contexto duplicado en agentes como Cursor y Zed que leen múltiples .md del raíz.
30
30
  ];
31
31
 
32
- const INIT_DIRS = [
32
+ // Directorios core a verificar en .agents/ (usado por doctor)
33
+ const CORE_DIRS = [
34
+ { src: 'skills', desc: 'Skills especializados' },
35
+ { src: 'rules', desc: 'Reglas de comportamiento' },
36
+ { src: 'workflows', desc: 'SOPs y Procedimientos' },
33
37
  { src: 'config', desc: 'Configuración del framework' },
34
- { src: 'templates', desc: 'Templates de proyecto' },
35
38
  { src: 'docs', desc: 'Documentación extendida' },
36
- { src: 'workflows', desc: 'SOPs y Procedimientos' },
39
+ { src: 'memory', desc: 'Contexto persistente' },
37
40
  ];
38
41
 
42
+ // HOME_PATHS: Rutas globales de configuración de cada agente en el HOME del usuario
43
+ // Se usan SOLO para DETECCIÓN (saber si el agente está instalado en el sistema)
44
+ // La instalación del framework siempre va al directorio del proyecto
45
+ const HOME_PATHS = {
46
+ 'cursor': ['.cursor'],
47
+ 'windsurf': ['.windsurf', '.codeium/windsurf'],
48
+ 'claude': ['.claude'],
49
+ 'cline': ['.cline'],
50
+ 'roo': ['.roo'],
51
+ 'vscode': ['.vscode'],
52
+ 'trae': ['.trae'],
53
+ 'zed': ['.config/zed'],
54
+ 'augment': ['.augment'],
55
+ 'gemini': ['.gemini'],
56
+ 'codex': ['.codex'],
57
+ 'continue': ['.continue'],
58
+ 'goose': ['.config/goose'],
59
+ 'junie': ['.junie'],
60
+ 'opencode': ['.opencode'],
61
+ 'openhands': ['.openhands'],
62
+ 'antigravity': ['.agent'],
63
+ };
64
+
65
+ // Helper: Detectar agentes instalados GLOBALMENTE en el HOME del usuario
66
+ function detectGlobalAgents() {
67
+ const homeDir = os.homedir();
68
+ const globallyDetected = new Set();
69
+
70
+ for (const [agentValue, paths] of Object.entries(HOME_PATHS)) {
71
+ for (const p of paths) {
72
+ const fullPath = path.join(homeDir, p);
73
+ if (fs.existsSync(fullPath)) {
74
+ globallyDetected.add(agentValue);
75
+ break;
76
+ }
77
+ }
78
+ }
79
+ return globallyDetected;
80
+ }
81
+
82
+
39
83
  // IDE_CONFIGS: Lista ÚNICA y DEDUPLICADA de todos los agentes soportados
40
84
  const IDE_CONFIGS = [
41
85
  // --- IDEs Principales (Auto-Detectados) ---
@@ -122,11 +166,12 @@ program.command('update')
122
166
  });
123
167
 
124
168
  program.command('init')
125
- .description('Inicializar proyecto con LMAgent (copia AGENTS.md y estructura base)')
169
+ .description('Inicializar proyecto con LMAgent (alias de install)')
126
170
  .option('-f, --force', 'Sobrescribir archivos existentes')
127
171
  .option('-y, --yes', 'No preguntar, instalar todo')
128
172
  .action((options) => {
129
- runInit(options);
173
+ console.log(chalk.blue('ℹ `init` es ahora alias de `install`. Ejecutando instalación unificada...'));
174
+ runInstall(options);
130
175
  });
131
176
 
132
177
  program.command('doctor')
@@ -435,106 +480,62 @@ async function runInstall(options) {
435
480
 
436
481
  const projectRoot = process.cwd();
437
482
 
483
+ // ── PASO 1: Desplegar Pilares (AGENTS.md) ──
438
484
  await deployCorePillars(options, projectRoot);
439
- const SOURCE_SKILLS = PACKAGE_SKILLS_DIR;
440
- const SOURCE_RULES = PACKAGE_RULES_DIR;
441
- const SOURCE_WORKFLOWS = PACKAGE_WORKFLOWS_DIR;
442
- const SOURCE_MEMORY = PACKAGE_MEMORY_DIR;
485
+
486
+ // ── PASO 2: Detección Automática de Agentes (Global + Proyecto) ──
487
+ // Detección global: busca agentes instalados en el HOME del usuario
488
+ const globalAgents = detectGlobalAgents();
489
+ // Detección en proyecto: busca marcadores en el directorio del proyecto
490
+ const detectedIdes = IDE_CONFIGS.filter(ide => {
491
+ if (ide.value === 'custom' || ide.value === 'generic') return false;
492
+ const markerInProject = ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile));
493
+ const rulesDirRoot = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir.split('/')[0]));
494
+ const installedGlobally = globalAgents.has(ide.value);
495
+ return markerInProject || rulesDirRoot || installedGlobally;
496
+ });
443
497
 
444
498
  let targetIdes = [];
445
499
  let selectedSkills = [];
446
500
  let selectedRules = [];
447
- let selectedWorkflows = []; // New
448
- let installMethod = 'symlink';
449
- let installTarget = 'project';
450
- let targetRoot = projectRoot;
501
+ let selectedWorkflows = [];
502
+ let installDirs = { config: true, templates: true, docs: true, memory: true };
451
503
 
452
504
  if (options.yes) {
505
+ // ── Modo No Interactivo ──
453
506
  console.log(chalk.yellow('⚡ Modo: No interactivo'));
454
- targetIdes = IDE_CONFIGS.filter(ide =>
455
- ide.value !== 'custom' && (fs.existsSync(path.join(projectRoot, ide.rulesDir ? ide.rulesDir.split('/')[0] : '')) || (ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile))))
456
- );
457
- if (targetIdes.length === 0 && options.force) {
458
- targetIdes = [IDE_CONFIGS.find(i => i.value === 'cursor')];
459
- }
460
- selectedSkills = getAllItems(SOURCE_SKILLS, true);
461
- selectedRules = getAllItems(SOURCE_RULES, false);
462
- selectedWorkflows = getAllItems(SOURCE_WORKFLOWS, false);
507
+ targetIdes = detectedIdes.length > 0 ? detectedIdes : [IDE_CONFIGS.find(i => i.value === 'cursor')];
508
+ selectedSkills = getAllItems(PACKAGE_SKILLS_DIR, true);
509
+ selectedRules = getAllItems(PACKAGE_RULES_DIR, false);
510
+ selectedWorkflows = getAllItems(PACKAGE_WORKFLOWS_DIR, false);
463
511
  } else {
512
+ // ── Modo Interactivo ──
464
513
  console.log(chalk.gray('================================================================'));
465
- console.log(chalk.cyan('🔹 Configuración de Instalación'));
514
+ console.log(chalk.cyan('🔹 Instalación Unificada LMAgent'));
466
515
  console.log(chalk.gray('================================================================'));
467
-
468
- // UX OPTIMIZATION: "Project First" & Windows Compat
469
- // 1. Detect Environment
470
- const isWindows = os.platform() === 'win32';
471
- installMethod = isWindows ? 'copy' : 'symlink';
472
- installTarget = 'project';
473
- targetRoot = projectRoot;
474
-
475
- // 2. Banner simplified
476
- console.log(`📍 Destino: ${chalk.green('Proyecto Actual')}`);
477
- console.log(`🔧 Método: ${chalk.green(isWindows ? 'Copy (Windows Safe)' : 'Symlink (Live Updates)')}`);
516
+ console.log(`📍 Destino: ${chalk.green(projectRoot)}`);
517
+ console.log(`🔧 Core: ${chalk.green('.agents/ (centralizado)')}`);
478
518
  console.log('');
479
519
 
480
- // 3. Auto-Detect IDEs — Detección DUAL: Proyecto + Sistema
481
- // Primero busca en el PROYECTO actual, luego en HOME del usuario.
482
- // HOME_PATHS se usa SOLO para detectar agentes instalados en el sistema (pre-selección).
483
- // La instalación siempre va al PROYECTO actual, nunca a HOME.
484
- const userHome = os.homedir();
485
- const HOME_PATHS = {
486
- 'cursor': ['.cursor'], 'windsurf': ['.windsurf', '.codeium/windsurf'],
487
- 'cline': ['.cline'], 'roo': ['.roo'],
488
- 'vscode': ['.vscode'], 'trae': ['.trae'], 'trae-cn': ['.trae-cn'],
489
- 'claude': ['.claude'], 'zed': ['.config/zed', '.zed'],
490
- 'antigravity': ['.gemini'], 'gemini': ['.gemini'],
491
- 'augment': ['.augment'], 'continue': ['.continue'], 'codex': ['.codex'],
492
- 'goose': ['.config/goose', '.goose'], 'junie': ['.junie'],
493
- 'kilo': ['.kilocode'], 'kiro': ['.kiro'], 'opencode': ['.opencode'],
494
- 'openhands': ['.openhands'], 'amp': ['.config/amp'],
495
- 'zencoder': ['.zencoder'], 'codebuddy': ['.codebuddy'],
496
- };
497
-
498
- const detectedIdes = IDE_CONFIGS.filter(ide => {
499
- if (ide.value === 'custom' || ide.value === 'generic') return false;
500
-
501
- // 1) Buscar en el PROYECTO actual
502
- const markerInProject = ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile));
503
- const rulesDirInProject = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir));
504
- const skillsDirInProject = ide.skillsDir && fs.existsSync(path.join(projectRoot, ide.skillsDir));
505
- const configInProject = ide.configFile && fs.existsSync(path.join(projectRoot, ide.configFile));
506
- const inProject = markerInProject || rulesDirInProject || skillsDirInProject || configInProject;
507
-
508
- // 2) Buscar en HOME del usuario (detectar agente instalado en el sistema)
509
- const homePaths = HOME_PATHS[ide.value] || [];
510
- const inSystem = homePaths.some(p => fs.existsSync(path.join(userHome, p)));
511
-
512
- return inProject || inSystem;
513
- });
514
-
515
- // 4. Smart Multi-Agent Auto-Detection Setup
520
+ // Auto-Detect IDEs
516
521
  if (detectedIdes.length === 0) {
517
- console.log(chalk.yellow('⚠️ No se detectaron agentes instalados en este entorno.'));
518
- console.log(chalk.blue('ℹ Instalando estructura estándar para Cursor por defecto.'));
522
+ console.log(chalk.yellow('⚠️ No se detectaron agentes en este proyecto.'));
523
+ console.log(chalk.blue('ℹ Se creará estructura base + Cursor por defecto.\n'));
519
524
  targetIdes = [IDE_CONFIGS.find(i => i.value === 'cursor')];
520
525
  } else {
521
- console.log(chalk.green(`\n🚀 ¡Agentes Detectados! (${detectedIdes.length})`));
522
526
  const names = detectedIdes.map(i => i.name).join(', ');
523
- console.log(chalk.cyan(` → Se aplicará integración aislada para: ${chalk.bold(names)}\n`));
527
+ console.log(chalk.green(`🚀 Agentes Detectados: ${chalk.bold(names)}\n`));
524
528
  targetIdes = detectedIdes;
525
529
  }
526
530
 
527
- const availableSkills = getAllItems(SOURCE_SKILLS, true);
528
- const availableRules = getAllItems(SOURCE_RULES, false);
529
- const availableWorkflows = getAllItems(SOURCE_WORKFLOWS, false);
530
- // Memory logic: usually just a directory, not individual items to select, but we can check if it exists
531
- const hasMemory = fs.existsSync(path.join(SOURCE_SKILLS, '../memory')); // Hacky relative check or use defined constant if available in scope
531
+ const availableSkills = getAllItems(PACKAGE_SKILLS_DIR, true);
532
+ const availableRules = getAllItems(PACKAGE_RULES_DIR, false);
533
+ const availableWorkflows = getAllItems(PACKAGE_WORKFLOWS_DIR, false);
532
534
 
533
- console.log('');
534
535
  const quickInstall = await inquirer.prompt([{
535
536
  type: 'confirm',
536
537
  name: 'all',
537
- message: '⚡ Instalación Rápida: ¿Instalar TODO (Skills, Rules, Workflows, Memory)?',
538
+ message: '⚡ ¿Instalar TODO (Skills, Rules, Workflows, Memory, Config, Docs)?',
538
539
  default: true
539
540
  }]);
540
541
 
@@ -542,480 +543,319 @@ async function runInstall(options) {
542
543
  selectedSkills = availableSkills;
543
544
  selectedRules = availableRules;
544
545
  selectedWorkflows = availableWorkflows;
545
- options.installMemory = true; // Flag to install memory
546
546
  } else {
547
- // Manual selection...
548
- // Seleccionar Skills
549
- console.log(chalk.bold('\n🔹 Skills Disponibles:'));
550
- const skillsAnswer = await inquirer.prompt([
551
- {
552
- type: 'checkbox',
553
- name: 'skills',
554
- message: 'Selecciona (Espacio para elegir, Enter para confirmar):',
555
- choices: availableSkills.map(s => ({ name: s, checked: true })),
556
- pageSize: 15
557
- }
558
- ]);
547
+ // Selección manual de componentes
548
+ const skillsAnswer = await inquirer.prompt([{
549
+ type: 'checkbox', name: 'skills',
550
+ message: '🧩 Skills:', pageSize: 15,
551
+ choices: availableSkills.map(s => ({ name: s, checked: true }))
552
+ }]);
559
553
  selectedSkills = skillsAnswer.skills;
560
554
 
561
- // Seleccionar Rules
562
- console.log(chalk.bold('\n🔹 Reglas Disponibles:'));
563
- const rulesAnswer = await inquirer.prompt([
564
- {
565
- type: 'checkbox',
566
- name: 'rules',
567
- message: 'Selecciona (Espacio para elegir, Enter para confirmar):',
568
- choices: availableRules.map(r => ({ name: r, checked: true })),
569
- pageSize: 15
570
- }
571
- ]);
555
+ const rulesAnswer = await inquirer.prompt([{
556
+ type: 'checkbox', name: 'rules',
557
+ message: '📜 Rules:', pageSize: 15,
558
+ choices: availableRules.map(r => ({ name: r, checked: true }))
559
+ }]);
572
560
  selectedRules = rulesAnswer.rules;
573
561
 
574
- // Seleccionar Workflows
575
- console.log(chalk.bold('\n🔹 Workflows Disponibles:'));
576
- const workflowsAnswer = await inquirer.prompt([
577
- {
578
- type: 'checkbox',
579
- name: 'workflows',
580
- message: 'Selecciona (Espacio para elegir, Enter para confirmar):',
581
- choices: availableWorkflows.map(w => ({ name: w, checked: true })),
582
- pageSize: 15
583
- }
584
- ]);
562
+ const workflowsAnswer = await inquirer.prompt([{
563
+ type: 'checkbox', name: 'workflows',
564
+ message: '🔄 Workflows:', pageSize: 15,
565
+ choices: availableWorkflows.map(w => ({ name: w, checked: true }))
566
+ }]);
585
567
  selectedWorkflows = workflowsAnswer.workflows;
586
568
 
587
- // Seleccionar Memory
588
- console.log(chalk.bold('\n🔹 Memoria (Contexto):'));
589
- const memoryAnswer = await inquirer.prompt([
590
- {
591
- type: 'confirm',
592
- name: 'memory',
593
- message: '¿Instalar estructura de Memoria (.agents/memory)?',
594
- default: true
595
- }
596
- ]);
597
- options.installMemory = memoryAnswer.memory;
569
+ const dirsAnswer = await inquirer.prompt([{
570
+ type: 'checkbox', name: 'dirs',
571
+ message: '📁 Directorios adicionales:',
572
+ choices: [
573
+ { name: 'config/ - Configuración del framework', value: 'config', checked: true },
574
+ { name: 'templates/ - Plantillas de proyecto', value: 'templates', checked: true },
575
+ { name: 'docs/ - Documentación', value: 'docs', checked: true },
576
+ { name: 'memory/ - Contexto persistente', value: 'memory', checked: true },
577
+ ]
578
+ }]);
579
+ installDirs = {
580
+ config: dirsAnswer.dirs.includes('config'),
581
+ templates: dirsAnswer.dirs.includes('templates'),
582
+ docs: dirsAnswer.dirs.includes('docs'),
583
+ memory: dirsAnswer.dirs.includes('memory'),
584
+ };
598
585
  }
599
586
 
600
-
601
-
602
- console.log('');
603
587
  const { confirm } = await inquirer.prompt([{
604
- type: 'confirm',
605
- name: 'confirm',
606
- message: '¿Proceder con la instalación?',
607
- default: true
588
+ type: 'confirm', name: 'confirm',
589
+ message: '¿Proceder con la instalación?', default: true
608
590
  }]);
609
591
  if (!confirm) return;
610
592
  }
611
593
 
612
- // --- AGGRESSIVE ROOT CLEANUP ---
613
- console.log(chalk.bold('\n🧹 Ejecutando limpieza de archivos root (Aislamiento de Agentes)...'));
614
- // Definimos TODOS los posibles entry points
615
- const allRootFiles = [
616
- '.cursorrules', '.windsurfrules', '.windsurfrules.md', '.continuerules',
617
- '.goosehints', '.roorules',
618
- 'CLAUDE.md', 'GEMINI.md', 'openclaw.json'
619
- ];
594
+ // ── PASO 3: Instalar CORE en .agents/ (ÚNICA ubicación) ──
595
+ const coreDir = path.join(projectRoot, '.agents');
596
+ if (!fs.existsSync(coreDir)) fs.mkdirSync(coreDir, { recursive: true });
620
597
 
621
- // Y determinamos cuáles deben existir según los agentes seleccionados
622
- const requiredRootFiles = new Set();
598
+ console.log(chalk.bold('\n📦 Instalando Core en .agents/ (centralizado):'));
623
599
 
624
- // AGENTS.md es siempre intocable
625
- requiredRootFiles.add('AGENTS.md');
600
+ // Skills
601
+ if (selectedSkills.length > 0) {
602
+ const targetDir = path.join(coreDir, 'skills');
603
+ if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
604
+ for (const skill of selectedSkills) {
605
+ const src = path.join(PACKAGE_SKILLS_DIR, skill);
606
+ const dest = path.join(targetDir, skill);
607
+ if (fs.existsSync(src)) {
608
+ copyRecursiveSync(src, dest, true);
609
+ }
610
+ }
611
+ console.log(` ${chalk.green('✔')} Skills: ${selectedSkills.length} instalados en .agents/skills/`);
612
+ }
626
613
 
627
- for (const ide of targetIdes) {
628
- if (ide.configFile && !ide.configFile.includes('/')) {
629
- requiredRootFiles.add(ide.configFile);
614
+ // Rules
615
+ if (selectedRules.length > 0) {
616
+ const targetDir = path.join(coreDir, 'rules');
617
+ if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
618
+ for (const rule of selectedRules) {
619
+ const src = path.join(PACKAGE_RULES_DIR, rule);
620
+ const dest = path.join(targetDir, rule);
621
+ if (fs.existsSync(src)) fs.copyFileSync(src, dest);
630
622
  }
631
- if (ide.markerFile && !ide.markerFile.includes('/')) {
632
- requiredRootFiles.add(ide.markerFile);
623
+ console.log(` ${chalk.green('✔')} Rules: ${selectedRules.length} instaladas en .agents/rules/`);
624
+ }
625
+
626
+ // Workflows
627
+ if (selectedWorkflows.length > 0) {
628
+ const targetDir = path.join(coreDir, 'workflows');
629
+ if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
630
+ for (const wf of selectedWorkflows) {
631
+ const src = path.join(PACKAGE_WORKFLOWS_DIR, wf);
632
+ const dest = path.join(targetDir, wf);
633
+ if (fs.existsSync(src)) fs.copyFileSync(src, dest);
633
634
  }
635
+ console.log(` ${chalk.green('✔')} Workflows: ${selectedWorkflows.length} instalados en .agents/workflows/`);
634
636
  }
635
637
 
636
- // Eliminamos todo lo que NO fue requerido
638
+ // Directorios adicionales (config, templates, docs, memory)
639
+ if (installDirs.config && fs.existsSync(PACKAGE_CONFIG_DIR)) {
640
+ copyRecursiveSync(PACKAGE_CONFIG_DIR, path.join(coreDir, 'config'), true);
641
+ console.log(` ${chalk.green('✔')} Config copiado a .agents/config/`);
642
+ }
643
+ if (installDirs.templates && fs.existsSync(PACKAGE_TEMPLATES_DIR)) {
644
+ copyRecursiveSync(PACKAGE_TEMPLATES_DIR, path.join(coreDir, 'templates'), true);
645
+ console.log(` ${chalk.green('✔')} Templates copiado a .agents/templates/`);
646
+ }
647
+ if (installDirs.docs && fs.existsSync(PACKAGE_DOCS_DIR)) {
648
+ copyRecursiveSync(PACKAGE_DOCS_DIR, path.join(coreDir, 'docs'), true);
649
+ console.log(` ${chalk.green('✔')} Docs copiado a .agents/docs/`);
650
+ }
651
+ if (installDirs.memory && fs.existsSync(PACKAGE_MEMORY_DIR)) {
652
+ const memTarget = path.join(coreDir, 'memory');
653
+ if (!fs.existsSync(memTarget)) {
654
+ copyRecursiveSync(PACKAGE_MEMORY_DIR, memTarget, true);
655
+ console.log(` ${chalk.green('✔')} Memory copiado a .agents/memory/`);
656
+ } else {
657
+ // Solo copiar archivos que no existan (no sobrescribir contexto del usuario)
658
+ const memFiles = fs.readdirSync(PACKAGE_MEMORY_DIR);
659
+ let newCount = 0;
660
+ for (const mf of memFiles) {
661
+ const dest = path.join(memTarget, mf);
662
+ if (!fs.existsSync(dest)) {
663
+ fs.copyFileSync(path.join(PACKAGE_MEMORY_DIR, mf), dest);
664
+ newCount++;
665
+ }
666
+ }
667
+ console.log(` ${chalk.cyan('ℹ')} Memory: ${newCount} nuevos (existentes preservados)`);
668
+ }
669
+ }
670
+
671
+ // ── PASO 4: Limpieza de Root ──
672
+ console.log(chalk.bold('\n🧹 Limpieza de archivos root:'));
673
+ const allRootFiles = [
674
+ '.cursorrules', '.windsurfrules', '.windsurfrules.md', '.continuerules',
675
+ '.goosehints', '.roorules'
676
+ ];
677
+ const requiredRootFiles = new Set(['AGENTS.md']);
678
+ for (const ide of targetIdes) {
679
+ if (ide.configFile && !ide.configFile.includes('/')) requiredRootFiles.add(ide.configFile);
680
+ }
637
681
  for (const file of allRootFiles) {
638
682
  if (!requiredRootFiles.has(file)) {
639
- const filePath = path.join(targetRoot, file);
683
+ const filePath = path.join(projectRoot, file);
640
684
  if (fs.existsSync(filePath)) {
641
685
  try {
642
686
  fs.unlinkSync(filePath);
643
- console.log(` ${chalk.green('✔')} Eliminado root config obsoleto o conflictivo: ${chalk.cyan(file)}`);
687
+ console.log(` ${chalk.green('✔')} Eliminado: ${chalk.cyan(file)}`);
644
688
  } catch (e) {
645
- console.error(` ${chalk.red('❌')} Error al eliminar ${file}: ${e.message}`);
689
+ console.error(` ${chalk.red('❌')} Error eliminando ${file}: ${e.message}`);
646
690
  }
647
691
  }
648
692
  }
649
693
  }
650
694
 
651
- console.log('');
652
- for (const ide of targetIdes) {
653
- let currentInstallMethod = installMethod;
654
- if (ide.forceCopy && currentInstallMethod === 'symlink') {
655
- console.log(chalk.yellow(`⚠️ ${ide.name} detectado: Forzando método 'copy' (Mejor compatibilidad)`));
656
- currentInstallMethod = 'copy';
657
- }
658
-
695
+ // ── PASO 5: Desplegar Bridge Files y ConfigFiles por Agente ──
696
+ console.log(chalk.bold('\n🔗 Configurando Agentes Detectados:'));
697
+ const skillCount = selectedSkills.length;
698
+ const ruleCount = selectedRules.length;
699
+ const wfCount = selectedWorkflows.length;
659
700
 
660
- if (selectedSkills.length > 0 && ide.skillsDir) {
661
- const targetDir = path.join(targetRoot, ide.skillsDir);
662
- console.log(chalk.bold(`\nInstalling Skills to ${chalk.cyan(targetDir)}:`));
663
-
664
- try {
665
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
666
-
667
- for (const skill of selectedSkills) {
668
- const srcFolder = path.join(SOURCE_SKILLS, skill);
669
- const destFolder = path.join(targetDir, skill);
670
-
671
- if (fs.existsSync(srcFolder)) {
672
- await applyFile(srcFolder, destFolder, currentInstallMethod);
673
- console.log(` ${chalk.green('✔')} ${skill}/`);
674
- }
701
+ for (const ide of targetIdes) {
702
+ // 5a. Limpiar skills/rules/workflows legacy copiados en carpetas de agente
703
+ if (ide.value !== 'generic' && ide.value !== 'amp') {
704
+ const legacyDirs = [ide.skillsDir, ide.workflowsDir].filter(d => d && !arePathsEqual(path.join(projectRoot, d), path.join(coreDir, 'skills')) && !arePathsEqual(path.join(projectRoot, d), path.join(coreDir, 'workflows')));
705
+ for (const ld of legacyDirs) {
706
+ const legacyPath = path.join(projectRoot, ld);
707
+ if (fs.existsSync(legacyPath)) {
708
+ try {
709
+ fs.rmSync(legacyPath, { recursive: true, force: true });
710
+ console.log(` ${chalk.yellow('🗑')} ${ide.name}: Eliminado ${ld}/ (legacy, ahora en .agents/)`);
711
+ } catch (e) { }
675
712
  }
676
- } catch (e) {
677
- console.error(chalk.red(`❌ Error installing skills for ${ide.name}: ${e.message}`));
678
713
  }
679
714
  }
680
715
 
681
- // 4. Generate/Update Global Config File (Bootstrap)
682
- let bootstrapStatus = 'SKIP';
716
+ // 5b. ConfigFile (CLAUDE.md, GEMINI.md, etc.)
683
717
  if (ide.configFile) {
684
- // Safety: Don't inject Markdown into JSON/YAML
685
718
  if (ide.configFile.endsWith('.json') || ide.configFile.endsWith('.yaml') || ide.configFile.endsWith('.yml')) {
686
- // console.log(chalk.gray(` ℹ Skipping bootstrap for ${ide.name} (Structured Config)`));
687
- bootstrapStatus = 'SKIP';
719
+ // Structured configs: use template if exists
720
+ const AGENT_CONFIGS_TEMPLATE_DIR = path.join(__dirname, '.agents', 'templates', 'agent-configs');
721
+ const templateFile = ide.configTemplate ? path.join(AGENT_CONFIGS_TEMPLATE_DIR, ide.configTemplate) : null;
722
+ if (templateFile && fs.existsSync(templateFile)) {
723
+ const configPath = path.join(projectRoot, ide.configFile);
724
+ if (!fs.existsSync(configPath) || options.force) {
725
+ const content = fs.readFileSync(templateFile, 'utf8')
726
+ .replace(/\{\{VERSION\}\}/g, PKG_VERSION)
727
+ .replace(/\{\{MAJOR\}\}/g, PKG_VERSION.split('.')[0]);
728
+ if (!fs.existsSync(path.dirname(configPath))) fs.mkdirSync(path.dirname(configPath), { recursive: true });
729
+ fs.writeFileSync(configPath, content);
730
+ console.log(` ${chalk.green('✔')} ${ide.name}: ${ide.configFile} (config)`);
731
+ }
732
+ }
688
733
  } else {
689
- const configPath = path.join(targetRoot, ide.configFile);
690
- const relativeRulesPath = getRelLink(ide.configFile, '.agents/rules/00-master.md');
691
- const relativeCatalogPath = getRelLink(ide.configFile, 'AGENTS.md');
692
- const relativeContextPath = getRelLink(ide.configFile, 'CLAUDE.md');
693
-
694
- // Usar template específico del agente si existe, si no usar contenido genérico
734
+ // Markdown configs (CLAUDE.md, GEMINI.md, copilot-instructions, etc.)
735
+ const configPath = path.join(projectRoot, ide.configFile);
695
736
  const AGENT_CONFIGS_TEMPLATE_DIR = path.join(__dirname, '.agents', 'templates', 'agent-configs');
696
737
  const templateFile = ide.configTemplate
697
738
  ? path.join(AGENT_CONFIGS_TEMPLATE_DIR, ide.configTemplate)
698
739
  : path.join(AGENT_CONFIGS_TEMPLATE_DIR, '_generic.md');
699
740
 
700
741
  let content;
701
- if (fs.existsSync(templateFile) && !ide.configFile.endsWith('.json')) {
702
- // Usar template del archivo, inyectando VERSION
703
- content = fs.readFileSync(templateFile, 'utf8')
704
- .replace(/\{\{VERSION\}\}/g, PKG_VERSION)
705
- .replace(/\{\{MAJOR\}\}/g, PKG_VERSION.split('.')[0])
706
- .replace(/\{\{SKILLS_DIR\}\}/g, ide.skillsDir || '.agents/skills')
707
- .replace(/\{\{RULES_DIR\}\}/g, ide.rulesDir || '.agents/rules')
708
- .replace(/\{\{WORKFLOWS_DIR\}\}/g, ide.workflowsDir || '.agents/workflows');
709
- } else if (ide.configFile.endsWith('.json') && fs.existsSync(templateFile)) {
710
- // JSON: usar template y reemplazar VERSION
742
+ if (fs.existsSync(templateFile)) {
711
743
  content = fs.readFileSync(templateFile, 'utf8')
712
744
  .replace(/\{\{VERSION\}\}/g, PKG_VERSION)
713
745
  .replace(/\{\{MAJOR\}\}/g, PKG_VERSION.split('.')[0])
714
- .replace(/\{\{SKILLS_DIR\}\}/g, ide.skillsDir || '.agents/skills')
715
- .replace(/\{\{RULES_DIR\}\}/g, ide.rulesDir || '.agents/rules')
716
- .replace(/\{\{WORKFLOWS_DIR\}\}/g, ide.workflowsDir || '.agents/workflows');
746
+ .replace(/\{\{SKILLS_DIR\}\}/g, '.agents/skills')
747
+ .replace(/\{\{RULES_DIR\}\}/g, '.agents/rules')
748
+ .replace(/\{\{WORKFLOWS_DIR\}\}/g, '.agents/workflows');
717
749
  } else {
718
- // Fallback: contenido genérico dinámico
719
- content = `
720
- # 🤖 LMAgent Framework v${PKG_VERSION}
721
- > Contexto Activo: Este proyecto utiliza el estándar LMAgent V${PKG_VERSION.split('.')[0]}.
722
-
723
- ## 🚨 SOURCE OF TRUTH (CEREBRO)
724
- **TU CONTEXTO Y REGLAS VIVEN AQUÍ 👉 [AGENTS.md](${relativeCatalogPath})**
725
- *Lee este archivo INMEDIATAMENTE para obtener tu identidad, skills y reglas operativas.*
726
-
727
- ## 🔄 ARRANQUE AUTOMÁTICO (Haz esto al iniciar cada sesión)
728
- 1. Lee [AGENTS.md](${relativeCatalogPath}) — Tu catálogo completo de capacidades
729
- 2. Lee \`.agents/rules/00-master.md\` — Reglas y protocolo de trabajo
730
- 3. Si existe \`.agents/memory/04-active-context.md\` — Recupera contexto previo
731
- 4. Clasifica la tarea (Nivel 0-4) y activa el skill apropiado
732
-
733
- ## 📁 RUTAS DE ENTORNO
734
- - **Skills**: \`${ide.skillsDir || '.agents/skills'}\` (31 skills disponibles)
735
- - **Rules**: \`${ide.rulesDir || '.agents/rules'}\` (11 reglas)
736
- - **Workflows**: \`${ide.workflowsDir || '.agents/workflows'}\` (13 SOPs)
737
- - **Memory**: \`.agents/memory/\`
738
- - **Config**: \`.agents/config/\`
739
-
740
- ## ⚡ QUICK START TRIGGERS (Menu Rápido)
741
- Usa estos comandos para activar un rol. Para el catálogo completo de 31 skills, consulta \`AGENTS.md\`.
742
-
743
- | Trigger | Rol / Skill | Objetivo |
744
- |:--- |:--- |:--- |
745
- | \`/orch\` | **Orchestrator** | Clasificar y delegar. |
746
- | \`/dev\` | **Backend** | APIs y Lógica. |
747
- | \`/front\` | **Frontend** | UI/UX, React. |
748
- | \`/pm\` | **Product** | PRDs y Roadmap. |
749
- | \`/fix\` | **Debugger** | Análisis de bugs. |
750
- | \`/arch\` | **Architect** | Diseño de sistemas. |
751
-
752
- > **IMPORTANTE**: Para activar un skill, lee su \`SKILL.md\` completo en \`${ide.skillsDir || '.agents/skills'}/[nombre]/SKILL.md\`.
750
+ content = generateMinimalConfig(ide, PKG_VERSION);
751
+ }
753
752
 
754
- !! SYSTEM NOTE: You MUST read AGENTS.md at startup to understand the full framework. !!
755
- `;
753
+ // Si es un archivo raíz (CLAUDE.md, GEMINI.md), usar contenido del paquete
754
+ if (!ide.configFile.includes('/')) {
755
+ const srcFile = path.join(__dirname, ide.configFile);
756
+ if (fs.existsSync(srcFile)) {
757
+ content = fs.readFileSync(srcFile, 'utf8')
758
+ .replace(/\{\{VERSION\}\}/g, PKG_VERSION)
759
+ .replace(/\{\{MAJOR\}\}/g, PKG_VERSION.split('.')[0]);
760
+ }
756
761
  }
757
- // If file exists, check if we need to append
762
+
758
763
  try {
759
- if (fs.existsSync(configPath)) {
760
- // Check if it's a directory (Edge case: Cline legacy folders)
761
- if (fs.statSync(configPath).isDirectory()) {
762
- console.error(chalk.red(` ❌ Cannot bootstrap ${ide.configFile}: Is a directory.`));
763
- bootstrapStatus = 'ERROR';
764
+ if (!fs.existsSync(path.dirname(configPath))) fs.mkdirSync(path.dirname(configPath), { recursive: true });
765
+ if (!fs.existsSync(configPath) || options.force) {
766
+ fs.writeFileSync(configPath, content);
767
+ console.log(` ${chalk.green('✔')} ${ide.name}: ${ide.configFile}`);
768
+ } else {
769
+ const existingContent = fs.readFileSync(configPath, 'utf8');
770
+ if (!existingContent.includes('AGENTS.md')) {
771
+ fs.appendFileSync(configPath, '\n' + content);
772
+ console.log(` ${chalk.blue('ℹ')} ${ide.name}: ${ide.configFile} (actualizado)`);
764
773
  } else {
765
- const existingContent = fs.readFileSync(configPath, 'utf8');
766
- if (!existingContent.includes('QUICK START TRIGGERS')) {
767
- fs.appendFileSync(configPath, '\n' + content);
768
- bootstrapStatus = 'UPDATED';
769
- } else {
770
- bootstrapStatus = 'OK';
771
- }
774
+ console.log(` ${chalk.cyan('')} ${ide.name}: ${ide.configFile} (OK)`);
772
775
  }
773
- } else {
774
- // Create parent dir if needed (for .github/copilot... etc)
775
- if (!fs.existsSync(path.dirname(configPath))) fs.mkdirSync(path.dirname(configPath), { recursive: true });
776
- fs.writeFileSync(configPath, content);
777
- bootstrapStatus = 'CREATED';
778
776
  }
779
777
  } catch (e) {
780
- console.error(chalk.red(` ❌ Error bootstrapping ${ide.name}: ${e.message}`));
781
- bootstrapStatus = 'ERROR';
778
+ console.error(chalk.red(` ❌ Error con ${ide.configFile}: ${e.message}`));
782
779
  }
783
780
  }
784
781
  }
785
782
 
786
- if (bootstrapStatus !== 'SKIP' && bootstrapStatus !== 'OK') {
787
- console.log(` ${bootstrapStatus === 'CREATED' ? chalk.green('✔') : chalk.blue('ℹ')} ${ide.name} Bootstrap: ${bootstrapStatus}`);
788
- }
789
-
790
- // 4.1 Generate Bridge Rule
791
- // Si el agente NO tiene configFile, necesita un archivo puente en rulesDir para auto-invocarse.
792
- // Si tampoco tiene bridgeFile definido, usamos '00-lmagent.md' como default genérico.
783
+ // 5c. Bridge File (ligero, solo para agentes sin configFile)
793
784
  const bridgeFile = ide.bridgeFile || (ide.rulesDir && !ide.configFile ? '00-lmagent.md' : null);
794
- const needsBridge = bridgeFile && !ide.configFile;
795
- // Garantizar que rulesDir existe siempre (para que el agente pueda detectar la instalación)
796
- if (ide.rulesDir && !fs.existsSync(path.join(targetRoot, ide.rulesDir))) {
797
- fs.mkdirSync(path.join(targetRoot, ide.rulesDir), { recursive: true });
798
- }
799
- if (ide.rulesDir && needsBridge) {
800
- const bridgePath = path.join(targetRoot, ide.rulesDir, bridgeFile);
801
- const relativeBridgeToRoot = path.join(ide.rulesDir, bridgeFile);
802
- // Usar entry point universal (AGENTS.md) en vez de hardcodear CLAUDE.md
803
- const agentEntryPoint = ide.configFile || 'AGENTS.md';
804
- const relContext = getRelLink(relativeBridgeToRoot, agentEntryPoint);
805
- const relCatalog = getRelLink(relativeBridgeToRoot, 'AGENTS.md');
806
- const relRules = getRelLink(relativeBridgeToRoot, '.agents/rules/00-master.md');
807
- const relMemory = getRelLink(relativeBridgeToRoot, '.agents/memory/04-active-context.md');
808
-
809
- let bridgeContent = '';
785
+ if (bridgeFile && !ide.configFile) {
786
+ if (ide.rulesDir && !fs.existsSync(path.join(projectRoot, ide.rulesDir))) {
787
+ fs.mkdirSync(path.join(projectRoot, ide.rulesDir), { recursive: true });
788
+ }
789
+ const bridgePath = path.join(projectRoot, ide.rulesDir, bridgeFile);
790
+ const relCatalog = getRelLink(path.join(ide.rulesDir, bridgeFile), 'AGENTS.md');
810
791
 
792
+ let bridgeContent;
811
793
  if (bridgeFile.endsWith('.mdc')) {
812
- // Cursor MDC Format
813
794
  bridgeContent = `---
814
- description: LMAgent Framework Entry Point - Use this rule to understand how to interact with the project skills and rules.
795
+ description: LMAgent Framework Entry Point - Read AGENTS.md for full context
815
796
  globs: **/*
816
797
  ---
817
798
 
818
- # 🤖 LMAgent Bridge Rule
819
-
820
- Este proyecto está potenciado por **LMAgent v${PKG_VERSION}**.
799
+ # 🤖 LMAgent v${PKG_VERSION}
800
+ > **LEE [AGENTS.md](${relCatalog})** para obtener tu contexto completo.
821
801
 
822
- ## 🚨 SOURCE OF TRUTH (CEREBRO)
823
- **TU CONTEXTO Y REGLAS VIVEN AQUÍ 👉 [AGENTS.md](${relCatalog})**
824
- *Lee este archivo INMEDIATAMENTE para obtener tu identidad, skills y reglas operativas.*
825
-
826
- ## 🔄 ARRANQUE AUTOMÁTICO (Haz esto al iniciar)
827
- 1. Lee [AGENTS.md](${relCatalog}) — Tu catálogo completo de capacidades
828
- 2. Lee [00-master.md](${relRules}) — Reglas y protocolo de trabajo
829
- 3. Si existe [04-active-context.md](${relMemory}) — Léelo para recuperar contexto de la sesión anterior
830
- 4. Clasifica la tarea (Nivel 0-4) y activa el skill apropiado
831
-
832
- ## 📁 RUTAS DE ENTORNO
833
- - **Tus Skills**: \`${ide.skillsDir || '.agents/skills'}\` (31 skills disponibles)
834
- - **Tus Rules**: \`${ide.rulesDir || '.agents/rules'}\` (11 reglas)
835
- - **Tus Workflows**: \`${ide.workflowsDir || '.agents/workflows'}\` (13 SOPs)
836
- - **Memory**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/memory') : '.agents/memory'}\`
837
- - **Config**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/config') : '.agents/config'}\`
838
-
839
- ## ⚡ QUICK START TRIGGERS (Menu Rápido)
840
- Usa estos comandos para activar un rol. Para el catálogo completo de 31 skills, consulta \`AGENTS.md\`.
841
-
842
- | Trigger | Rol / Skill | Objetivo |
843
- |:--- |:--- |:--- |
844
- | \`/orch\` | **Orchestrator** | Clasificar y delegar. |
845
- | \`/dev\` | **Backend** | APIs y Lógica. |
846
- | \`/front\` | **Frontend** | UI/UX, React. |
847
- | \`/pm\` | **Product** | PRDs y Roadmap. |
848
- | \`/fix\` | **Debugger** | Análisis de bugs. |
849
- | \`/arch\` | **Architect** | Diseño de sistemas. |
850
-
851
- > **IMPORTANTE**: Para activar un skill, lee su \`SKILL.md\` completo en \`${ide.skillsDir || '.agents/skills'}/[nombre]/SKILL.md\`.
802
+ ## Rutas del Framework
803
+ - **Skills**: \`.agents/skills/\` (${skillCount} skills)
804
+ - **Rules**: \`.agents/rules/\` (${ruleCount} reglas)
805
+ - **Workflows**: \`.agents/workflows/\` (${wfCount} workflows)
806
+ - **Memory**: \`.agents/memory/\`
852
807
 
853
- !! SYSTEM NOTE: You MUST read AGENTS.md at startup to understand the full framework. !!
808
+ !! SYSTEM NOTE: You MUST read AGENTS.md at startup. !!
854
809
  `;
855
810
  } else {
856
- // Standard Markdown (Universal & Cline/Windsurf)
857
- bridgeContent = `# 🤖 LMAgent Framework Entry Point
858
-
859
- Este proyecto utiliza **LMAgent v${PKG_VERSION}**.
860
-
861
- ## 🚨 SOURCE OF TRUTH (CEREBRO)
862
- **TU CONTEXTO Y REGLAS VIVEN AQUÍ 👉 [AGENTS.md](${relCatalog})**
863
- *Lee este archivo INMEDIATAMENTE para obtener tu identidad, skills y reglas operativas.*
864
-
865
- ## 🔄 ARRANQUE AUTOMÁTICO (Haz esto al iniciar)
866
- 1. Lee [AGENTS.md](${relCatalog}) — Tu catálogo completo de capacidades
867
- 2. Lee [00-master.md](${relRules}) — Reglas y protocolo de trabajo
868
- 3. Si existe [04-active-context.md](${relMemory}) — Léelo para recuperar contexto de la sesión anterior
869
- 4. Clasifica la tarea (Nivel 0-4) y activa el skill apropiado
870
-
871
- ## 📁 RUTAS DE ENTORNO
872
- - **Tus Skills**: \`${ide.skillsDir || '.agents/skills'}\` (31 skills disponibles)
873
- - **Tus Rules**: \`${ide.rulesDir || '.agents/rules'}\` (11 reglas)
874
- - **Tus Workflows**: \`${ide.workflowsDir || '.agents/workflows'}\` (13 SOPs)
875
- - **Memory**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/memory') : '.agents/memory'}\`
876
- - **Config**: \`${ide.skillsDir ? ide.skillsDir.replace(/\/[^/]+$/, '/config') : '.agents/config'}\`
877
-
878
- ## ⚡ QUICK START TRIGGERS (Menu Rápido)
879
- Usa estos comandos para activar un rol. Para el catálogo completo de 31 skills, consulta \`AGENTS.md\`.
880
-
881
- | Trigger | Rol / Skill | Objetivo |
882
- |:--- |:--- |:--- |
883
- | \`/orch\` | **Orchestrator** | Clasificar y delegar. |
884
- | \`/dev\` | **Backend** | APIs y Lógica. |
885
- | \`/front\` | **Frontend** | UI/UX, React. |
886
- | \`/pm\` | **Product** | PRDs y Roadmap. |
887
- | \`/fix\` | **Debugger** | Análisis de bugs. |
888
- | \`/arch\` | **Architect** | Diseño de sistemas. |
889
-
890
- > **IMPORTANTE**: Para activar un skill, lee su \`SKILL.md\` completo en \`${ide.skillsDir || '.agents/skills'}/[nombre]/SKILL.md\`.
891
- `;
892
- }
811
+ bridgeContent = `# 🤖 LMAgent v${PKG_VERSION}
812
+ > **LEE [AGENTS.md](${relCatalog})** para obtener tu contexto completo.
893
813
 
894
- // CLEANUP: Legacy Cursor Skills Directory
895
- // Since we moved skills to .cursor/rules/skills, we must remove .cursor/skills to avoid duplicates
896
- if (ide.value === 'cursor') {
897
- const legacySkillsDir = path.join(targetRoot, '.cursor/skills');
898
- if (fs.existsSync(legacySkillsDir)) {
899
- try {
900
- fs.rmSync(legacySkillsDir, { recursive: true, force: true });
901
- console.log(` ${chalk.yellow('🗑 Eliminado directorio obsoleto:')} .cursor / skills(Movido a.cursor / rules / skills)`);
902
- } catch (e) {
903
- console.error(chalk.red(` ⚠️ No se pudo eliminar.cursor / skills: ${e.message} `));
904
- }
905
- }
814
+ ## Rutas del Framework
815
+ - **Skills**: \`.agents/skills/\` (${skillCount} skills)
816
+ - **Rules**: \`.agents/rules/\` (${ruleCount} reglas)
817
+ - **Workflows**: \`.agents/workflows/\` (${wfCount} workflows)
818
+ - **Memory**: \`.agents/memory/\`
819
+ `;
906
820
  }
907
821
 
908
822
  try {
909
823
  if (!fs.existsSync(path.dirname(bridgePath))) fs.mkdirSync(path.dirname(bridgePath), { recursive: true });
910
824
  fs.writeFileSync(bridgePath, bridgeContent);
911
- console.log(` ${chalk.green('✔')} ${ide.name} Bridge Rule: ${bridgeFile} `);
825
+ console.log(` ${chalk.green('✔')} ${ide.name}: ${bridgeFile} (bridge)`);
912
826
  } catch (e) {
913
- console.error(chalk.red(` ❌ Error creating bridge for ${ide.name}: ${e.message} `));
914
- }
915
- }
916
- // 2. Install RULES (Files)
917
- if (selectedRules.length > 0 && ide.rulesDir) {
918
- const targetDir = path.join(targetRoot, ide.rulesDir);
919
- console.log(chalk.bold(`\nInstalling Rules to ${chalk.cyan(targetDir)}: `));
920
-
921
- try {
922
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
923
-
924
- // CLEANUP: Remove legacy rules (V2 & Duplicates)
925
- const legacyRules = [
926
- '_bootstrap.md', '_bootstrap.mdc', '00-bootstrap.md',
927
- 'agents-ia.md', 'stack.md', 'testing.md', 'security.md', 'code-style.md', 'documentation.md',
928
- 'workflow.md', 'api-design.md', 'automations-n8n.md', 'frontend.md', 'backend.md'
929
- ];
930
- for (const legacy of legacyRules) {
931
- const legacyPath = path.join(targetDir, legacy);
932
- if (fs.existsSync(legacyPath)) {
933
- fs.unlinkSync(legacyPath);
934
- console.log(` ${chalk.yellow('🗑 Eliminado regla obsoleta:')} ${legacy} `);
935
- }
936
- }
937
-
938
- for (const rule of selectedRules) {
939
- const srcVal = path.join(SOURCE_RULES, rule);
940
- const destVal = path.join(targetDir, rule);
941
-
942
- if (fs.existsSync(srcVal)) {
943
- await applyFile(srcVal, destVal, currentInstallMethod);
944
- console.log(` ${chalk.blue('✔')} ${rule} `);
945
- }
946
- }
947
- } catch (e) {
948
- console.error(chalk.red(`❌ Error installing rules for ${ide.name}: ${e.message} `));
949
- }
950
- }
951
-
952
- // 3. Install WORKFLOWS (Files)
953
- if (selectedWorkflows.length > 0 && ide.workflowsDir) {
954
- const targetDir = path.join(targetRoot, ide.workflowsDir);
955
- console.log(chalk.bold(`\nInstalling Workflows to ${chalk.cyan(targetDir)}: `));
956
-
957
- try {
958
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
959
-
960
- for (const wf of selectedWorkflows) {
961
- const srcVal = path.join(SOURCE_WORKFLOWS, wf);
962
- const destVal = path.join(targetDir, wf);
963
-
964
- if (fs.existsSync(srcVal)) {
965
- await applyFile(srcVal, destVal, currentInstallMethod);
966
- console.log(` ${chalk.magenta('✔')} ${wf} `);
967
- }
968
- }
969
- } catch (e) {
970
- console.error(chalk.red(`❌ Error installing workflows for ${ide.name}: ${e.message} `));
971
- }
972
- }
973
-
974
-
975
-
976
- if (SOURCE_MEMORY && ide.skillsDir) {
977
- const parentDir = path.dirname(ide.skillsDir);
978
- const targetDir = path.join(targetRoot, parentDir, 'memory');
979
- const targetDirLower = targetDir.toLowerCase();
980
- const sourceMemoryLower = SOURCE_MEMORY.toLowerCase();
981
-
982
- if (targetDirLower !== sourceMemoryLower) {
983
- try {
984
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
985
- copyRecursiveSync(SOURCE_MEMORY, targetDir, true);
986
- console.log(` ${chalk.cyan('✔')} Memory(Context) optimized.`);
987
- } catch (e) {
988
- console.error(chalk.red(`❌ Error installing memory for ${ide.name}: ${e.message} `));
989
- }
990
- } else {
991
- console.log(` ${chalk.cyan('ℹ')} Memory(Context) already in origin path.`);
827
+ console.error(chalk.red(` ❌ Error bridge ${ide.name}: ${e.message}`));
992
828
  }
993
829
  }
994
830
  }
995
831
 
996
- // 🔄 Sincronizar Catálogo de Skills en AGENTS.md y 00-master.md
997
- await syncSkillCatalog(targetRoot);
832
+ // ── PASO 6: Sincronizar Catálogo ──
833
+ await syncSkillCatalog(projectRoot);
998
834
 
835
+ // ── Resumen Final ──
999
836
  console.log(gradient.pastel.multiline('\n✨ Instalación Finalizada ✨'));
1000
-
1001
837
  console.log(chalk.gray('================================================================'));
1002
- console.log(chalk.bold.green('🎉 ¡Todo listo! Aquí tienes cómo usar tus nuevos superpoderes:'));
838
+ console.log(chalk.bold.green('🎉 ¡Todo listo!'));
1003
839
  console.log('');
1004
-
1005
- // Mensaje dinámico según agentes instalados
1006
840
  const ideNames = targetIdes.map(i => i.name).join(', ');
1007
- console.log(chalk.cyan(`🤖 Agentes configurados: ${chalk.bold(ideNames)} `));
841
+ console.log(chalk.cyan(`🤖 Agentes: ${chalk.bold(ideNames)}`));
842
+ console.log(chalk.cyan(`📦 Core: ${chalk.bold('.agents/')} (${skillCount} skills, ${ruleCount} rules, ${wfCount} workflows)`));
1008
843
  console.log('');
1009
- console.log(chalk.white(' 1. Abre tu agente en este proyecto — leerá el contexto automáticamente.'));
1010
- console.log(chalk.white(' 2. Usa los triggers para activar un rol específico:'));
1011
- console.log(chalk.gray(' /dev → Backend | /front → Frontend | /arch → Arquitecto'));
1012
- console.log(chalk.gray(' /fix → Debugger | /pm → Product | /orch → Orchestrator'));
844
+ console.log(chalk.white(' 1. Abre tu agente — leerá el contexto automáticamente.'));
845
+ console.log(chalk.white(' 2. Usa triggers: /dev /front /arch /fix /pm /orch'));
1013
846
  console.log('');
1014
- console.log(chalk.dim(' 💡 Ejecuta `lmagent doctor` para verificar la instalación.'));
1015
- console.log(chalk.dim(' 💡 Ejecuta `lmagent tokens` para ver el consumo de tokens del framework.'));
847
+ console.log(chalk.dim(' 💡 `lmagent doctor` para verificar | `lmagent tokens` para ver consumo'));
1016
848
  console.log(chalk.gray('================================================================'));
1017
849
  }
1018
850
 
851
+ // Helper: Genera config mínimo si no hay template
852
+ function generateMinimalConfig(ide, version) {
853
+ return `# 🤖 LMAgent Framework v${version}
854
+ > LEE [AGENTS.md](./AGENTS.md) para obtener tu contexto completo.
855
+ > Skills: \`.agents/skills/\` | Rules: \`.agents/rules/\` | Workflows: \`.agents/workflows/\`
856
+ `;
857
+ }
858
+
1019
859
  // ─── Sincronización Dinámica del Catálogo de Skills ───────────────────────────
1020
860
  // Escanea .agents/skills/*/SKILL.md, extrae frontmatter y regenera las tablas
1021
861
  // entre <!-- SKILLS_CATALOG_START --> y <!-- SKILLS_CATALOG_END --> en:
@@ -1109,65 +949,7 @@ ${masterTableLines.join('\n')}`;
1109
949
  }
1110
950
  }
1111
951
 
1112
- async function applyFile(source, dest, method) {
1113
- const srcPath = path.resolve(source);
1114
- const destPath = path.resolve(dest);
1115
952
 
1116
- // Case-insensitive check for Windows compatibility
1117
- if (srcPath.toLowerCase() === destPath.toLowerCase()) {
1118
- // console.log(chalk.gray(` (Skipping self - install: ${ path.basename(source) })`));
1119
- return;
1120
- }
1121
-
1122
- try {
1123
- if (fs.existsSync(dest) || (fs.existsSync(path.dirname(dest)) && fs.readdirSync(path.dirname(dest)).includes(path.basename(dest)))) {
1124
- const lstat = fs.lstatSync(dest);
1125
- if (lstat.isSymbolicLink()) {
1126
- fs.unlinkSync(dest); // Safe to remove old symlink
1127
- } else if (lstat.isDirectory()) {
1128
- // AUGMENTATION: No borramos el directorio real para no perder archivos del usuario.
1129
- // Si el usuario pidió symlink pero hay un directorio real, forzamos merge por copia.
1130
- if (method === 'symlink') method = 'copy';
1131
- } else {
1132
- fs.unlinkSync(dest); // It is a file, overwrite it
1133
- }
1134
- }
1135
- } catch (e) { }
1136
-
1137
- const destDir = path.dirname(dest);
1138
- if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
1139
-
1140
- const srcStat = fs.statSync(source);
1141
- const isDir = srcStat.isDirectory();
1142
-
1143
- if (method === 'symlink') {
1144
- try {
1145
- const type = isDir ? 'junction' : 'file';
1146
- fs.symlinkSync(source, dest, type);
1147
- } catch (e) {
1148
- try {
1149
- if (isDir) {
1150
- copyRecursiveSync(source, dest, true);
1151
- } else {
1152
- fs.copyFileSync(source, dest);
1153
- }
1154
- const isWindows = os.platform() === 'win32';
1155
- const msg = isWindows && !isDir
1156
- ? `(Symlink falló[Requiere Admin / DevMode en Win].Copiado.)`
1157
- : `(Symlink falló, se usó copia)`;
1158
- console.log(chalk.yellow(` ${msg} `));
1159
- } catch (err) {
1160
- console.error(chalk.red(` Error copiando ${path.basename(dest)}: ${err.message} `));
1161
- }
1162
- }
1163
- } else {
1164
- if (isDir) {
1165
- copyRecursiveSync(source, dest, true);
1166
- } else {
1167
- fs.copyFileSync(source, dest);
1168
- }
1169
- }
1170
- }
1171
953
 
1172
954
  function copyRecursiveSync(src, dest, overwrite) {
1173
955
  if (fs.cpSync) {
@@ -1220,224 +1002,15 @@ function getAllItems(dir, isNested) {
1220
1002
  }
1221
1003
  }
1222
1004
 
1223
- // ============================================
1224
- // INIT: Inicializar proyecto con LMAgent
1225
- // ============================================
1226
-
1227
- async function runInit(options) {
1228
- let targetIdes = []; // Initialize targetIdes
1229
- console.clear();
1230
- const branding = figlet.textSync('LMAGENT', { font: 'ANSI Shadow' });
1231
- console.log(gradient.pastel.multiline(branding));
1232
- console.log(gradient.cristal(' by QuBit\n'));
1233
-
1234
- const projectRoot = process.cwd();
1235
- const targetRoot = projectRoot; // Fix for ReferenceError in runInit
1236
- console.log(chalk.cyan(`📦 Inicializando proyecto LMAgent en: ${chalk.bold(projectRoot)} \n`));
1237
-
1238
- // Verificar si ya está inicializado
1239
- const agentsExists = fs.existsSync(path.join(projectRoot, 'AGENTS.md'));
1240
- if (agentsExists && !options.force) {
1241
- console.log(chalk.yellow('⚠️ Este proyecto ya tiene AGENTS.md'));
1242
- if (!options.yes) {
1243
- const { overwrite } = await inquirer.prompt([{
1244
- type: 'confirm',
1245
- name: 'overwrite',
1246
- message: '¿Sobrescribir archivos existentes?',
1247
- default: false
1248
- }]);
1249
- if (!overwrite) {
1250
- console.log(chalk.yellow('Cancelado. Usa --force para forzar.'));
1251
- return;
1252
- }
1253
- }
1254
- }
1255
-
1256
- let filesToCopy = [...INIT_FILES];
1257
- let dirsToCopy = [...INIT_DIRS];
1258
-
1259
- // Modo interactivo: preguntar qué copiar
1260
- if (!options.yes) {
1261
- const answers = await inquirer.prompt([
1262
- {
1263
- type: 'checkbox',
1264
- name: 'files',
1265
- message: 'Archivos de entry point a copiar:',
1266
- choices: INIT_FILES.map(f => ({
1267
- name: `${f.src} - ${f.desc} `,
1268
- value: f.src,
1269
- checked: true
1270
- }))
1271
- },
1272
- {
1273
- type: 'checkbox',
1274
- name: 'dirs',
1275
- message: 'Directorios a copiar:',
1276
- choices: INIT_DIRS.map(d => ({
1277
- name: `${d.src}/ - ${d.desc}`,
1278
- value: d.src,
1279
- checked: true
1280
- }))
1281
- }
1282
- ]);
1283
- dirsToCopy = INIT_DIRS.filter(d => answers.dirs.includes(d.src));
1284
-
1285
- // Seleccionar IDE para destino de archivos
1286
- console.log('');
1287
- const ideAnswer = await inquirer.prompt([
1288
- {
1289
- type: 'checkbox',
1290
- name: 'ides',
1291
- message: 'Selecciona tu IDE principal (para ubicar las carpetas):',
1292
- choices: IDE_CONFIGS.filter(i => i.value !== 'custom').map(i => ({
1293
- name: i.name,
1294
- value: i.value,
1295
- checked: i.value === 'cursor'
1296
- }))
1297
- }
1298
- ]);
1299
- targetIdes = IDE_CONFIGS.filter(i => ideAnswer.ides.includes(i.value));
1300
- } else {
1301
- // Defaults for non-interactive
1302
- targetIdes = [IDE_CONFIGS.find(i => i.value === 'cursor')];
1303
- }
1304
-
1305
- // Copiar archivos del framework a la carpeta del Agente (Clean Root)
1306
- console.log(chalk.bold('\n📦 Instalando framework en directorios de Agente:'));
1307
-
1308
- for (const ide of targetIdes) {
1309
- if (!ide.skillsDir) continue; // Skip custom/manual if no dir defined
1310
-
1311
- // Determinar "Agent Root" (ej: .cursor/ o .github/)
1312
- // Asume que skillsDir es "root/skills", así que dirname obtiene "root"
1313
- const agentRootDir = path.join(targetRoot, path.dirname(ide.skillsDir));
1314
-
1315
- console.log(chalk.dim(` Destino: ${agentRootDir}`));
1316
-
1317
- // Crear directorio root si no existe
1318
- if (!fs.existsSync(agentRootDir)) fs.mkdirSync(agentRootDir, { recursive: true });
1319
-
1320
- // 5. Install Root Configs (CLAUDE.md, AGENTS.md) with Prompt
1321
- console.log(chalk.bold('\nChecking Root Configurations:'));
1322
- for (const file of INIT_FILES) {
1323
- const srcPath = path.join(__dirname, file.src);
1324
- const destPath = path.join(targetRoot, file.src);
1005
+ // runInit() eliminada en v3.4.0 — unificada en runInstall()
1006
+ // El comando `init` ahora es alias de `install`
1325
1007
 
1326
- if (fs.existsSync(srcPath)) {
1327
- if (!fs.existsSync(destPath)) {
1328
- let content = fs.readFileSync(srcPath, 'utf8');
1329
- if (file.versionTemplate) content = content.replace(/\{\{VERSION\}\}/g, PKG_VERSION);
1330
- fs.writeFileSync(destPath, content, 'utf8');
1331
- console.log(` ${chalk.green('✔')} ${file.src} (Created)`);
1332
- } else {
1333
- // Exists: Ask to overwrite (unless force/yes)
1334
- let shouldOverwrite = false;
1335
- if (options.force) {
1336
- shouldOverwrite = true;
1337
- } else if (!options.yes) {
1338
- const answer = await inquirer.prompt([{
1339
- type: 'confirm',
1340
- name: 'overwrite',
1341
- message: `⚠️ ${file.src} ya existe. ¿Sobrescribir?`,
1342
- default: false
1343
- }]);
1344
- shouldOverwrite = answer.overwrite;
1345
- }
1346
1008
 
1347
- if (shouldOverwrite) {
1348
- let content = fs.readFileSync(srcPath, 'utf8');
1349
- if (file.versionTemplate) content = content.replace(/\{\{VERSION\}\}/g, PKG_VERSION);
1350
- fs.writeFileSync(destPath, content, 'utf8');
1351
- console.log(` ${chalk.yellow('✎')} ${file.src} (Overwritten)`);
1352
- } else {
1353
- console.log(` ${chalk.gray('SKIP')} ${file.src} (Kept existing)`);
1354
- }
1355
- }
1356
- }
1357
- }
1358
1009
 
1359
- // Copiar Directorios (docs, config, templates)
1360
- for (const dir of dirsToCopy) {
1361
- const src = path.join(__dirname, dir.src);
1362
- const dest = path.join(agentRootDir, dir.src);
1363
- if (fs.existsSync(src)) {
1364
- copyRecursiveSync(src, dest, true); // Force overwrite
1365
- console.log(` ${chalk.green('✔')} ${dir.src}/ -> ${path.dirname(ide.skillsDir)}/${dir.src}/`);
1366
-
1367
- // CLEANUP: If docs, remove assets (legacy logo, etc.)
1368
- if (dir.src === 'docs') {
1369
- const assetsDir = path.join(dest, 'assets');
1370
- if (fs.existsSync(assetsDir)) {
1371
- try {
1372
- fs.rmSync(assetsDir, { recursive: true, force: true });
1373
- console.log(` ${chalk.yellow('🗑 Eliminado assets heredados (logo, etc.)')}`);
1374
- } catch (e) { }
1375
- }
1376
- }
1377
- }
1378
- }
1379
- }
1380
1010
 
1381
- // Crear .env.example si no existe
1382
- const envExampleDest = path.join(projectRoot, '.env.example');
1383
- if (!fs.existsSync(envExampleDest)) {
1384
- const envContent = `# ============================================
1385
- # LMAgent Project - Environment Variables
1386
- # ============================================
1387
- # Copiar este archivo a .env y completar los valores
1388
1011
 
1389
- # Database
1390
- DATABASE_URL=postgresql://user:password@localhost:5432/dbname
1391
1012
 
1392
- # Redis
1393
- REDIS_URL=redis://localhost:6379
1394
1013
 
1395
- # Security
1396
- JWT_SECRET=change-this-to-a-strong-secret-minimum-32-characters
1397
-
1398
- # LLM API Keys
1399
- OPENAI_API_KEY=sk-...
1400
- ANTHROPIC_API_KEY=sk-ant-...
1401
- GOOGLE_API_KEY=...
1402
-
1403
- # n8n (si aplica)
1404
- N8N_WEBHOOK_URL=https://n8n.yourserver.com/webhook
1405
-
1406
- # Environment
1407
- ENVIRONMENT=development
1408
- DEBUG=true
1409
- `;
1410
- fs.writeFileSync(envExampleDest, envContent);
1411
- console.log(` ${chalk.green('✔')} .env.example ${chalk.green('(nuevo)')}`);
1412
- } else {
1413
- console.log(` ${chalk.blue('ℹ')} .env.example ya existe, no se sobrescribe`);
1414
- }
1415
-
1416
- // Resumen
1417
- console.log(gradient.pastel.multiline(`\n✨ Proyecto inicializado con LMAgent v${PKG_VERSION} ✨`));
1418
- console.log('');
1419
- console.log(chalk.cyan('Próximos pasos:'));
1420
- console.log(` 1. ${chalk.bold('lmagent install')} - Instalar skills/rules/workflows en tu IDE`);
1421
- console.log(` 2. Editar ${chalk.bold('.env.example')} → ${chalk.bold('.env')} con tus credenciales`);
1422
- console.log(` 3. Leer ${chalk.bold('AGENTS.md')} para conocer las capacidades disponibles`);
1423
- console.log('');
1424
-
1425
- // Preguntar si quiere ejecutar install también
1426
- if (!options.yes) {
1427
- const { runInstallNow } = await inquirer.prompt([{
1428
- type: 'confirm',
1429
- name: 'runInstallNow',
1430
- message: '¿Ejecutar lmagent install ahora para conectar al IDE?',
1431
- default: true
1432
- }]);
1433
- if (runInstallNow) {
1434
- console.log('');
1435
- await runInstall(options);
1436
- }
1437
- } else {
1438
- console.log(chalk.cyan('💡 Ejecuta `lmagent install` para conectar al IDE.\n'));
1439
- }
1440
- }
1441
1014
 
1442
1015
  // ============================================
1443
1016
  // DOCTOR: Verificar configuración del proyecto
@@ -1463,91 +1036,93 @@ async function runDoctor() {
1463
1036
  console.log(` ${chalk.green('✔')} ${file.src}`);
1464
1037
  ok++;
1465
1038
  } else {
1466
- console.log(` ${chalk.red('✘')} ${file.src} - ${chalk.red('FALTANTE')} → ejecuta ${chalk.bold('lmagent init')}`);
1039
+ console.log(` ${chalk.red('✘')} ${file.src} - ${chalk.red('FALTANTE')} → ejecuta ${chalk.bold('lmagent install')}`);
1467
1040
  issues++;
1468
1041
  }
1469
1042
  }
1470
1043
 
1471
- // 2. Directorios de configuración
1472
- console.log(chalk.bold('\n📁 Configuración:'));
1473
- for (const dir of INIT_DIRS) {
1474
- const exists = fs.existsSync(path.join(projectRoot, dir.src));
1475
- if (exists) {
1476
- console.log(` ${chalk.green('✔')} ${dir.src}/`);
1477
- ok++;
1478
- } else {
1479
- console.log(` ${chalk.yellow('⚠')} ${dir.src}/ - Opcional, ejecuta ${chalk.bold('lmagent init')} para copiar`);
1044
+ // 2. Core centralizado en .agents/
1045
+ console.log(chalk.bold('\n📦 Core (.agents/):'));
1046
+ const coreDir = path.join(projectRoot, '.agents');
1047
+ if (fs.existsSync(coreDir)) {
1048
+ console.log(` ${chalk.green('✔')} .agents/ existe`);
1049
+ ok++;
1050
+
1051
+ for (const dir of CORE_DIRS) {
1052
+ const dirPath = path.join(coreDir, dir.src);
1053
+ if (fs.existsSync(dirPath)) {
1054
+ let count = 0;
1055
+ try {
1056
+ const items = fs.readdirSync(dirPath);
1057
+ count = items.filter(i => !i.startsWith('.')).length;
1058
+ } catch (e) { }
1059
+ console.log(` ${chalk.green('✔')} ${dir.src}/ (${count} elementos)`);
1060
+ ok++;
1061
+ } else {
1062
+ console.log(` ${chalk.yellow('⚠')} ${dir.src}/ - No encontrado → ejecuta ${chalk.bold('lmagent install')}`);
1063
+ }
1480
1064
  }
1065
+
1066
+ // Verificar conteo de skills
1067
+ const skillsDir = path.join(coreDir, 'skills');
1068
+ if (fs.existsSync(skillsDir)) {
1069
+ const installedSkills = fs.readdirSync(skillsDir)
1070
+ .filter(item => fs.statSync(path.join(skillsDir, item)).isDirectory());
1071
+ const expectedSkillsCount = getAllItems(PACKAGE_SKILLS_DIR, true).length;
1072
+
1073
+ if (installedSkills.length < expectedSkillsCount) {
1074
+ console.log(` ${chalk.yellow('⚠')} Solo ${installedSkills.length}/${expectedSkillsCount} skills → ejecuta ${chalk.bold('lmagent install')}`);
1075
+ } else {
1076
+ console.log(` ${chalk.green('✔')} ${installedSkills.length}/${expectedSkillsCount} skills completos`);
1077
+ }
1078
+ }
1079
+ } else {
1080
+ console.log(` ${chalk.red('✘')} .agents/ NO existe → ejecuta ${chalk.bold('lmagent install')}`);
1081
+ issues++;
1481
1082
  }
1482
1083
 
1483
- // 3. Detectar IDEs configurados
1484
- console.log(chalk.bold('\n🔧 IDEs detectados:'));
1084
+ // 3. Detectar agentes (Global + Proyecto)
1085
+ console.log(chalk.bold('\n🔧 Agentes detectados:'));
1086
+ const globalAgents = detectGlobalAgents();
1485
1087
  let ideFound = false;
1486
1088
  for (const ide of IDE_CONFIGS) {
1487
- if (ide.value === 'custom') continue;
1488
- const rulesExist = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir));
1489
- const skillsExist = ide.skillsDir && fs.existsSync(path.join(projectRoot, ide.skillsDir));
1089
+ if (ide.value === 'custom' || ide.value === 'generic') continue;
1490
1090
  const markerExist = ide.markerFile && fs.existsSync(path.join(projectRoot, ide.markerFile));
1091
+ const rulesExist = ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir));
1092
+ const isGlobal = globalAgents.has(ide.value);
1093
+ const bridgeExists = ide.bridgeFile && ide.rulesDir && fs.existsSync(path.join(projectRoot, ide.rulesDir, ide.bridgeFile));
1094
+ const configExists = ide.configFile && fs.existsSync(path.join(projectRoot, ide.configFile));
1491
1095
 
1492
- if (rulesExist || skillsExist || markerExist) {
1096
+ if (markerExist || rulesExist || isGlobal) {
1493
1097
  ideFound = true;
1494
1098
  const parts = [];
1495
- if (rulesExist) parts.push('rules');
1496
- if (skillsExist) parts.push('skills');
1099
+ if (isGlobal && !markerExist && !rulesExist) parts.push(chalk.blue('global'));
1100
+ if (bridgeExists) parts.push(chalk.green('bridge ✔'));
1101
+ else if (configExists) parts.push(chalk.green('config ✔'));
1102
+ else parts.push(chalk.yellow('sin bridge'));
1103
+
1497
1104
  console.log(` ${chalk.green('✔')} ${ide.name} (${parts.join(', ')})`);
1498
1105
  ok++;
1499
-
1500
- // Verificar que tiene los skills correctos
1501
- if (skillsExist) {
1502
- const installedSkills = fs.readdirSync(path.join(projectRoot, ide.skillsDir))
1503
- .filter(item => fs.statSync(path.join(projectRoot, ide.skillsDir, item)).isDirectory());
1504
-
1505
- // Calcular skills esperados dinámicamente
1506
- const expectedSkillsCount = getAllItems(PACKAGE_SKILLS_DIR, true).length;
1507
- const skillCount = installedSkills.length;
1508
-
1509
- if (skillCount < expectedSkillsCount) {
1510
- console.log(` ${chalk.yellow('⚠')} Solo ${skillCount}/${expectedSkillsCount} skills instalados → ejecuta ${chalk.bold('lmagent install')}`);
1511
- } else {
1512
- console.log(` ${chalk.green('✔')} ${skillCount} skills instalados`);
1513
- }
1514
- }
1515
1106
  }
1516
1107
  }
1517
1108
  if (!ideFound) {
1518
- console.log(` ${chalk.red('✘')} Ningún IDE detectado → ejecuta ${chalk.bold('lmagent install')}`);
1109
+ console.log(` ${chalk.red('✘')} Ningún agente detectado → ejecuta ${chalk.bold('lmagent install')}`);
1519
1110
  issues++;
1520
1111
  }
1521
1112
 
1522
- // 4. Verificar .env
1113
+ // 4. Verificar .gitignore
1523
1114
  console.log(chalk.bold('\n🔒 Seguridad:'));
1524
- const envExists = fs.existsSync(path.join(projectRoot, '.env'));
1525
- const envExampleExists = fs.existsSync(path.join(projectRoot, '.env.example'));
1526
1115
  const gitignoreExists = fs.existsSync(path.join(projectRoot, '.gitignore'));
1527
-
1528
- if (envExampleExists) {
1529
- console.log(` ${chalk.green('✔')} .env.example existe`);
1530
- ok++;
1531
- } else {
1532
- console.log(` ${chalk.yellow('⚠')} .env.example no encontrado`);
1533
- }
1534
-
1535
- if (envExists) {
1536
- console.log(` ${chalk.green('✔')} .env existe`);
1537
- ok++;
1538
- } else {
1539
- console.log(` ${chalk.yellow('⚠')} .env no encontrado (necesario para ejecutar)`);
1540
- }
1541
-
1542
1116
  if (gitignoreExists) {
1543
1117
  const gitignore = fs.readFileSync(path.join(projectRoot, '.gitignore'), 'utf-8');
1544
1118
  if (gitignore.includes('.env')) {
1545
1119
  console.log(` ${chalk.green('✔')} .env está en .gitignore`);
1546
1120
  ok++;
1547
1121
  } else {
1548
- console.log(` ${chalk.red('')} .env NO está en .gitignore → ${chalk.red('RIESGO DE SEGURIDAD')}`);
1549
- issues++;
1122
+ console.log(` ${chalk.yellow('')} .env no está en .gitignore`);
1550
1123
  }
1124
+ } else {
1125
+ console.log(` ${chalk.yellow('⚠')} .gitignore no encontrado`);
1551
1126
  }
1552
1127
 
1553
1128
  // Resumen