aios-core 4.2.2 → 4.2.4

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 (75) hide show
  1. package/.aios-core/core/registry/service-registry.json +6466 -6586
  2. package/.aios-core/core-config.yaml +10 -5
  3. package/.aios-core/data/aios-kb.md +19 -25
  4. package/.aios-core/data/entity-registry.yaml +311 -204
  5. package/.aios-core/data/registry-update-log.jsonl +8 -0
  6. package/.aios-core/development/tasks/db-squad-integration.md +3 -3
  7. package/.aios-core/development/tasks/dev-develop-story.md +1 -1
  8. package/.aios-core/development/tasks/integrate-squad.md +1 -1
  9. package/.aios-core/development/tasks/pr-automation.md +3 -3
  10. package/.aios-core/development/tasks/squad-creator-migrate.md +1 -1
  11. package/.aios-core/development/tasks/squad-creator-sync-ide-command.md +0 -2
  12. package/.aios-core/development/tasks/update-aios.md +2 -2
  13. package/.aios-core/development/tasks/validate-next-story.md +2 -99
  14. package/.aios-core/development/workflows/README.md +0 -4
  15. package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +0 -1
  16. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +3 -3
  17. package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +1 -1
  18. package/.aios-core/docs/standards/STANDARDS-INDEX.md +4 -4
  19. package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +2 -2
  20. package/.aios-core/framework-config.yaml +8 -4
  21. package/.aios-core/infrastructure/scripts/ide-sync/README.md +29 -5
  22. package/.aios-core/infrastructure/scripts/ide-sync/gemini-commands.js +205 -0
  23. package/.aios-core/infrastructure/scripts/ide-sync/index.js +48 -11
  24. package/.aios-core/infrastructure/scripts/ide-sync/redirect-generator.js +1 -1
  25. package/.aios-core/infrastructure/scripts/test-utilities.js +1 -1
  26. package/.aios-core/infrastructure/scripts/tool-resolver.js +2 -2
  27. package/.aios-core/infrastructure/scripts/validate-claude-integration.js +101 -0
  28. package/.aios-core/infrastructure/scripts/validate-codex-integration.js +141 -0
  29. package/.aios-core/infrastructure/scripts/validate-gemini-integration.js +151 -0
  30. package/.aios-core/infrastructure/scripts/validate-parity.js +119 -0
  31. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +0 -11
  32. package/.aios-core/install-manifest.yaml +83 -71
  33. package/.aios-core/local-config.yaml.template +2 -1
  34. package/.aios-core/presets/README.md +0 -1
  35. package/.aios-core/product/data/integration-patterns.md +1 -1
  36. package/.aios-core/product/templates/ide-rules/gemini-rules.md +87 -233
  37. package/.aios-core/product/templates/statusline/statusline-script.js +188 -0
  38. package/.aios-core/product/templates/statusline/track-agent.sh +68 -0
  39. package/.aios-core/user-guide.md +14 -19
  40. package/LICENSE +0 -27
  41. package/README.md +42 -6
  42. package/bin/aios-init.js +98 -54
  43. package/bin/modules/env-config.js +0 -1
  44. package/package.json +18 -5
  45. package/packages/gemini-aios-extension/README.md +13 -8
  46. package/packages/gemini-aios-extension/commands/aios-agent.js +7 -0
  47. package/packages/gemini-aios-extension/commands/aios-agents.js +2 -1
  48. package/packages/gemini-aios-extension/commands/aios-analyst.js +6 -0
  49. package/packages/gemini-aios-extension/commands/aios-architect.js +6 -0
  50. package/packages/gemini-aios-extension/commands/aios-data-engineer.js +6 -0
  51. package/packages/gemini-aios-extension/commands/aios-dev.js +6 -0
  52. package/packages/gemini-aios-extension/commands/aios-devops.js +6 -0
  53. package/packages/gemini-aios-extension/commands/aios-master.js +6 -0
  54. package/packages/gemini-aios-extension/commands/aios-menu.js +6 -0
  55. package/packages/gemini-aios-extension/commands/aios-pm.js +6 -0
  56. package/packages/gemini-aios-extension/commands/aios-po.js +6 -0
  57. package/packages/gemini-aios-extension/commands/aios-qa.js +6 -0
  58. package/packages/gemini-aios-extension/commands/aios-sm.js +6 -0
  59. package/packages/gemini-aios-extension/commands/aios-squad-creator.js +6 -0
  60. package/packages/gemini-aios-extension/commands/aios-ux-design-expert.js +6 -0
  61. package/packages/gemini-aios-extension/commands/lib/agent-launcher.js +138 -0
  62. package/packages/gemini-aios-extension/extension.json +70 -0
  63. package/packages/gemini-aios-extension/gemini-extension.json +147 -0
  64. package/packages/gemini-aios-extension/hooks/hooks.json +67 -65
  65. package/packages/installer/src/config/ide-configs.js +10 -10
  66. package/packages/installer/src/config/templates/core-config-template.js +6 -3
  67. package/packages/installer/src/wizard/ide-config-generator.js +373 -2
  68. package/packages/installer/src/wizard/ide-selector.js +1 -1
  69. package/packages/installer/src/wizard/pro-setup.js +237 -150
  70. package/scripts/code-intel-health-check.js +125 -125
  71. package/scripts/ensure-manifest.js +58 -0
  72. package/.aios-core/infrastructure/scripts/ide-sync/transformers/windsurf.js +0 -106
  73. package/.aios-core/product/templates/ide-rules/cline-rules.md +0 -84
  74. package/.aios-core/product/templates/ide-rules/roo-rules.md +0 -86
  75. package/.aios-core/product/templates/ide-rules/windsurf-rules.md +0 -80
@@ -12,6 +12,7 @@ const path = require('path');
12
12
  const yaml = require('js-yaml');
13
13
  const inquirer = require('inquirer');
14
14
  const ora = require('ora');
15
+ const { spawnSync } = require('child_process');
15
16
  const { getIDEConfig } = require('../config/ide-configs');
16
17
  const { validateProjectName } = require('./validators');
17
18
  const { getMergeStrategy, hasMergeStrategy } = require('../merger/index.js');
@@ -386,8 +387,8 @@ async function createAntiGravityConfigJson(projectRoot, ideConfig) {
386
387
  * @returns {Promise<{success: boolean, files: string[], errors: Array}>}
387
388
  *
388
389
  * @example
389
- * const result = await generateIDEConfigs(['cursor', 'windsurf'], wizardState);
390
- * console.log(result.files); // ['.cursorrules', '.windsurfrules']
390
+ * const result = await generateIDEConfigs(['cursor', 'github-copilot'], wizardState);
391
+ * console.log(result.files); // ['.cursorrules', '.github/copilot-instructions.md']
391
392
  */
392
393
  async function generateIDEConfigs(selectedIDEs, wizardState, options = {}) {
393
394
  const projectRoot = options.projectRoot || process.cwd();
@@ -538,6 +539,44 @@ async function generateIDEConfigs(selectedIDEs, wizardState, options = {}) {
538
539
  } else {
539
540
  spinner.info('Skipped settings.local.json (no hooks to register)');
540
541
  }
542
+
543
+ // Silent statusline setup (graceful skip if user already has one)
544
+ const statuslineResult = await setupGlobalStatusline();
545
+ if (statuslineResult.installed) {
546
+ createdFiles.push(...statuslineResult.files);
547
+ }
548
+ }
549
+
550
+ // Gemini parity with Claude Code: copy hooks and configure settings
551
+ if (ideKey === 'gemini') {
552
+ spinner.start('Copying Gemini CLI hooks...');
553
+ const hookFiles = await copyGeminiHooksFolder(projectRoot);
554
+ createdFiles.push(...hookFiles);
555
+ if (hookFiles.length > 0) {
556
+ createdFolders.push(path.join(projectRoot, '.gemini', 'hooks'));
557
+ spinner.succeed(`Copied ${hookFiles.length} hook file(s) to .gemini/hooks`);
558
+ } else {
559
+ spinner.info('No Gemini hook files to copy');
560
+ }
561
+
562
+ spinner.start('Configuring Gemini CLI settings...');
563
+ const settingsFile = await createGeminiSettings(projectRoot);
564
+ if (settingsFile) {
565
+ createdFiles.push(settingsFile);
566
+ spinner.succeed('Created .gemini/settings.json with AIOS hooks');
567
+ } else {
568
+ spinner.info('Skipped .gemini/settings.json (no hooks to register)');
569
+ }
570
+
571
+ spinner.start('Linking Gemini AIOS extension...');
572
+ const extensionResult = await linkGeminiExtension(projectRoot);
573
+ if (extensionResult.status === 'linked') {
574
+ spinner.succeed('Gemini extension "aios" linked and enabled');
575
+ } else if (extensionResult.status === 'already-linked') {
576
+ spinner.succeed('Gemini extension "aios" already linked');
577
+ } else {
578
+ spinner.info(`Skipped Gemini extension linking (${extensionResult.reason})`);
579
+ }
541
580
  }
542
581
 
543
582
  } catch (error) {
@@ -724,6 +763,334 @@ async function createClaudeSettingsLocal(projectRoot) {
724
763
  return settingsPath;
725
764
  }
726
765
 
766
+ /**
767
+ <<<<<<< HEAD
768
+ * Copy .aios-core/hooks/gemini folder into .gemini/hooks during installation
769
+ * @param {string} projectRoot - Project root directory
770
+ * @returns {Promise<string[]>} List of copied files
771
+ */
772
+ async function copyGeminiHooksFolder(projectRoot) {
773
+ const sourceDir = path.join(__dirname, '..', '..', '..', '..', '.aios-core', 'hooks', 'gemini');
774
+ const targetDir = path.join(projectRoot, '.gemini', 'hooks');
775
+ const copiedFiles = [];
776
+
777
+ if (!await fs.pathExists(sourceDir)) {
778
+ return copiedFiles;
779
+ }
780
+
781
+ if (path.resolve(sourceDir) === path.resolve(targetDir)) {
782
+ return copiedFiles;
783
+ }
784
+
785
+ await fs.ensureDir(targetDir);
786
+
787
+ const files = await fs.readdir(sourceDir);
788
+ for (const file of files) {
789
+ if (!file.endsWith('.js')) continue;
790
+
791
+ const sourcePath = path.join(sourceDir, file);
792
+ const targetPath = path.join(targetDir, file);
793
+ const stat = await fs.stat(sourcePath);
794
+ if (stat.isFile()) {
795
+ await fs.copy(sourcePath, targetPath);
796
+ copiedFiles.push(targetPath);
797
+ }
798
+ }
799
+
800
+ return copiedFiles;
801
+ }
802
+
803
+ /**
804
+ * Create/merge .gemini/settings.json and register AIOS hooks as enabled.
805
+ * @param {string} projectRoot - Project root directory
806
+ * @returns {Promise<string|null>} Path to settings file or null if skipped
807
+ */
808
+ async function createGeminiSettings(projectRoot) {
809
+ const settingsPath = path.join(projectRoot, '.gemini', 'settings.json');
810
+ const hooksDir = path.join(projectRoot, '.gemini', 'hooks');
811
+
812
+ if (!await fs.pathExists(hooksDir)) {
813
+ return null;
814
+ }
815
+
816
+ const hookEntries = [
817
+ {
818
+ event: 'SessionStart',
819
+ matcher: '*',
820
+ hook: {
821
+ name: 'aios-session-init',
822
+ type: 'command',
823
+ command: 'node ".gemini/hooks/session-start.js"',
824
+ timeout: 5000,
825
+ enabled: true,
826
+ },
827
+ },
828
+ {
829
+ event: 'BeforeAgent',
830
+ matcher: '*',
831
+ hook: {
832
+ name: 'aios-context-inject',
833
+ type: 'command',
834
+ command: 'node ".gemini/hooks/before-agent.js"',
835
+ timeout: 3000,
836
+ enabled: true,
837
+ },
838
+ },
839
+ {
840
+ event: 'BeforeTool',
841
+ matcher: 'write_file|replace|shell|bash|execute',
842
+ hook: {
843
+ name: 'aios-security-check',
844
+ type: 'command',
845
+ command: 'node ".gemini/hooks/before-tool.js"',
846
+ timeout: 2000,
847
+ enabled: true,
848
+ },
849
+ },
850
+ {
851
+ event: 'AfterTool',
852
+ matcher: '*',
853
+ hook: {
854
+ name: 'aios-audit-log',
855
+ type: 'command',
856
+ command: 'node ".gemini/hooks/after-tool.js"',
857
+ timeout: 2000,
858
+ enabled: true,
859
+ },
860
+ },
861
+ {
862
+ event: 'SessionEnd',
863
+ matcher: '*',
864
+ hook: {
865
+ name: 'aios-session-persist',
866
+ type: 'command',
867
+ command: 'node ".gemini/hooks/session-end.js"',
868
+ timeout: 5000,
869
+ enabled: true,
870
+ },
871
+ },
872
+ ];
873
+
874
+ let settings = {};
875
+ if (await fs.pathExists(settingsPath)) {
876
+ try {
877
+ settings = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
878
+ } catch (error) {
879
+ console.error(` ⚠️ Could not parse ${settingsPath}: ${error.message}`);
880
+ settings = {};
881
+ }
882
+ }
883
+
884
+ settings.previewFeatures = true;
885
+ settings.folderTrust = settings.folderTrust || { enabled: true };
886
+ settings.hooks = settings.hooks || {};
887
+
888
+ for (const entry of hookEntries) {
889
+ if (!Array.isArray(settings.hooks[entry.event])) {
890
+ settings.hooks[entry.event] = [];
891
+ }
892
+
893
+ const alreadyRegistered = settings.hooks[entry.event].some((wrapper) => {
894
+ if (wrapper && Array.isArray(wrapper.hooks)) {
895
+ return wrapper.hooks.some((h) => h && h.name === entry.hook.name);
896
+ }
897
+ return false;
898
+ });
899
+
900
+ if (!alreadyRegistered) {
901
+ settings.hooks[entry.event].push({
902
+ matcher: entry.matcher,
903
+ hooks: [entry.hook],
904
+ });
905
+ }
906
+ }
907
+
908
+ await fs.ensureDir(path.dirname(settingsPath));
909
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
910
+ return settingsPath;
911
+ }
912
+
913
+ /**
914
+ * Best-effort Gemini extension linking for AIOS project.
915
+ * Does not fail installation when auth/CLI is unavailable.
916
+ * @param {string} projectRoot
917
+ * @returns {Promise<{status: 'linked'|'already-linked'|'skipped', reason?: string}>}
918
+ */
919
+ async function linkGeminiExtension(projectRoot) {
920
+ const extensionDir = path.join(projectRoot, 'packages', 'gemini-aios-extension');
921
+ const manifestPath = path.join(extensionDir, 'gemini-extension.json');
922
+ const legacyManifestPath = path.join(extensionDir, 'extension.json');
923
+
924
+ if (!await fs.pathExists(extensionDir)) {
925
+ return { status: 'skipped', reason: 'extension-dir-not-found' };
926
+ }
927
+
928
+ // Gemini CLI >=0.28 expects gemini-extension.json
929
+ if (!await fs.pathExists(manifestPath) && await fs.pathExists(legacyManifestPath)) {
930
+ await fs.copy(legacyManifestPath, manifestPath);
931
+ }
932
+
933
+ if (!await fs.pathExists(manifestPath)) {
934
+ return { status: 'skipped', reason: 'manifest-not-found' };
935
+ }
936
+
937
+ const versionCheck = spawnSync('gemini', ['--version'], { encoding: 'utf8' });
938
+ if (versionCheck.status !== 0) {
939
+ return { status: 'skipped', reason: 'gemini-cli-not-available' };
940
+ }
941
+
942
+ let linkResult = spawnSync('gemini', ['extensions', 'link', extensionDir, '--consent'], {
943
+ cwd: projectRoot,
944
+ encoding: 'utf8',
945
+ timeout: 30000,
946
+ });
947
+
948
+ if (linkResult.status === 0) {
949
+ return { status: 'linked' };
950
+ }
951
+
952
+ const output = `${linkResult.stdout || ''}\n${linkResult.stderr || ''}`;
953
+
954
+ // When already installed, perform idempotent relink.
955
+ if (output.includes('already installed')) {
956
+ const uninstall = spawnSync('gemini', ['extensions', 'uninstall', 'aios'], {
957
+ cwd: projectRoot,
958
+ encoding: 'utf8',
959
+ timeout: 30000,
960
+ });
961
+
962
+ if (uninstall.status !== 0) {
963
+ return { status: 'skipped', reason: 'uninstall-failed' };
964
+ }
965
+
966
+ linkResult = spawnSync('gemini', ['extensions', 'link', extensionDir, '--consent'], {
967
+ cwd: projectRoot,
968
+ encoding: 'utf8',
969
+ timeout: 30000,
970
+ });
971
+
972
+ if (linkResult.status === 0) {
973
+ return { status: 'linked' };
974
+ }
975
+ return { status: 'skipped', reason: 'relink-failed' };
976
+ }
977
+
978
+ if (output.toLowerCase().includes('authentication')) {
979
+ return { status: 'skipped', reason: 'authentication-required' };
980
+ }
981
+
982
+ return { status: 'skipped', reason: 'link-failed' };
983
+ }
984
+
985
+ /**
986
+ * Setup global statusline for Claude Code
987
+ *
988
+ * Copies statusline-script.js and track-agent.sh to ~/.claude/
989
+ * and configures ~/.claude/settings.json with statusLine + hook entries.
990
+ *
991
+ * GRACEFUL SKIP: If user already has a statusLine configured, this function
992
+ * returns silently without any output — the user never knows it was checked.
993
+ *
994
+ * @returns {Promise<{installed: boolean, files: string[]}>}
995
+ */
996
+ async function setupGlobalStatusline() {
997
+ const homeDir = require('os').homedir();
998
+ const globalSettingsPath = path.join(homeDir, '.claude', 'settings.json');
999
+ const result = { installed: false, files: [] };
1000
+
1001
+ // Read existing global settings
1002
+ let settings = {};
1003
+ try {
1004
+ if (await fs.pathExists(globalSettingsPath)) {
1005
+ const content = await fs.readFile(globalSettingsPath, 'utf8');
1006
+ settings = JSON.parse(content);
1007
+ }
1008
+ } catch {
1009
+ // Corrupted or unreadable — treat as empty
1010
+ settings = {};
1011
+ }
1012
+
1013
+ // GRACEFUL SKIP: User already has a statusLine configured
1014
+ if (settings.statusLine) {
1015
+ return result;
1016
+ }
1017
+
1018
+ // Source templates
1019
+ const templatesDir = path.join(__dirname, '..', '..', '..', '..', '.aios-core', 'product', 'templates', 'statusline');
1020
+
1021
+ const scriptSource = path.join(templatesDir, 'statusline-script.js');
1022
+ const hookSource = path.join(templatesDir, 'track-agent.sh');
1023
+
1024
+ // Verify templates exist
1025
+ if (!await fs.pathExists(scriptSource) || !await fs.pathExists(hookSource)) {
1026
+ return result;
1027
+ }
1028
+
1029
+ // Target paths
1030
+ const scriptTarget = path.join(homeDir, '.claude', 'statusline-script.js');
1031
+ const hookTarget = path.join(homeDir, '.claude', 'hooks', 'track-agent.sh');
1032
+ const cacheDir = path.join(homeDir, '.claude', 'session-cache');
1033
+
1034
+ // Copy files
1035
+ try {
1036
+ await fs.ensureDir(path.join(homeDir, '.claude', 'hooks'));
1037
+ await fs.ensureDir(cacheDir);
1038
+ await fs.copy(scriptSource, scriptTarget);
1039
+ await fs.copy(hookSource, hookTarget);
1040
+ result.files.push(scriptTarget, hookTarget);
1041
+ } catch {
1042
+ return result;
1043
+ }
1044
+
1045
+ // Build the statusLine command with platform-appropriate path
1046
+ const scriptPathEscaped = scriptTarget.replace(/\\/g, '\\\\');
1047
+
1048
+ // Add statusLine to settings
1049
+ settings.statusLine = {
1050
+ type: 'command',
1051
+ command: `node "${scriptPathEscaped}"`,
1052
+ };
1053
+
1054
+ // Add track-agent hook to UserPromptSubmit (if not already present)
1055
+ if (!settings.hooks) {
1056
+ settings.hooks = {};
1057
+ }
1058
+ if (!Array.isArray(settings.hooks.UserPromptSubmit)) {
1059
+ settings.hooks.UserPromptSubmit = [];
1060
+ }
1061
+
1062
+ const hookPathEscaped = hookTarget.replace(/\\/g, '\\\\');
1063
+ const alreadyHasTrackAgent = settings.hooks.UserPromptSubmit.some(entry => {
1064
+ if (Array.isArray(entry.hooks)) {
1065
+ return entry.hooks.some(h => h.command && h.command.includes('track-agent'));
1066
+ }
1067
+ return entry.command && entry.command.includes('track-agent');
1068
+ });
1069
+
1070
+ if (!alreadyHasTrackAgent) {
1071
+ settings.hooks.UserPromptSubmit.push({
1072
+ matcher: '',
1073
+ hooks: [
1074
+ {
1075
+ type: 'command',
1076
+ command: `bash "${hookPathEscaped}"`,
1077
+ },
1078
+ ],
1079
+ });
1080
+ }
1081
+
1082
+ // Write settings back
1083
+ try {
1084
+ await fs.ensureDir(path.dirname(globalSettingsPath));
1085
+ await fs.writeFile(globalSettingsPath, JSON.stringify(settings, null, 2), 'utf8');
1086
+ result.installed = true;
1087
+ } catch {
1088
+ return result;
1089
+ }
1090
+
1091
+ return result;
1092
+ }
1093
+
727
1094
  module.exports = {
728
1095
  generateIDEConfigs,
729
1096
  showSuccessSummary,
@@ -734,4 +1101,8 @@ module.exports = {
734
1101
  generateTemplateVariables,
735
1102
  copyClaudeHooksFolder,
736
1103
  createClaudeSettingsLocal,
1104
+ copyGeminiHooksFolder,
1105
+ createGeminiSettings,
1106
+ linkGeminiExtension,
1107
+ setupGlobalStatusline,
737
1108
  };
@@ -46,7 +46,7 @@ function validateIDESelection(selectedIDEs) {
46
46
  *
47
47
  * @example
48
48
  * const selectedIDEs = await selectIDEs();
49
- * console.log(selectedIDEs); // ['cursor', 'windsurf']
49
+ * console.log(selectedIDEs); // ['cursor', 'github-copilot']
50
50
  */
51
51
  async function selectIDEs() {
52
52
  const { selectedIDEs } = await inquirer.prompt([