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.
- package/.aios-core/cli/commands/pro/index.js +82 -148
- package/.aios-core/core/synapse/domain/domain-loader.js +2 -2
- package/.aios-core/core/synapse/engine.js +17 -4
- package/.aios-core/core/synapse/memory/memory-bridge.js +246 -0
- package/.aios-core/core/synapse/output/formatter.js +34 -12
- package/.aios-core/core/synapse/scripts/generate-constitution.js +204 -0
- package/.aios-core/core/synapse/utils/tokens.js +25 -0
- package/.aios-core/data/aios-kb.md +2 -4
- package/.aios-core/data/entity-registry.yaml +61 -8
- package/.aios-core/development/scripts/unified-activation-pipeline.js +9 -1
- package/.aios-core/framework-config.yaml +1 -1
- package/.aios-core/install-manifest.yaml +33 -21
- package/.aios-core/lib/build.json +1 -0
- package/.aios-core/package.json +2 -1
- package/.aios-core/user-guide.md +1 -1
- package/.claude/CLAUDE.md +8 -9
- package/.claude/hooks/README.md +169 -0
- package/.claude/hooks/precompact-session-digest.js +46 -0
- package/.claude/hooks/synapse-engine.js +87 -0
- package/bin/aios-init.js +4 -4
- package/bin/aios-minimal.js +1 -4
- package/bin/aios.js +1 -1
- package/bin/modules/env-config.js +0 -1
- package/package.json +4 -1
- package/packages/aios-pro-cli/bin/aios-pro.js +158 -0
- package/packages/aios-pro-cli/package.json +32 -0
- package/packages/installer/package.json +1 -1
- package/packages/installer/src/installer/aios-core-installer.js +23 -0
- package/packages/installer/src/wizard/ide-config-generator.js +146 -1
- 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
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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 (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
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}`);
|