aios-core 4.2.3 → 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 (74) 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/scripts/code-intel-health-check.js +125 -125
  70. package/scripts/ensure-manifest.js +58 -0
  71. package/.aios-core/infrastructure/scripts/ide-sync/transformers/windsurf.js +0 -106
  72. package/.aios-core/product/templates/ide-rules/cline-rules.md +0 -84
  73. package/.aios-core/product/templates/ide-rules/roo-rules.md +0 -86
  74. package/.aios-core/product/templates/ide-rules/windsurf-rules.md +0 -80
@@ -24,11 +24,11 @@ const yaml = require('js-yaml');
24
24
  const { parseAllAgents } = require('./agent-parser');
25
25
  const { generateAllRedirects, writeRedirects } = require('./redirect-generator');
26
26
  const { validateAllIdes, formatValidationReport } = require('./validator');
27
+ const { syncGeminiCommands, buildGeminiCommandFiles } = require('./gemini-commands');
27
28
 
28
29
  // Transformers
29
30
  const claudeCodeTransformer = require('./transformers/claude-code');
30
31
  const cursorTransformer = require('./transformers/cursor');
31
- const windsurfTransformer = require('./transformers/windsurf');
32
32
  const antigravityTransformer = require('./transformers/antigravity');
33
33
 
34
34
  // ANSI colors for output
@@ -66,16 +66,21 @@ function loadConfig(projectRoot) {
66
66
  path: '.codex/agents',
67
67
  format: 'full-markdown-yaml',
68
68
  },
69
+ gemini: {
70
+ enabled: true,
71
+ path: '.gemini/rules/AIOS/agents',
72
+ format: 'full-markdown-yaml',
73
+ },
74
+ 'github-copilot': {
75
+ enabled: true,
76
+ path: '.github/agents',
77
+ format: 'full-markdown-yaml',
78
+ },
69
79
  cursor: {
70
80
  enabled: true,
71
81
  path: '.cursor/rules/agents',
72
82
  format: 'condensed-rules',
73
83
  },
74
- windsurf: {
75
- enabled: false, // Disabled - consolidating to core IDEs (v3.10.0)
76
- path: '.windsurf/rules/agents',
77
- format: 'xml-tagged-markdown',
78
- },
79
84
  antigravity: {
80
85
  enabled: true,
81
86
  path: '.antigravity/rules/agents',
@@ -120,7 +125,6 @@ function getTransformer(format) {
120
125
  const transformers = {
121
126
  'full-markdown-yaml': claudeCodeTransformer,
122
127
  'condensed-rules': cursorTransformer,
123
- 'xml-tagged-markdown': windsurfTransformer,
124
128
  'cursor-style': antigravityTransformer,
125
129
  };
126
130
 
@@ -258,6 +262,15 @@ async function commandSync(options) {
258
262
  }
259
263
 
260
264
  const result = syncIde(agents, ideConfig, ideName, projectRoot, options);
265
+
266
+ // Gemini CLI: also sync slash launcher command files (.gemini/commands/*.toml)
267
+ if (ideName === 'gemini') {
268
+ const geminiCommands = syncGeminiCommands(agents, projectRoot, options);
269
+ result.commandFiles = geminiCommands.files;
270
+ } else {
271
+ result.commandFiles = [];
272
+ }
273
+
261
274
  results.push(result);
262
275
 
263
276
  // Generate redirects for this IDE
@@ -269,6 +282,7 @@ async function commandSync(options) {
269
282
  }
270
283
 
271
284
  const agentCount = result.files.length;
285
+ const commandCount = (result.commandFiles || []).length;
272
286
  const redirectCount = redirectResult.written.length;
273
287
  const errorCount = result.errors.length;
274
288
 
@@ -279,7 +293,7 @@ async function commandSync(options) {
279
293
  }
280
294
 
281
295
  console.log(
282
- ` ${status} ${agentCount} agents, ${redirectCount} redirects${errorCount > 0 ? `, ${errorCount} errors` : ''}`
296
+ ` ${status} ${agentCount} agents${commandCount > 0 ? `, ${commandCount} commands` : ''}, ${redirectCount} redirects${errorCount > 0 ? `, ${errorCount} errors` : ''}`
283
297
  );
284
298
 
285
299
  if (options.verbose && result.errors.length > 0) {
@@ -291,7 +305,7 @@ async function commandSync(options) {
291
305
  }
292
306
 
293
307
  // Summary
294
- const totalFiles = results.reduce((sum, r) => sum + r.files.length, 0);
308
+ const totalFiles = results.reduce((sum, r) => sum + r.files.length + (r.commandFiles || []).length, 0);
295
309
  const totalRedirects =
296
310
  Object.keys(config.redirects).length * targetIdes.filter(([, c]) => c.enabled).length;
297
311
  const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
@@ -337,9 +351,18 @@ async function commandValidate(options) {
337
351
 
338
352
  // Build expected files for each IDE
339
353
  const ideConfigs = {};
354
+ let targetIdes = Object.entries(config.targets).filter(([, ideConfig]) => ideConfig.enabled);
340
355
 
341
- for (const [ideName, ideConfig] of Object.entries(config.targets)) {
342
- if (!ideConfig.enabled) continue;
356
+ // Filter IDEs if --ide flag specified
357
+ if (options.ide) {
358
+ targetIdes = targetIdes.filter(([name]) => name === options.ide);
359
+ if (targetIdes.length === 0) {
360
+ console.error(`${colors.red}Error: IDE '${options.ide}' not found in config${colors.reset}`);
361
+ process.exit(1);
362
+ }
363
+ }
364
+
365
+ for (const [ideName, ideConfig] of targetIdes) {
343
366
 
344
367
  const transformer = getTransformer(ideConfig.format);
345
368
  const expectedFiles = [];
@@ -374,6 +397,18 @@ async function commandValidate(options) {
374
397
  expectedFiles,
375
398
  targetDir: path.join(projectRoot, ideConfig.path),
376
399
  };
400
+
401
+ // Gemini CLI command launcher files are synced under .gemini/commands/*.toml
402
+ if (ideName === 'gemini') {
403
+ const commandFiles = buildGeminiCommandFiles(agents).map((entry) => ({
404
+ filename: entry.filename,
405
+ content: entry.content,
406
+ }));
407
+ ideConfigs['gemini-commands'] = {
408
+ expectedFiles: commandFiles,
409
+ targetDir: path.join(projectRoot, '.gemini', 'commands'),
410
+ };
411
+ }
377
412
  }
378
413
 
379
414
  // Validate
@@ -450,7 +485,9 @@ ${colors.bright}Options:${colors.reset}
450
485
  ${colors.bright}Examples:${colors.reset}
451
486
  node ide-sync/index.js sync
452
487
  node ide-sync/index.js sync --ide codex
488
+ node ide-sync/index.js sync --ide gemini
453
489
  node ide-sync/index.js sync --ide cursor
490
+ node ide-sync/index.js validate --ide gemini --strict
454
491
  node ide-sync/index.js validate --strict
455
492
  node ide-sync/index.js sync --dry-run --verbose
456
493
  `);
@@ -55,7 +55,7 @@ ${baseContent.instruction}
55
55
  `;
56
56
 
57
57
  case 'xml-tagged-markdown':
58
- // Windsurf format
58
+ // Generic markdown format
59
59
  return `${baseContent.header}
60
60
 
61
61
  <redirect>
@@ -27,7 +27,7 @@ async function countIntegrationReferences(utilityName) {
27
27
  const searchDirs = [
28
28
  '.aios-core/agents',
29
29
  '.aios-core/tasks',
30
- 'expansion-packs',
30
+ 'squads',
31
31
  ];
32
32
 
33
33
  let totalCount = 0;
@@ -48,7 +48,7 @@ class ToolResolver {
48
48
  // 2. Build search paths (squad → core priority)
49
49
  const searchPaths = [];
50
50
  if (context.expansionPack) {
51
- searchPaths.push(`expansion-packs/${context.expansionPack}/tools`);
51
+ searchPaths.push(`squads/${context.expansionPack}/tools`);
52
52
  }
53
53
  searchPaths.push(...this.basePaths);
54
54
 
@@ -304,7 +304,7 @@ class ToolResolver {
304
304
  async toolExists(toolName, context = {}) {
305
305
  const searchPaths = [];
306
306
  if (context.expansionPack) {
307
- searchPaths.push(`expansion-packs/${context.expansionPack}/tools`);
307
+ searchPaths.push(`squads/${context.expansionPack}/tools`);
308
308
  }
309
309
  searchPaths.push(...this.basePaths);
310
310
 
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ function parseArgs(argv = process.argv.slice(2)) {
8
+ const args = new Set(argv);
9
+ return {
10
+ quiet: args.has('--quiet') || args.has('-q'),
11
+ json: args.has('--json'),
12
+ };
13
+ }
14
+
15
+ function countMarkdownFiles(dirPath) {
16
+ if (!fs.existsSync(dirPath)) return 0;
17
+ return fs.readdirSync(dirPath).filter((f) => f.endsWith('.md')).length;
18
+ }
19
+
20
+ function validateClaudeIntegration(options = {}) {
21
+ const projectRoot = options.projectRoot || process.cwd();
22
+ const rulesFile = options.rulesFile || path.join(projectRoot, '.claude', 'CLAUDE.md');
23
+ const agentsDir = options.agentsDir || path.join(projectRoot, '.claude', 'commands', 'AIOS', 'agents');
24
+ const hooksDir = options.hooksDir || path.join(projectRoot, '.claude', 'hooks');
25
+ const sourceAgentsDir =
26
+ options.sourceAgentsDir || path.join(projectRoot, '.aios-core', 'development', 'agents');
27
+
28
+ const errors = [];
29
+ const warnings = [];
30
+
31
+ if (!fs.existsSync(agentsDir)) {
32
+ errors.push(`Missing Claude agents dir: ${path.relative(projectRoot, agentsDir)}`);
33
+ }
34
+ if (!fs.existsSync(rulesFile)) {
35
+ warnings.push(`Claude rules file not found yet: ${path.relative(projectRoot, rulesFile)}`);
36
+ }
37
+ if (!fs.existsSync(hooksDir)) {
38
+ warnings.push(`Claude hooks dir not found yet: ${path.relative(projectRoot, hooksDir)}`);
39
+ }
40
+
41
+ const sourceCount = countMarkdownFiles(sourceAgentsDir);
42
+ const claudeCount = countMarkdownFiles(agentsDir);
43
+ if (sourceCount > 0 && claudeCount !== sourceCount) {
44
+ warnings.push(`Claude agent count differs from source (${claudeCount}/${sourceCount})`);
45
+ }
46
+
47
+ return {
48
+ ok: errors.length === 0,
49
+ errors,
50
+ warnings,
51
+ metrics: {
52
+ sourceAgents: sourceCount,
53
+ claudeAgents: claudeCount,
54
+ },
55
+ };
56
+ }
57
+
58
+ function formatHumanReport(result) {
59
+ if (result.ok) {
60
+ const lines = [`✅ Claude integration validation passed (agents: ${result.metrics.claudeAgents})`];
61
+ if (result.warnings.length > 0) {
62
+ lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
63
+ }
64
+ return lines.join('\n');
65
+ }
66
+ const lines = [
67
+ `❌ Claude integration validation failed (${result.errors.length} issue(s))`,
68
+ ...result.errors.map((e) => `- ${e}`),
69
+ ];
70
+ if (result.warnings.length > 0) {
71
+ lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
72
+ }
73
+ return lines.join('\n');
74
+ }
75
+
76
+ function main() {
77
+ const args = parseArgs();
78
+ const result = validateClaudeIntegration(args);
79
+
80
+ if (!args.quiet) {
81
+ if (args.json) {
82
+ console.log(JSON.stringify(result, null, 2));
83
+ } else {
84
+ console.log(formatHumanReport(result));
85
+ }
86
+ }
87
+
88
+ if (!result.ok) {
89
+ process.exitCode = 1;
90
+ }
91
+ }
92
+
93
+ if (require.main === module) {
94
+ main();
95
+ }
96
+
97
+ module.exports = {
98
+ validateClaudeIntegration,
99
+ parseArgs,
100
+ countMarkdownFiles,
101
+ };
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ function getDefaultOptions() {
8
+ const projectRoot = process.cwd();
9
+ return {
10
+ projectRoot,
11
+ instructionsFile: path.join(projectRoot, 'AGENTS.md'),
12
+ agentsDir: path.join(projectRoot, '.codex', 'agents'),
13
+ skillsDir: path.join(projectRoot, '.codex', 'skills'),
14
+ sourceAgentsDir: path.join(projectRoot, '.aios-core', 'development', 'agents'),
15
+ quiet: false,
16
+ json: false,
17
+ };
18
+ }
19
+
20
+ function parseArgs(argv = process.argv.slice(2)) {
21
+ const args = new Set(argv);
22
+ return {
23
+ quiet: args.has('--quiet') || args.has('-q'),
24
+ json: args.has('--json'),
25
+ };
26
+ }
27
+
28
+ function countMarkdownFiles(dirPath) {
29
+ if (!fs.existsSync(dirPath)) return 0;
30
+ return fs.readdirSync(dirPath).filter((f) => f.endsWith('.md')).length;
31
+ }
32
+
33
+ function countSkillFiles(skillsDir) {
34
+ if (!fs.existsSync(skillsDir)) return 0;
35
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
36
+ return entries
37
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith('aios-'))
38
+ .filter((entry) => fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md')))
39
+ .length;
40
+ }
41
+
42
+ function validateCodexIntegration(options = {}) {
43
+ const projectRoot = options.projectRoot || process.cwd();
44
+ const resolved = {
45
+ ...getDefaultOptions(),
46
+ ...options,
47
+ projectRoot,
48
+ instructionsFile: options.instructionsFile || path.join(projectRoot, 'AGENTS.md'),
49
+ agentsDir: options.agentsDir || path.join(projectRoot, '.codex', 'agents'),
50
+ skillsDir: options.skillsDir || path.join(projectRoot, '.codex', 'skills'),
51
+ sourceAgentsDir: options.sourceAgentsDir || path.join(projectRoot, '.aios-core', 'development', 'agents'),
52
+ };
53
+ const errors = [];
54
+ const warnings = [];
55
+
56
+ if (!fs.existsSync(resolved.instructionsFile)) {
57
+ warnings.push(
58
+ `Codex instructions file not found yet: ${path.relative(resolved.projectRoot, resolved.instructionsFile)}`,
59
+ );
60
+ }
61
+
62
+ if (!fs.existsSync(resolved.agentsDir)) {
63
+ errors.push(`Missing Codex agents dir: ${path.relative(resolved.projectRoot, resolved.agentsDir)}`);
64
+ }
65
+
66
+ if (!fs.existsSync(resolved.skillsDir)) {
67
+ errors.push(`Missing Codex skills dir: ${path.relative(resolved.projectRoot, resolved.skillsDir)}`);
68
+ }
69
+
70
+ const sourceCount = countMarkdownFiles(resolved.sourceAgentsDir);
71
+ const codexAgentsCount = countMarkdownFiles(resolved.agentsDir);
72
+ const codexSkillsCount = countSkillFiles(resolved.skillsDir);
73
+
74
+ if (sourceCount > 0 && codexAgentsCount !== sourceCount) {
75
+ warnings.push(`Codex agent count differs from source (${codexAgentsCount}/${sourceCount})`);
76
+ }
77
+
78
+ if (sourceCount > 0 && codexSkillsCount !== sourceCount) {
79
+ warnings.push(`Codex skill count differs from source (${codexSkillsCount}/${sourceCount})`);
80
+ }
81
+
82
+ return {
83
+ ok: errors.length === 0,
84
+ errors,
85
+ warnings,
86
+ metrics: {
87
+ sourceAgents: sourceCount,
88
+ codexAgents: codexAgentsCount,
89
+ codexSkills: codexSkillsCount,
90
+ },
91
+ };
92
+ }
93
+
94
+ function formatHumanReport(result) {
95
+ if (result.ok) {
96
+ const lines = [
97
+ `✅ Codex integration validation passed (agents: ${result.metrics.codexAgents}, skills: ${result.metrics.codexSkills})`,
98
+ ];
99
+ if (result.warnings.length > 0) {
100
+ lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
101
+ }
102
+ return lines.join('\n');
103
+ }
104
+ const lines = [
105
+ `❌ Codex integration validation failed (${result.errors.length} issue(s))`,
106
+ ...result.errors.map((e) => `- ${e}`),
107
+ ];
108
+ if (result.warnings.length > 0) {
109
+ lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
110
+ }
111
+ return lines.join('\n');
112
+ }
113
+
114
+ function main() {
115
+ const args = parseArgs();
116
+ const result = validateCodexIntegration(args);
117
+
118
+ if (!args.quiet) {
119
+ if (args.json) {
120
+ console.log(JSON.stringify(result, null, 2));
121
+ } else {
122
+ console.log(formatHumanReport(result));
123
+ }
124
+ }
125
+
126
+ if (!result.ok) {
127
+ process.exitCode = 1;
128
+ }
129
+ }
130
+
131
+ if (require.main === module) {
132
+ main();
133
+ }
134
+
135
+ module.exports = {
136
+ validateCodexIntegration,
137
+ parseArgs,
138
+ getDefaultOptions,
139
+ countMarkdownFiles,
140
+ countSkillFiles,
141
+ };
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ function getDefaultOptions() {
8
+ const projectRoot = process.cwd();
9
+ return {
10
+ projectRoot,
11
+ rulesFile: path.join(projectRoot, '.gemini', 'rules.md'),
12
+ agentsDir: path.join(projectRoot, '.gemini', 'rules', 'AIOS', 'agents'),
13
+ commandsDir: path.join(projectRoot, '.gemini', 'commands'),
14
+ extensionDir: path.join(projectRoot, 'packages', 'gemini-aios-extension'),
15
+ sourceAgentsDir: path.join(projectRoot, '.aios-core', 'development', 'agents'),
16
+ quiet: false,
17
+ json: false,
18
+ };
19
+ }
20
+
21
+ function parseArgs(argv = process.argv.slice(2)) {
22
+ const args = new Set(argv);
23
+ return {
24
+ quiet: args.has('--quiet') || args.has('-q'),
25
+ json: args.has('--json'),
26
+ };
27
+ }
28
+
29
+ function countMarkdownFiles(dirPath) {
30
+ if (!fs.existsSync(dirPath)) return 0;
31
+ return fs.readdirSync(dirPath).filter((f) => f.endsWith('.md')).length;
32
+ }
33
+
34
+ function validateGeminiIntegration(options = {}) {
35
+ const projectRoot = options.projectRoot || process.cwd();
36
+ const resolved = {
37
+ ...getDefaultOptions(),
38
+ ...options,
39
+ projectRoot,
40
+ rulesFile: options.rulesFile || path.join(projectRoot, '.gemini', 'rules.md'),
41
+ agentsDir: options.agentsDir || path.join(projectRoot, '.gemini', 'rules', 'AIOS', 'agents'),
42
+ commandsDir: options.commandsDir || path.join(projectRoot, '.gemini', 'commands'),
43
+ extensionDir: options.extensionDir || path.join(projectRoot, 'packages', 'gemini-aios-extension'),
44
+ sourceAgentsDir: options.sourceAgentsDir || path.join(projectRoot, '.aios-core', 'development', 'agents'),
45
+ };
46
+ const errors = [];
47
+ const warnings = [];
48
+
49
+ if (!fs.existsSync(resolved.rulesFile)) {
50
+ warnings.push(`Gemini rules file not found yet: ${path.relative(resolved.projectRoot, resolved.rulesFile)}`);
51
+ }
52
+
53
+ if (!fs.existsSync(resolved.agentsDir)) {
54
+ errors.push(`Missing Gemini agents dir: ${path.relative(resolved.projectRoot, resolved.agentsDir)}`);
55
+ }
56
+ if (!fs.existsSync(resolved.commandsDir)) {
57
+ errors.push(`Missing Gemini commands dir: ${path.relative(resolved.projectRoot, resolved.commandsDir)}`);
58
+ }
59
+
60
+ const sourceCount = countMarkdownFiles(resolved.sourceAgentsDir);
61
+ const geminiCount = countMarkdownFiles(resolved.agentsDir);
62
+ const commandFiles = fs.existsSync(resolved.commandsDir)
63
+ ? fs.readdirSync(resolved.commandsDir).filter((f) => f.endsWith('.toml'))
64
+ : [];
65
+ const expectedCommandCount = sourceCount > 0 ? sourceCount + 1 : 0;
66
+
67
+ if (sourceCount > 0 && commandFiles.length !== expectedCommandCount) {
68
+ warnings.push(`Gemini command count differs from source (${commandFiles.length}/${expectedCommandCount})`);
69
+ }
70
+ if (!commandFiles.includes('aios-menu.toml')) {
71
+ errors.push(`Missing Gemini command file: ${path.relative(resolved.projectRoot, path.join(resolved.commandsDir, 'aios-menu.toml'))}`);
72
+ }
73
+ if (sourceCount > 0 && geminiCount !== sourceCount) {
74
+ warnings.push(`Gemini agent count differs from source (${geminiCount}/${sourceCount})`);
75
+ }
76
+
77
+ const requiredExtensionFiles = [
78
+ 'extension.json',
79
+ 'README.md',
80
+ path.join('commands', 'aios-status.js'),
81
+ path.join('commands', 'aios-agents.js'),
82
+ path.join('commands', 'aios-validate.js'),
83
+ path.join('hooks', 'hooks.json'),
84
+ ];
85
+
86
+ for (const rel of requiredExtensionFiles) {
87
+ const abs = path.join(resolved.extensionDir, rel);
88
+ if (!fs.existsSync(abs)) {
89
+ errors.push(`Missing Gemini extension file: ${path.relative(resolved.projectRoot, abs)}`);
90
+ }
91
+ }
92
+
93
+ return {
94
+ ok: errors.length === 0,
95
+ errors,
96
+ warnings,
97
+ metrics: {
98
+ sourceAgents: sourceCount,
99
+ geminiAgents: geminiCount,
100
+ geminiCommands: commandFiles.length,
101
+ },
102
+ };
103
+ }
104
+
105
+ function formatHumanReport(result) {
106
+ if (result.ok) {
107
+ const lines = [
108
+ `✅ Gemini integration validation passed (agents: ${result.metrics.geminiAgents}, commands: ${result.metrics.geminiCommands})`,
109
+ ];
110
+ if (result.warnings.length > 0) {
111
+ lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
112
+ }
113
+ return lines.join('\n');
114
+ }
115
+ const lines = [
116
+ `❌ Gemini integration validation failed (${result.errors.length} issue(s))`,
117
+ ...result.errors.map((e) => `- ${e}`),
118
+ ];
119
+ if (result.warnings.length > 0) {
120
+ lines.push(...result.warnings.map((w) => `⚠️ ${w}`));
121
+ }
122
+ return lines.join('\n');
123
+ }
124
+
125
+ function main() {
126
+ const args = parseArgs();
127
+ const result = validateGeminiIntegration(args);
128
+
129
+ if (!args.quiet) {
130
+ if (args.json) {
131
+ console.log(JSON.stringify(result, null, 2));
132
+ } else {
133
+ console.log(formatHumanReport(result));
134
+ }
135
+ }
136
+
137
+ if (!result.ok) {
138
+ process.exitCode = 1;
139
+ }
140
+ }
141
+
142
+ if (require.main === module) {
143
+ main();
144
+ }
145
+
146
+ module.exports = {
147
+ validateGeminiIntegration,
148
+ parseArgs,
149
+ getDefaultOptions,
150
+ countMarkdownFiles,
151
+ };
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+ const { validateClaudeIntegration } = require('./validate-claude-integration');
7
+ const { validateCodexIntegration } = require('./validate-codex-integration');
8
+ const { validateGeminiIntegration } = require('./validate-gemini-integration');
9
+ const { validateCodexSkills } = require('./codex-skills-sync/validate');
10
+ const { validatePaths } = require('./validate-paths');
11
+
12
+ function parseArgs(argv = process.argv.slice(2)) {
13
+ const args = new Set(argv);
14
+ return {
15
+ quiet: args.has('--quiet') || args.has('-q'),
16
+ json: args.has('--json'),
17
+ };
18
+ }
19
+
20
+ function runSyncValidate(ide, projectRoot) {
21
+ const script = path.join('.aios-core', 'infrastructure', 'scripts', 'ide-sync', 'index.js');
22
+ const result = spawnSync('node', [script, 'validate', '--ide', ide, '--strict'], {
23
+ cwd: projectRoot,
24
+ encoding: 'utf8',
25
+ });
26
+ return {
27
+ ok: result.status === 0,
28
+ errors: result.status === 0 ? [] : [`Sync validation failed for ${ide}`],
29
+ warnings: [],
30
+ raw: result.stdout || result.stderr || '',
31
+ };
32
+ }
33
+
34
+ function normalizeResult(input) {
35
+ if (!input || typeof input !== 'object') {
36
+ return { ok: false, errors: ['Validator returned invalid result'], warnings: [] };
37
+ }
38
+ return {
39
+ ok: Boolean(input.ok),
40
+ errors: Array.isArray(input.errors) ? input.errors : [],
41
+ warnings: Array.isArray(input.warnings) ? input.warnings : [],
42
+ metrics: input.metrics || {},
43
+ };
44
+ }
45
+
46
+ function runParityValidation(options = {}, deps = {}) {
47
+ const projectRoot = options.projectRoot || process.cwd();
48
+ const runSync = deps.runSyncValidate || runSyncValidate;
49
+ const runClaudeIntegration = deps.validateClaudeIntegration || validateClaudeIntegration;
50
+ const runCodexIntegration = deps.validateCodexIntegration || validateCodexIntegration;
51
+ const runGeminiIntegration = deps.validateGeminiIntegration || validateGeminiIntegration;
52
+ const runCodexSkills = deps.validateCodexSkills || validateCodexSkills;
53
+ const runPaths = deps.validatePaths || validatePaths;
54
+ const checks = [
55
+ { id: 'claude-sync', exec: () => runSync('claude-code', projectRoot) },
56
+ { id: 'claude-integration', exec: () => runClaudeIntegration({ projectRoot }) },
57
+ { id: 'codex-sync', exec: () => runSync('codex', projectRoot) },
58
+ { id: 'codex-integration', exec: () => runCodexIntegration({ projectRoot }) },
59
+ { id: 'gemini-sync', exec: () => runSync('gemini', projectRoot) },
60
+ { id: 'gemini-integration', exec: () => runGeminiIntegration({ projectRoot }) },
61
+ { id: 'codex-skills', exec: () => runCodexSkills({ projectRoot, strict: true, quiet: true }) },
62
+ { id: 'paths', exec: () => runPaths({ projectRoot }) },
63
+ ];
64
+
65
+ const results = checks.map((check) => {
66
+ const normalized = normalizeResult(check.exec());
67
+ return { id: check.id, ...normalized };
68
+ });
69
+
70
+ return {
71
+ ok: results.every((r) => r.ok),
72
+ checks: results,
73
+ };
74
+ }
75
+
76
+ function formatHumanReport(result) {
77
+ const lines = [];
78
+ for (const check of result.checks) {
79
+ lines.push(`${check.ok ? '✅' : '❌'} ${check.id}`);
80
+ if (check.errors.length > 0) {
81
+ lines.push(...check.errors.map((e) => `- ${e}`));
82
+ }
83
+ if (check.warnings.length > 0) {
84
+ lines.push(...check.warnings.map((w) => `⚠️ ${w}`));
85
+ }
86
+ }
87
+ lines.push('');
88
+ lines.push(result.ok ? '✅ Parity validation passed' : '❌ Parity validation failed');
89
+ return lines.join('\n');
90
+ }
91
+
92
+ function main() {
93
+ const args = parseArgs();
94
+ const result = runParityValidation(args);
95
+
96
+ if (!args.quiet) {
97
+ if (args.json) {
98
+ console.log(JSON.stringify(result, null, 2));
99
+ } else {
100
+ console.log(formatHumanReport(result));
101
+ }
102
+ }
103
+
104
+ if (!result.ok) {
105
+ process.exitCode = 1;
106
+ }
107
+ }
108
+
109
+ if (require.main === module) {
110
+ main();
111
+ }
112
+
113
+ module.exports = {
114
+ parseArgs,
115
+ runSyncValidate,
116
+ runParityValidation,
117
+ normalizeResult,
118
+ formatHumanReport,
119
+ };