aios-core 3.2.0 → 3.3.0

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.
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * IDE Sync - Main orchestrator for syncing agents to IDEs
5
+ * @story 6.19 - IDE Command Auto-Sync System
6
+ *
7
+ * Commands:
8
+ * sync - Sync agents to all enabled IDEs
9
+ * validate - Validate sync status (report mode)
10
+ * report - Generate sync status report
11
+ *
12
+ * Flags:
13
+ * --ide <name> - Sync specific IDE only
14
+ * --strict - Exit with code 1 if drift detected (CI mode)
15
+ * --dry-run - Preview changes without writing
16
+ * --verbose - Show detailed output
17
+ */
18
+
19
+ const fs = require('fs-extra');
20
+ const path = require('path');
21
+ const yaml = require('js-yaml');
22
+
23
+ const { parseAllAgents } = require('./agent-parser');
24
+ const { generateAllRedirects, writeRedirects } = require('./redirect-generator');
25
+ const { validateAllIdes, formatValidationReport } = require('./validator');
26
+
27
+ // Transformers
28
+ const claudeCodeTransformer = require('./transformers/claude-code');
29
+ const cursorTransformer = require('./transformers/cursor');
30
+ const windsurfTransformer = require('./transformers/windsurf');
31
+ const traeTransformer = require('./transformers/trae');
32
+ const antigravityTransformer = require('./transformers/antigravity');
33
+
34
+ // ANSI colors for output
35
+ const colors = {
36
+ reset: '\x1b[0m',
37
+ bright: '\x1b[1m',
38
+ dim: '\x1b[2m',
39
+ red: '\x1b[31m',
40
+ green: '\x1b[32m',
41
+ yellow: '\x1b[33m',
42
+ blue: '\x1b[34m',
43
+ cyan: '\x1b[36m',
44
+ };
45
+
46
+ /**
47
+ * Load core-config.yaml and extract ideSync section
48
+ * @param {string} projectRoot - Project root directory
49
+ * @returns {object} - ideSync configuration
50
+ */
51
+ function loadConfig(projectRoot) {
52
+ const configPath = path.join(projectRoot, '.aios-core', 'core-config.yaml');
53
+
54
+ // Default configuration
55
+ const defaultConfig = {
56
+ enabled: true,
57
+ source: '.aios-core/development/agents',
58
+ targets: {
59
+ 'claude-code': {
60
+ enabled: true,
61
+ path: '.claude/commands/AIOS/agents',
62
+ format: 'full-markdown-yaml',
63
+ },
64
+ cursor: {
65
+ enabled: true,
66
+ path: '.cursor/rules/agents',
67
+ format: 'condensed-rules',
68
+ },
69
+ windsurf: {
70
+ enabled: true,
71
+ path: '.windsurf/rules/agents',
72
+ format: 'xml-tagged-markdown',
73
+ },
74
+ trae: {
75
+ enabled: true,
76
+ path: '.trae/rules/agents',
77
+ format: 'project-rules',
78
+ },
79
+ antigravity: {
80
+ enabled: true,
81
+ path: '.antigravity/rules/agents',
82
+ format: 'cursor-style',
83
+ },
84
+ },
85
+ redirects: {
86
+ 'aios-developer': 'aios-master',
87
+ 'aios-orchestrator': 'aios-master',
88
+ 'db-sage': 'data-engineer',
89
+ 'github-devops': 'devops',
90
+ },
91
+ validation: {
92
+ strictMode: true,
93
+ failOnDrift: true,
94
+ failOnOrphaned: false,
95
+ },
96
+ };
97
+
98
+ try {
99
+ if (fs.existsSync(configPath)) {
100
+ const content = fs.readFileSync(configPath, 'utf8');
101
+ const config = yaml.load(content);
102
+
103
+ if (config && config.ideSync) {
104
+ return { ...defaultConfig, ...config.ideSync };
105
+ }
106
+ }
107
+ } catch (error) {
108
+ console.warn(`${colors.yellow}Warning: Could not load config, using defaults${colors.reset}`);
109
+ }
110
+
111
+ return defaultConfig;
112
+ }
113
+
114
+ /**
115
+ * Get transformer for IDE format
116
+ * @param {string} format - IDE format name
117
+ * @returns {object} - Transformer module
118
+ */
119
+ function getTransformer(format) {
120
+ const transformers = {
121
+ 'full-markdown-yaml': claudeCodeTransformer,
122
+ 'condensed-rules': cursorTransformer,
123
+ 'xml-tagged-markdown': windsurfTransformer,
124
+ 'project-rules': traeTransformer,
125
+ 'cursor-style': antigravityTransformer,
126
+ };
127
+
128
+ return transformers[format] || claudeCodeTransformer;
129
+ }
130
+
131
+ /**
132
+ * Sync agents to a specific IDE
133
+ * @param {object[]} agents - Parsed agent data
134
+ * @param {object} ideConfig - IDE configuration
135
+ * @param {string} ideName - IDE name
136
+ * @param {string} projectRoot - Project root
137
+ * @param {object} options - Sync options
138
+ * @returns {object} - Sync result
139
+ */
140
+ function syncIde(agents, ideConfig, ideName, projectRoot, options) {
141
+ const result = {
142
+ ide: ideName,
143
+ targetDir: path.join(projectRoot, ideConfig.path),
144
+ files: [],
145
+ errors: [],
146
+ };
147
+
148
+ if (!ideConfig.enabled) {
149
+ result.skipped = true;
150
+ return result;
151
+ }
152
+
153
+ const transformer = getTransformer(ideConfig.format);
154
+
155
+ // Ensure target directory exists
156
+ if (!options.dryRun) {
157
+ fs.ensureDirSync(result.targetDir);
158
+ }
159
+
160
+ // Transform and write each agent
161
+ for (const agent of agents) {
162
+ // Skip agents with fatal errors (no YAML block found or failed parse with no fallback)
163
+ if (agent.error && agent.error === 'Failed to parse YAML') {
164
+ result.errors.push({
165
+ agent: agent.id,
166
+ error: agent.error,
167
+ });
168
+ continue;
169
+ }
170
+ if (agent.error && agent.error === 'No YAML block found') {
171
+ result.errors.push({
172
+ agent: agent.id,
173
+ error: agent.error,
174
+ });
175
+ continue;
176
+ }
177
+
178
+ try {
179
+ const content = transformer.transform(agent);
180
+ const filename = transformer.getFilename(agent);
181
+ const targetPath = path.join(result.targetDir, filename);
182
+
183
+ if (!options.dryRun) {
184
+ fs.writeFileSync(targetPath, content, 'utf8');
185
+ }
186
+
187
+ result.files.push({
188
+ agent: agent.id,
189
+ filename,
190
+ path: targetPath,
191
+ content,
192
+ });
193
+ } catch (error) {
194
+ result.errors.push({
195
+ agent: agent.id,
196
+ error: error.message,
197
+ });
198
+ }
199
+ }
200
+
201
+ return result;
202
+ }
203
+
204
+ /**
205
+ * Execute sync command
206
+ * @param {object} options - Command options
207
+ */
208
+ async function commandSync(options) {
209
+ const projectRoot = process.cwd();
210
+ const config = loadConfig(projectRoot);
211
+
212
+ if (!config.enabled) {
213
+ console.log(`${colors.yellow}IDE sync is disabled in config${colors.reset}`);
214
+ return;
215
+ }
216
+
217
+ console.log(`${colors.bright}${colors.blue}🔄 IDE Sync${colors.reset}`);
218
+ console.log('');
219
+
220
+ // Parse all agents
221
+ const agentsDir = path.join(projectRoot, config.source);
222
+ console.log(`${colors.dim}Source: ${agentsDir}${colors.reset}`);
223
+
224
+ const agents = parseAllAgents(agentsDir);
225
+ console.log(`${colors.dim}Found ${agents.length} agents${colors.reset}`);
226
+ console.log('');
227
+
228
+ // Filter IDEs if --ide flag specified
229
+ let targetIdes = Object.entries(config.targets);
230
+ if (options.ide) {
231
+ targetIdes = targetIdes.filter(([name]) => name === options.ide);
232
+ if (targetIdes.length === 0) {
233
+ console.error(`${colors.red}Error: IDE '${options.ide}' not found in config${colors.reset}`);
234
+ process.exit(1);
235
+ }
236
+ }
237
+
238
+ const results = [];
239
+
240
+ // Sync to each IDE
241
+ for (const [ideName, ideConfig] of targetIdes) {
242
+ if (!ideConfig.enabled) {
243
+ console.log(`${colors.dim}⏭️ ${ideName}: skipped (disabled)${colors.reset}`);
244
+ continue;
245
+ }
246
+
247
+ console.log(`${colors.cyan}📁 Syncing ${ideName}...${colors.reset}`);
248
+
249
+ const result = syncIde(agents, ideConfig, ideName, projectRoot, options);
250
+ results.push(result);
251
+
252
+ // Generate redirects for this IDE
253
+ const redirects = generateAllRedirects(
254
+ config.redirects,
255
+ result.targetDir,
256
+ ideConfig.format
257
+ );
258
+ const redirectResult = writeRedirects(redirects, options.dryRun);
259
+
260
+ if (options.verbose) {
261
+ console.log(` ${colors.dim}Target: ${result.targetDir}${colors.reset}`);
262
+ }
263
+
264
+ const agentCount = result.files.length;
265
+ const redirectCount = redirectResult.written.length;
266
+ const errorCount = result.errors.length;
267
+
268
+ let status = `${colors.green}✓${colors.reset}`;
269
+ if (errorCount > 0) {
270
+ status = `${colors.yellow}⚠${colors.reset}`;
271
+ }
272
+
273
+ console.log(
274
+ ` ${status} ${agentCount} agents, ${redirectCount} redirects${errorCount > 0 ? `, ${errorCount} errors` : ''}`
275
+ );
276
+
277
+ if (options.verbose && result.errors.length > 0) {
278
+ for (const err of result.errors) {
279
+ console.log(` ${colors.red}✗ ${err.agent}: ${err.error}${colors.reset}`);
280
+ }
281
+ }
282
+ }
283
+
284
+ console.log('');
285
+
286
+ // Summary
287
+ const totalFiles = results.reduce((sum, r) => sum + r.files.length, 0);
288
+ const totalRedirects = Object.keys(config.redirects).length * targetIdes.filter(([, c]) => c.enabled).length;
289
+ const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
290
+
291
+ if (options.dryRun) {
292
+ console.log(`${colors.yellow}Dry run: ${totalFiles} agents + ${totalRedirects} redirects would be written${colors.reset}`);
293
+ } else {
294
+ console.log(`${colors.green}✅ Sync complete: ${totalFiles} agents + ${totalRedirects} redirects${colors.reset}`);
295
+ }
296
+
297
+ if (totalErrors > 0) {
298
+ console.log(`${colors.yellow}⚠️ ${totalErrors} errors occurred${colors.reset}`);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Execute validate command
304
+ * @param {object} options - Command options
305
+ */
306
+ async function commandValidate(options) {
307
+ const projectRoot = process.cwd();
308
+ const config = loadConfig(projectRoot);
309
+
310
+ if (!config.enabled) {
311
+ console.log(`${colors.yellow}IDE sync is disabled in config${colors.reset}`);
312
+ return;
313
+ }
314
+
315
+ console.log(`${colors.bright}${colors.blue}🔍 IDE Sync Validation${colors.reset}`);
316
+ console.log('');
317
+
318
+ // Parse all agents
319
+ const agentsDir = path.join(projectRoot, config.source);
320
+ const agents = parseAllAgents(agentsDir);
321
+
322
+ // Build expected files for each IDE
323
+ const ideConfigs = {};
324
+
325
+ for (const [ideName, ideConfig] of Object.entries(config.targets)) {
326
+ if (!ideConfig.enabled) continue;
327
+
328
+ const transformer = getTransformer(ideConfig.format);
329
+ const expectedFiles = [];
330
+
331
+ for (const agent of agents) {
332
+ if (agent.error) continue;
333
+
334
+ try {
335
+ const content = transformer.transform(agent);
336
+ const filename = transformer.getFilename(agent);
337
+ expectedFiles.push({ filename, content });
338
+ } catch (error) {
339
+ // Skip agents that fail to transform
340
+ }
341
+ }
342
+
343
+ // Add redirect files
344
+ const redirects = generateAllRedirects(
345
+ config.redirects,
346
+ path.join(projectRoot, ideConfig.path),
347
+ ideConfig.format
348
+ );
349
+
350
+ for (const redirect of redirects) {
351
+ expectedFiles.push({
352
+ filename: redirect.filename,
353
+ content: redirect.content,
354
+ });
355
+ }
356
+
357
+ ideConfigs[ideName] = {
358
+ expectedFiles,
359
+ targetDir: path.join(projectRoot, ideConfig.path),
360
+ };
361
+ }
362
+
363
+ // Validate
364
+ const results = validateAllIdes(ideConfigs, config.redirects);
365
+
366
+ // Output report
367
+ const report = formatValidationReport(results, options.verbose);
368
+ console.log(report);
369
+
370
+ // Exit code
371
+ if (options.strict && !results.summary.pass) {
372
+ console.log('');
373
+ console.log(`${colors.red}Validation failed in strict mode${colors.reset}`);
374
+ process.exit(1);
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Parse command line arguments
380
+ * @returns {object} - Parsed options
381
+ */
382
+ function parseArgs() {
383
+ const args = process.argv.slice(2);
384
+ const options = {
385
+ command: args[0] || 'sync',
386
+ ide: null,
387
+ strict: false,
388
+ dryRun: false,
389
+ verbose: false,
390
+ };
391
+
392
+ for (let i = 1; i < args.length; i++) {
393
+ const arg = args[i];
394
+
395
+ if (arg === '--ide' && args[i + 1]) {
396
+ options.ide = args[++i];
397
+ } else if (arg === '--strict') {
398
+ options.strict = true;
399
+ } else if (arg === '--dry-run') {
400
+ options.dryRun = true;
401
+ } else if (arg === '--verbose' || arg === '-v') {
402
+ options.verbose = true;
403
+ }
404
+ }
405
+
406
+ return options;
407
+ }
408
+
409
+ /**
410
+ * Show help
411
+ */
412
+ function showHelp() {
413
+ console.log(`
414
+ ${colors.bright}IDE Sync${colors.reset} - Sync AIOS agents to IDE command files
415
+
416
+ ${colors.bright}Usage:${colors.reset}
417
+ node ide-sync/index.js <command> [options]
418
+
419
+ ${colors.bright}Commands:${colors.reset}
420
+ sync Sync agents to all enabled IDEs (default)
421
+ validate Validate sync status
422
+ report Generate sync status report (alias for validate)
423
+
424
+ ${colors.bright}Options:${colors.reset}
425
+ --ide <name> Sync/validate specific IDE only
426
+ --strict Exit with code 1 if drift detected (CI mode)
427
+ --dry-run Preview changes without writing files
428
+ --verbose, -v Show detailed output
429
+
430
+ ${colors.bright}Examples:${colors.reset}
431
+ node ide-sync/index.js sync
432
+ node ide-sync/index.js sync --ide cursor
433
+ node ide-sync/index.js validate --strict
434
+ node ide-sync/index.js sync --dry-run --verbose
435
+ `);
436
+ }
437
+
438
+ /**
439
+ * Main entry point
440
+ */
441
+ async function main() {
442
+ const options = parseArgs();
443
+
444
+ if (options.command === 'help' || options.command === '--help' || options.command === '-h') {
445
+ showHelp();
446
+ return;
447
+ }
448
+
449
+ switch (options.command) {
450
+ case 'sync':
451
+ await commandSync(options);
452
+ break;
453
+
454
+ case 'validate':
455
+ case 'report':
456
+ await commandValidate(options);
457
+ break;
458
+
459
+ default:
460
+ console.error(`${colors.red}Unknown command: ${options.command}${colors.reset}`);
461
+ showHelp();
462
+ process.exit(1);
463
+ }
464
+ }
465
+
466
+ // Run if executed directly
467
+ if (require.main === module) {
468
+ main().catch(error => {
469
+ console.error(`${colors.red}Error: ${error.message}${colors.reset}`);
470
+ process.exit(1);
471
+ });
472
+ }
473
+
474
+ module.exports = {
475
+ loadConfig,
476
+ getTransformer,
477
+ syncIde,
478
+ commandSync,
479
+ commandValidate,
480
+ };
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Redirect Generator - Creates redirect files for deprecated agents
3
+ * @story 6.19 - IDE Command Auto-Sync System
4
+ */
5
+
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Default redirects configuration
11
+ * Maps deprecated agent IDs to their new target IDs
12
+ */
13
+ const DEFAULT_REDIRECTS = {
14
+ 'aios-developer': 'aios-master',
15
+ 'aios-orchestrator': 'aios-master',
16
+ 'db-sage': 'data-engineer',
17
+ 'github-devops': 'devops',
18
+ };
19
+
20
+ /**
21
+ * Generate redirect content for a specific IDE format
22
+ * @param {string} oldId - Deprecated agent ID
23
+ * @param {string} newId - New target agent ID
24
+ * @param {string} format - IDE format
25
+ * @returns {string} - Redirect file content
26
+ */
27
+ function generateRedirectContent(oldId, newId, format) {
28
+ const baseContent = {
29
+ header: `# Agent Redirect: ${oldId} → ${newId}`,
30
+ notice: `**DEPRECATED:** This agent has been renamed/merged.`,
31
+ instruction: `Use \`@${newId}\` instead.`,
32
+ };
33
+
34
+ switch (format) {
35
+ case 'full-markdown-yaml':
36
+ // Claude Code format
37
+ return `${baseContent.header}
38
+
39
+ ${baseContent.notice}
40
+
41
+ ${baseContent.instruction}
42
+
43
+ ---
44
+
45
+ ## Redirect Details
46
+
47
+ | Property | Value |
48
+ |----------|-------|
49
+ | Old ID | @${oldId} |
50
+ | New ID | @${newId} |
51
+ | Status | Deprecated |
52
+
53
+ ---
54
+ *AIOS Redirect - Synced automatically*
55
+ `;
56
+
57
+ case 'xml-tagged-markdown':
58
+ // Windsurf format
59
+ return `${baseContent.header}
60
+
61
+ <redirect>
62
+ Old: @${oldId}
63
+ New: @${newId}
64
+ Status: Deprecated
65
+ </redirect>
66
+
67
+ <notice>
68
+ ${baseContent.notice}
69
+ ${baseContent.instruction}
70
+ </notice>
71
+
72
+ ---
73
+ *AIOS Redirect - Synced automatically*
74
+ `;
75
+
76
+ case 'project-rules':
77
+ // Trae format
78
+ return `${baseContent.header}
79
+
80
+ ## Redirect Notice
81
+
82
+ ${baseContent.notice}
83
+
84
+ ## Action Required
85
+
86
+ ${baseContent.instruction}
87
+
88
+ ## Mapping
89
+
90
+ | Old Agent | New Agent |
91
+ |-----------|-----------|
92
+ | @${oldId} | @${newId} |
93
+
94
+ ---
95
+ *AIOS Redirect - Synced automatically*
96
+ `;
97
+
98
+ case 'condensed-rules':
99
+ case 'cursor-style':
100
+ default:
101
+ // Cursor/Antigravity format
102
+ return `${baseContent.header}
103
+
104
+ > ${baseContent.notice} ${baseContent.instruction}
105
+
106
+ ---
107
+ *AIOS Redirect - Synced automatically*
108
+ `;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Generate redirect file for a deprecated agent
114
+ * @param {string} oldId - Deprecated agent ID
115
+ * @param {string} newId - New target agent ID
116
+ * @param {string} targetDir - Target directory for the redirect file
117
+ * @param {string} format - IDE format
118
+ * @returns {object} - Result with path and content
119
+ */
120
+ function generateRedirect(oldId, newId, targetDir, format) {
121
+ const filename = `${oldId}.md`;
122
+ const filePath = path.join(targetDir, filename);
123
+ const content = generateRedirectContent(oldId, newId, format);
124
+
125
+ return {
126
+ oldId,
127
+ newId,
128
+ filename,
129
+ path: filePath,
130
+ content,
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Generate all redirects for a specific IDE
136
+ * @param {object} redirectsConfig - Redirects configuration (oldId -> newId)
137
+ * @param {string} targetDir - Target directory
138
+ * @param {string} format - IDE format
139
+ * @returns {object[]} - Array of redirect objects
140
+ */
141
+ function generateAllRedirects(redirectsConfig, targetDir, format) {
142
+ const redirects = redirectsConfig || DEFAULT_REDIRECTS;
143
+ const results = [];
144
+
145
+ for (const [oldId, newId] of Object.entries(redirects)) {
146
+ const redirect = generateRedirect(oldId, newId, targetDir, format);
147
+ results.push(redirect);
148
+ }
149
+
150
+ return results;
151
+ }
152
+
153
+ /**
154
+ * Write redirect files to disk
155
+ * @param {object[]} redirects - Array of redirect objects
156
+ * @param {boolean} dryRun - If true, don't write files
157
+ * @returns {object} - Result summary
158
+ */
159
+ function writeRedirects(redirects, dryRun = false) {
160
+ const results = {
161
+ written: [],
162
+ errors: [],
163
+ };
164
+
165
+ for (const redirect of redirects) {
166
+ try {
167
+ if (!dryRun) {
168
+ fs.ensureDirSync(path.dirname(redirect.path));
169
+ fs.writeFileSync(redirect.path, redirect.content, 'utf8');
170
+ }
171
+ results.written.push(redirect.path);
172
+ } catch (error) {
173
+ results.errors.push({
174
+ path: redirect.path,
175
+ error: error.message,
176
+ });
177
+ }
178
+ }
179
+
180
+ return results;
181
+ }
182
+
183
+ /**
184
+ * Get list of redirect filenames
185
+ * @param {object} redirectsConfig - Redirects configuration
186
+ * @returns {string[]} - Array of filenames
187
+ */
188
+ function getRedirectFilenames(redirectsConfig) {
189
+ const redirects = redirectsConfig || DEFAULT_REDIRECTS;
190
+ return Object.keys(redirects).map(id => `${id}.md`);
191
+ }
192
+
193
+ module.exports = {
194
+ DEFAULT_REDIRECTS,
195
+ generateRedirectContent,
196
+ generateRedirect,
197
+ generateAllRedirects,
198
+ writeRedirects,
199
+ getRedirectFilenames,
200
+ };