aios-core 4.0.0 → 4.0.2

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.
Files changed (30) hide show
  1. package/.aios-core/cli/commands/pro/index.js +82 -148
  2. package/.aios-core/core/synapse/domain/domain-loader.js +2 -2
  3. package/.aios-core/core/synapse/engine.js +17 -4
  4. package/.aios-core/core/synapse/memory/memory-bridge.js +246 -0
  5. package/.aios-core/core/synapse/output/formatter.js +34 -12
  6. package/.aios-core/core/synapse/scripts/generate-constitution.js +204 -0
  7. package/.aios-core/core/synapse/utils/tokens.js +25 -0
  8. package/.aios-core/data/aios-kb.md +2 -4
  9. package/.aios-core/data/entity-registry.yaml +61 -8
  10. package/.aios-core/development/scripts/unified-activation-pipeline.js +9 -1
  11. package/.aios-core/framework-config.yaml +1 -1
  12. package/.aios-core/install-manifest.yaml +33 -21
  13. package/.aios-core/lib/build.json +1 -0
  14. package/.aios-core/package.json +2 -1
  15. package/.aios-core/user-guide.md +1 -1
  16. package/.claude/CLAUDE.md +8 -9
  17. package/.claude/hooks/README.md +169 -0
  18. package/.claude/hooks/precompact-session-digest.js +46 -0
  19. package/.claude/hooks/synapse-engine.js +87 -0
  20. package/bin/aios-init.js +4 -4
  21. package/bin/aios-minimal.js +1 -4
  22. package/bin/aios.js +1 -1
  23. package/bin/modules/env-config.js +0 -1
  24. package/package.json +4 -1
  25. package/packages/aios-pro-cli/bin/aios-pro.js +158 -0
  26. package/packages/aios-pro-cli/package.json +32 -0
  27. package/packages/installer/package.json +1 -1
  28. package/packages/installer/src/installer/aios-core-installer.js +23 -0
  29. package/packages/installer/src/wizard/ide-config-generator.js +146 -1
  30. package/packages/installer/src/wizard/index.js +49 -32
@@ -506,7 +506,7 @@ async function generateIDEConfigs(selectedIDEs, wizardState, options = {}) {
506
506
  }
507
507
  }
508
508
 
509
- // For Claude Code, also copy .claude/rules folder
509
+ // For Claude Code, also copy .claude/rules folder, hooks, and settings
510
510
  if (ideKey === 'claude-code') {
511
511
  spinner.start('Copying Claude Code rules...');
512
512
  const rulesFiles = await copyClaudeRulesFolder(projectRoot);
@@ -517,6 +517,27 @@ async function generateIDEConfigs(selectedIDEs, wizardState, options = {}) {
517
517
  } else {
518
518
  spinner.info('No rule files to copy');
519
519
  }
520
+
521
+ // BUG-3 fix (INS-1): Copy .claude/hooks/ folder (SYNAPSE engine + precompact)
522
+ spinner.start('Copying Claude Code hooks...');
523
+ const hookFiles = await copyClaudeHooksFolder(projectRoot);
524
+ createdFiles.push(...hookFiles);
525
+ if (hookFiles.length > 0) {
526
+ createdFolders.push(path.join(projectRoot, '.claude', 'hooks'));
527
+ spinner.succeed(`Copied ${hookFiles.length} hook file(s) to .claude/hooks`);
528
+ } else {
529
+ spinner.info('No hook files to copy (SYNAPSE hooks not found in source)');
530
+ }
531
+
532
+ // BUG-4 fix (INS-1): Create .claude/settings.local.json with hook registration
533
+ spinner.start('Configuring Claude Code settings...');
534
+ const settingsFile = await createClaudeSettingsLocal(projectRoot);
535
+ if (settingsFile) {
536
+ createdFiles.push(settingsFile);
537
+ spinner.succeed('Created .claude/settings.local.json with SYNAPSE hook');
538
+ } else {
539
+ spinner.info('Skipped settings.local.json (no hooks to register)');
540
+ }
520
541
  }
521
542
 
522
543
  } catch (error) {
@@ -581,6 +602,128 @@ function showSuccessSummary(result) {
581
602
  console.log(' 4. Use * commands to interact with agents\n');
582
603
  }
583
604
 
605
+ /**
606
+ * BUG-3 fix (INS-1): Copy .claude/hooks/ folder during installation
607
+ * Only copies JS hooks that work without external dependencies (Python, etc.)
608
+ * @param {string} projectRoot - Project root directory
609
+ * @returns {Promise<string[]>} List of copied files
610
+ */
611
+ async function copyClaudeHooksFolder(projectRoot) {
612
+ const sourceDir = path.join(__dirname, '..', '..', '..', '..', '.claude', 'hooks');
613
+ const targetDir = path.join(projectRoot, '.claude', 'hooks');
614
+ const copiedFiles = [];
615
+
616
+ if (!await fs.pathExists(sourceDir)) {
617
+ return copiedFiles;
618
+ }
619
+
620
+ // QA-C2 fix: Guard source === dest (framework-dev mode)
621
+ if (path.resolve(sourceDir) === path.resolve(targetDir)) {
622
+ return copiedFiles;
623
+ }
624
+
625
+ await fs.ensureDir(targetDir);
626
+
627
+ // Only copy JS hooks that work standalone (no Python/shell deps)
628
+ const HOOKS_TO_COPY = [
629
+ 'synapse-engine.js',
630
+ 'precompact-session-digest.js',
631
+ 'README.md',
632
+ ];
633
+
634
+ const files = await fs.readdir(sourceDir);
635
+
636
+ for (const file of files) {
637
+ if (!HOOKS_TO_COPY.includes(file)) {
638
+ continue;
639
+ }
640
+
641
+ const sourcePath = path.join(sourceDir, file);
642
+ const targetPath = path.join(targetDir, file);
643
+
644
+ const stat = await fs.stat(sourcePath);
645
+ if (stat.isFile()) {
646
+ await fs.copy(sourcePath, targetPath);
647
+ copiedFiles.push(targetPath);
648
+ }
649
+ }
650
+
651
+ return copiedFiles;
652
+ }
653
+
654
+ /**
655
+ * BUG-4 fix (INS-1): Create .claude/settings.local.json with hook registration
656
+ * Creates or merges hook entries into settings.local.json
657
+ * @param {string} projectRoot - Project root directory
658
+ * @returns {Promise<string|null>} Path to created/updated file, or null if skipped
659
+ */
660
+ async function createClaudeSettingsLocal(projectRoot) {
661
+ const settingsPath = path.join(projectRoot, '.claude', 'settings.local.json');
662
+ const hookFile = path.join(projectRoot, '.claude', 'hooks', 'synapse-engine.js');
663
+
664
+ // Only create if the hook file was actually copied
665
+ if (!await fs.pathExists(hookFile)) {
666
+ return null;
667
+ }
668
+
669
+ // QA-C1 fix: Use correct Claude Code nested hook format
670
+ // Format: { hooks: [{ type, command }] } not flat { type, command }
671
+ const hookWrapper = {
672
+ hooks: [
673
+ {
674
+ type: 'command',
675
+ command: 'node ".claude/hooks/synapse-engine.js"',
676
+ },
677
+ ],
678
+ };
679
+
680
+ let settings = {};
681
+
682
+ // Merge with existing settings if present
683
+ if (await fs.pathExists(settingsPath)) {
684
+ try {
685
+ const existing = await fs.readFile(settingsPath, 'utf8');
686
+ settings = JSON.parse(existing);
687
+ } catch (parseError) {
688
+ // Corrupted file — log and overwrite with fresh settings
689
+ console.error(` ⚠️ Could not parse ${settingsPath}: ${parseError.message}`);
690
+ settings = {};
691
+ }
692
+ }
693
+
694
+ // Ensure hooks.UserPromptSubmit structure exists
695
+ if (!settings.hooks) {
696
+ settings.hooks = {};
697
+ }
698
+ if (!Array.isArray(settings.hooks.UserPromptSubmit)) {
699
+ settings.hooks.UserPromptSubmit = [];
700
+ }
701
+
702
+ // Check if synapse hook is already registered (supports both nested and flat formats)
703
+ const alreadyRegistered = settings.hooks.UserPromptSubmit.some(entry => {
704
+ // Nested format: entry.hooks[].command
705
+ if (Array.isArray(entry.hooks)) {
706
+ return entry.hooks.some(h => h.command && h.command.includes('synapse-engine.js'));
707
+ }
708
+ // Flat format (legacy): entry.command
709
+ return entry.command && entry.command.includes('synapse-engine.js');
710
+ });
711
+
712
+ if (!alreadyRegistered) {
713
+ settings.hooks.UserPromptSubmit.push(hookWrapper);
714
+ }
715
+
716
+ try {
717
+ await fs.ensureDir(path.dirname(settingsPath));
718
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
719
+ } catch (writeError) {
720
+ console.error(` ⚠️ Failed to write ${settingsPath}: ${writeError.message}`);
721
+ return null;
722
+ }
723
+
724
+ return settingsPath;
725
+ }
726
+
584
727
  module.exports = {
585
728
  generateIDEConfigs,
586
729
  showSuccessSummary,
@@ -589,4 +732,6 @@ module.exports = {
589
732
  backupFile,
590
733
  promptFileExists,
591
734
  generateTemplateVariables,
735
+ copyClaudeHooksFolder,
736
+ createClaudeSettingsLocal,
592
737
  };
@@ -384,40 +384,57 @@ async function runWizard(options = {}) {
384
384
  const targetPresetDir = path.join(process.cwd(), '.aios-core', 'data', 'tech-presets');
385
385
  await fse.ensureDir(targetPresetDir);
386
386
 
387
- // Copy the selected preset
388
- await fse.copy(
389
- presetFile,
390
- path.join(targetPresetDir, `${answers.selectedTechPreset}.md`),
391
- );
392
-
393
- // Copy the template too
394
- const templateFile = path.join(sourcePresetDir, '_template.md');
395
- if (fse.existsSync(templateFile)) {
396
- await fse.copy(templateFile, path.join(targetPresetDir, '_template.md'));
397
- }
398
-
399
- // Update technical-preferences.md to mark the selected preset
400
- const techPrefsFile = path.join(
401
- process.cwd(),
402
- '.aios-core',
403
- 'data',
404
- 'technical-preferences.md',
405
- );
406
- const techPrefsSource = path.join(sourcePresetDir, '..', 'technical-preferences.md');
387
+ // BUG-5 fix (INS-1): Guard against source === dest (e.g., running inside aios-core repo)
388
+ const targetPresetFile = path.join(targetPresetDir, `${answers.selectedTechPreset}.md`);
389
+ const sourceResolved = path.resolve(presetFile);
390
+ const targetResolved = path.resolve(targetPresetFile);
407
391
 
408
- if (fse.existsSync(techPrefsSource)) {
409
- let techPrefsContent = await fse.readFile(techPrefsSource, 'utf8');
410
-
411
- // Add active preset marker
412
- const activePresetSection = `\n## Active Preset\n\n**Selected:** \`${answers.selectedTechPreset}\`\n\nThis preset was selected during installation. The @architect and @dev agents will use these patterns by default.\n`;
413
-
414
- // Insert after the first heading
415
- techPrefsContent = techPrefsContent.replace(
416
- '# User-Defined Preferred Patterns and Preferences',
417
- '# User-Defined Preferred Patterns and Preferences' + activePresetSection,
392
+ if (sourceResolved === targetResolved) {
393
+ console.log(' ℹ️ Tech preset already in place (framework-dev mode)');
394
+ } else {
395
+ // Copy the selected preset
396
+ await fse.copy(presetFile, targetPresetFile);
397
+
398
+ // Copy the template too
399
+ const templateFile = path.join(sourcePresetDir, '_template.md');
400
+ if (fse.existsSync(templateFile)) {
401
+ const targetTemplate = path.join(targetPresetDir, '_template.md');
402
+ if (path.resolve(templateFile) !== path.resolve(targetTemplate)) {
403
+ await fse.copy(templateFile, targetTemplate);
404
+ }
405
+ }
406
+
407
+ // Update technical-preferences.md to mark the selected preset
408
+ const techPrefsFile = path.join(
409
+ process.cwd(),
410
+ '.aios-core',
411
+ 'data',
412
+ 'technical-preferences.md',
418
413
  );
419
-
420
- await fse.writeFile(techPrefsFile, techPrefsContent, 'utf8');
414
+ const techPrefsSource = path.join(sourcePresetDir, '..', 'technical-preferences.md');
415
+
416
+ if (fse.existsSync(techPrefsSource)) {
417
+ const techPrefsSourceResolved = path.resolve(techPrefsSource);
418
+ const techPrefsTargetResolved = path.resolve(techPrefsFile);
419
+
420
+ if (techPrefsSourceResolved !== techPrefsTargetResolved) {
421
+ // Prefer existing target file to preserve user customizations
422
+ const baseFile = fse.existsSync(techPrefsFile) ? techPrefsFile : techPrefsSource;
423
+ let techPrefsContent = await fse.readFile(baseFile, 'utf8');
424
+
425
+ // Add active preset marker only if not already present
426
+ const activePresetSection = `\n## Active Preset\n\n**Selected:** \`${answers.selectedTechPreset}\`\n\nThis preset was selected during installation. The @architect and @dev agents will use these patterns by default.\n`;
427
+
428
+ if (!techPrefsContent.includes('## Active Preset')) {
429
+ // Insert after the first heading
430
+ techPrefsContent = techPrefsContent.replace(
431
+ '# User-Defined Preferred Patterns and Preferences',
432
+ '# User-Defined Preferred Patterns and Preferences' + activePresetSection,
433
+ );
434
+ await fse.writeFile(techPrefsFile, techPrefsContent, 'utf8');
435
+ }
436
+ }
437
+ }
421
438
  }
422
439
 
423
440
  console.log(` ✅ Tech Preset: ${answers.selectedTechPreset}`);