aios-core 4.2.3 → 4.2.5

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 (82) 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 +14 -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 +19 -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/pro/README.md +66 -0
  70. package/pro/license/degradation.js +220 -0
  71. package/pro/license/errors.js +450 -0
  72. package/pro/license/feature-gate.js +354 -0
  73. package/pro/license/index.js +181 -0
  74. package/pro/license/license-api.js +617 -0
  75. package/pro/license/license-cache.js +523 -0
  76. package/pro/license/license-crypto.js +303 -0
  77. package/scripts/code-intel-health-check.js +125 -125
  78. package/scripts/ensure-manifest.js +58 -0
  79. package/.aios-core/infrastructure/scripts/ide-sync/transformers/windsurf.js +0 -106
  80. package/.aios-core/product/templates/ide-rules/cline-rules.md +0 -84
  81. package/.aios-core/product/templates/ide-rules/roo-rules.md +0 -86
  82. package/.aios-core/product/templates/ide-rules/windsurf-rules.md +0 -80
@@ -0,0 +1,205 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+
6
+ const FALLBACK_DESCRIPTION = 'Agente especializado AIOS';
7
+ const MAX_DESCRIPTION_CONTEXT = 120;
8
+
9
+ const MENU_ORDER = [
10
+ 'aios-master',
11
+ 'analyst',
12
+ 'architect',
13
+ 'data-engineer',
14
+ 'dev',
15
+ 'devops',
16
+ 'pm',
17
+ 'po',
18
+ 'qa',
19
+ 'sm',
20
+ 'squad-creator',
21
+ 'ux-design-expert',
22
+ ];
23
+
24
+ function commandSlugForAgent(agentId) {
25
+ if (agentId.startsWith('aios-')) {
26
+ return agentId.replace(/^aios-/, '');
27
+ }
28
+ return agentId;
29
+ }
30
+
31
+ function menuCommandName(agentId) {
32
+ return `/aios-${commandSlugForAgent(agentId)}`;
33
+ }
34
+
35
+ function normalizeText(text) {
36
+ if (!text || typeof text !== 'string') return '';
37
+ return text.replace(/\s+/g, ' ').trim();
38
+ }
39
+
40
+ function truncateText(text, maxLen = MAX_DESCRIPTION_CONTEXT) {
41
+ if (!text || text.length <= maxLen) return text;
42
+ return `${text.slice(0, maxLen - 1).trimEnd()}…`;
43
+ }
44
+
45
+ function summarizeWhenToUse(whenToUse) {
46
+ const normalized = normalizeText(whenToUse);
47
+ if (!normalized) return '';
48
+
49
+ // Drop redirect/negative guidance sections that are useful for routing, not for menu labels.
50
+ const withoutNegativeSection = normalized.split(/\b(?:NOT\s+for|NÃO\s+para)\b/i)[0].trim();
51
+ const primary = withoutNegativeSection || normalized;
52
+
53
+ // Keep only the first sentence/chunk for concise autocomplete labels.
54
+ const firstChunk = primary.split(/[.;!?](?:\s|$)/)[0].trim();
55
+ return truncateText(firstChunk || primary);
56
+ }
57
+
58
+ function escapeTomlString(text) {
59
+ return String(text || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
60
+ }
61
+
62
+ function buildAgentDescription(agent) {
63
+ const agentData = agent.agent || {};
64
+ const title = normalizeText(agentData.title);
65
+ const whenToUseSummary = summarizeWhenToUse(agentData.whenToUse);
66
+
67
+ if (title && whenToUseSummary) {
68
+ return `${title} (${whenToUseSummary})`;
69
+ }
70
+ if (title) {
71
+ return title;
72
+ }
73
+ if (whenToUseSummary) {
74
+ return whenToUseSummary;
75
+ }
76
+ return `Ativar agente AIOS ${agent.id}`;
77
+ }
78
+
79
+ function buildAgentCommandPrompt(agentId) {
80
+ return [
81
+ `Ative o agente ${agentId}:`,
82
+ `1. Leia a definição completa em .gemini/rules/AIOS/agents/${agentId}.md`,
83
+ '2. Siga as activation-instructions do bloco YAML',
84
+ `3. Renderize o greeting via: node .aios-core/development/scripts/generate-greeting.js ${agentId}`,
85
+ ' Se shell nao disponivel, exiba o greeting de persona_profile.communication.greeting_levels.named',
86
+ '4. Mostre Quick Commands e aguarde input do usuario',
87
+ 'Mantenha a persona até *exit.',
88
+ ].join('\n');
89
+ }
90
+
91
+ function buildAgentCommandFile(agentId, description = FALLBACK_DESCRIPTION) {
92
+ const slug = commandSlugForAgent(agentId);
93
+
94
+ const prompt = buildAgentCommandPrompt(agentId);
95
+ const content = [
96
+ `description = "${escapeTomlString(description)}"`,
97
+ 'prompt = """',
98
+ prompt,
99
+ '"""',
100
+ '',
101
+ ].join('\n');
102
+
103
+ return {
104
+ filename: `aios-${slug}.toml`,
105
+ content,
106
+ agentId,
107
+ description,
108
+ };
109
+ }
110
+
111
+ function buildMenuPrompt(commandFiles) {
112
+ const lines = [
113
+ 'Você está no launcher AIOS para Gemini.',
114
+ '',
115
+ 'Mostre a lista de agentes abaixo em formato numerado, explicando em 1 linha quando usar cada um:',
116
+ ];
117
+
118
+ let index = 1;
119
+ for (const commandFile of commandFiles) {
120
+ lines.push(`${index}. ${menuCommandName(commandFile.agentId)} - ${commandFile.description}`);
121
+ index += 1;
122
+ }
123
+
124
+ lines.push('');
125
+ lines.push('No final, peça para o usuário escolher um número ou digitar o comando direto.');
126
+ return lines.join('\n');
127
+ }
128
+
129
+ function buildMenuCommandFile(commandFiles) {
130
+ const content = [
131
+ 'description = "Menu rápido AIOS (lista agentes e orienta qual ativar)"',
132
+ 'prompt = """',
133
+ buildMenuPrompt(commandFiles),
134
+ '"""',
135
+ '',
136
+ ].join('\n');
137
+
138
+ return {
139
+ filename: 'aios-menu.toml',
140
+ content,
141
+ };
142
+ }
143
+
144
+ function resolveAgentOrder(agentIds) {
145
+ const unique = [...new Set(agentIds)];
146
+ const known = MENU_ORDER.filter((id) => unique.includes(id));
147
+ const extra = unique.filter((id) => !MENU_ORDER.includes(id)).sort();
148
+ return [...known, ...extra];
149
+ }
150
+
151
+ function buildGeminiCommandFiles(agents) {
152
+ const validAgents = agents
153
+ .filter((agent) => !agent.error)
154
+ .map((agent) => ({
155
+ id: agent.id,
156
+ description: buildAgentDescription(agent),
157
+ }));
158
+
159
+ const ordered = resolveAgentOrder(validAgents.map((agent) => agent.id));
160
+ const byId = new Map(validAgents.map((agent) => [agent.id, agent]));
161
+ const files = ordered.map((id) => {
162
+ const meta = byId.get(id);
163
+ const description = meta?.description || FALLBACK_DESCRIPTION;
164
+ return buildAgentCommandFile(id, description);
165
+ });
166
+ files.unshift(buildMenuCommandFile(files));
167
+ return files;
168
+ }
169
+
170
+ function syncGeminiCommands(agents, projectRoot, options = {}) {
171
+ const commandsDir = path.join(projectRoot, '.gemini', 'commands');
172
+ const files = buildGeminiCommandFiles(agents);
173
+ const written = [];
174
+
175
+ if (!options.dryRun) {
176
+ fs.ensureDirSync(commandsDir);
177
+ }
178
+
179
+ for (const file of files) {
180
+ const targetPath = path.join(commandsDir, file.filename);
181
+ if (!options.dryRun) {
182
+ fs.writeFileSync(targetPath, file.content, 'utf8');
183
+ }
184
+ written.push({
185
+ filename: path.join('commands', file.filename),
186
+ path: targetPath,
187
+ content: file.content,
188
+ });
189
+ }
190
+
191
+ return { commandsDir, files: written };
192
+ }
193
+
194
+ module.exports = {
195
+ FALLBACK_DESCRIPTION,
196
+ MENU_ORDER,
197
+ commandSlugForAgent,
198
+ menuCommandName,
199
+ buildAgentDescription,
200
+ summarizeWhenToUse,
201
+ truncateText,
202
+ escapeTomlString,
203
+ buildGeminiCommandFiles,
204
+ syncGeminiCommands,
205
+ };
@@ -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
+ };