aios-core 2.3.1 → 3.1.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,1317 @@
1
+ /**
2
+ * Squad Generator
3
+ *
4
+ * Generates squad structure following task-first architecture.
5
+ * Used by the *create-squad task of the squad-creator agent.
6
+ *
7
+ * @module squad-generator
8
+ * @version 1.0.0
9
+ * @see Story SQS-4: Squad Creator Agent + Tasks
10
+ */
11
+
12
+ const fs = require('fs').promises;
13
+ const path = require('path');
14
+ const { execSync } = require('child_process');
15
+ const yaml = require('js-yaml');
16
+
17
+ /**
18
+ * Default path for squads directory
19
+ * @constant {string}
20
+ */
21
+ const DEFAULT_SQUADS_PATH = './squads';
22
+
23
+ /**
24
+ * Default path for squad designs directory
25
+ * @constant {string}
26
+ */
27
+ const DEFAULT_DESIGNS_PATH = './squads/.designs';
28
+
29
+ /**
30
+ * Path to squad design schema
31
+ * @constant {string}
32
+ */
33
+ const SQUAD_DESIGN_SCHEMA_PATH = path.join(__dirname, '../../schemas/squad-design-schema.json');
34
+
35
+ /**
36
+ * Default AIOS minimum version
37
+ * @constant {string}
38
+ */
39
+ const DEFAULT_AIOS_MIN_VERSION = '2.1.0';
40
+
41
+ /**
42
+ * Available templates
43
+ * @constant {string[]}
44
+ */
45
+ const AVAILABLE_TEMPLATES = ['basic', 'etl', 'agent-only'];
46
+
47
+ /**
48
+ * Available config modes
49
+ * @constant {string[]}
50
+ */
51
+ const CONFIG_MODES = ['extend', 'override', 'none'];
52
+
53
+ /**
54
+ * Available licenses
55
+ * @constant {string[]}
56
+ */
57
+ const AVAILABLE_LICENSES = ['MIT', 'Apache-2.0', 'ISC', 'UNLICENSED'];
58
+
59
+ /**
60
+ * Directories to create in squad structure
61
+ * @constant {string[]}
62
+ */
63
+ const SQUAD_DIRECTORIES = [
64
+ '',
65
+ 'config',
66
+ 'agents',
67
+ 'tasks',
68
+ 'workflows',
69
+ 'checklists',
70
+ 'templates',
71
+ 'tools',
72
+ 'scripts',
73
+ 'data',
74
+ ];
75
+
76
+ /**
77
+ * Error codes for SquadGeneratorError
78
+ * @enum {string}
79
+ */
80
+ const GeneratorErrorCodes = {
81
+ INVALID_NAME: 'INVALID_NAME',
82
+ SQUAD_EXISTS: 'SQUAD_EXISTS',
83
+ PERMISSION_DENIED: 'PERMISSION_DENIED',
84
+ TEMPLATE_NOT_FOUND: 'TEMPLATE_NOT_FOUND',
85
+ INVALID_CONFIG_MODE: 'INVALID_CONFIG_MODE',
86
+ BLUEPRINT_NOT_FOUND: 'BLUEPRINT_NOT_FOUND',
87
+ BLUEPRINT_INVALID: 'BLUEPRINT_INVALID',
88
+ BLUEPRINT_PARSE_ERROR: 'BLUEPRINT_PARSE_ERROR',
89
+ SCHEMA_NOT_FOUND: 'SCHEMA_NOT_FOUND',
90
+ };
91
+
92
+ /**
93
+ * Custom error class for Squad Generator operations
94
+ * @extends Error
95
+ */
96
+ class SquadGeneratorError extends Error {
97
+ /**
98
+ * Create a SquadGeneratorError
99
+ * @param {string} code - Error code from GeneratorErrorCodes enum
100
+ * @param {string} message - Human-readable error message
101
+ * @param {string} [suggestion] - Suggested fix for the error
102
+ */
103
+ constructor(code, message, suggestion) {
104
+ super(message);
105
+ this.name = 'SquadGeneratorError';
106
+ this.code = code;
107
+ this.suggestion = suggestion || '';
108
+
109
+ if (Error.captureStackTrace) {
110
+ Error.captureStackTrace(this, SquadGeneratorError);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Create error for invalid squad name
116
+ * @param {string} name - Invalid name provided
117
+ * @returns {SquadGeneratorError}
118
+ */
119
+ static invalidName(name) {
120
+ return new SquadGeneratorError(
121
+ GeneratorErrorCodes.INVALID_NAME,
122
+ `Invalid squad name "${name}": must be kebab-case (lowercase letters, numbers, hyphens)`,
123
+ 'Use format: my-squad-name (lowercase, hyphens only)',
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Create error for existing squad
129
+ * @param {string} name - Squad name that exists
130
+ * @param {string} squadPath - Path where squad exists
131
+ * @returns {SquadGeneratorError}
132
+ */
133
+ static squadExists(name, squadPath) {
134
+ return new SquadGeneratorError(
135
+ GeneratorErrorCodes.SQUAD_EXISTS,
136
+ `Squad "${name}" already exists at ${squadPath}`,
137
+ `Choose a different name or delete existing squad: rm -rf ${squadPath}`,
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Create error for invalid template
143
+ * @param {string} template - Invalid template name
144
+ * @returns {SquadGeneratorError}
145
+ */
146
+ static templateNotFound(template) {
147
+ return new SquadGeneratorError(
148
+ GeneratorErrorCodes.TEMPLATE_NOT_FOUND,
149
+ `Template "${template}" not found`,
150
+ `Available templates: ${AVAILABLE_TEMPLATES.join(', ')}`,
151
+ );
152
+ }
153
+
154
+ /**
155
+ * Create error for invalid config mode
156
+ * @param {string} mode - Invalid config mode
157
+ * @returns {SquadGeneratorError}
158
+ */
159
+ static invalidConfigMode(mode) {
160
+ return new SquadGeneratorError(
161
+ GeneratorErrorCodes.INVALID_CONFIG_MODE,
162
+ `Invalid config mode "${mode}"`,
163
+ `Available modes: ${CONFIG_MODES.join(', ')}`,
164
+ );
165
+ }
166
+
167
+ /**
168
+ * Create error for blueprint not found
169
+ * @param {string} blueprintPath - Path that doesn't exist
170
+ * @returns {SquadGeneratorError}
171
+ */
172
+ static blueprintNotFound(blueprintPath) {
173
+ return new SquadGeneratorError(
174
+ GeneratorErrorCodes.BLUEPRINT_NOT_FOUND,
175
+ `Blueprint not found at "${blueprintPath}"`,
176
+ 'Generate a blueprint first: *design-squad --docs ./your-docs.md',
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Create error for blueprint parse error
182
+ * @param {string} blueprintPath - Path to blueprint
183
+ * @param {string} parseError - Parse error message
184
+ * @returns {SquadGeneratorError}
185
+ */
186
+ static blueprintParseError(blueprintPath, parseError) {
187
+ return new SquadGeneratorError(
188
+ GeneratorErrorCodes.BLUEPRINT_PARSE_ERROR,
189
+ `Failed to parse blueprint at "${blueprintPath}": ${parseError}`,
190
+ 'Ensure blueprint is valid YAML format',
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Create error for invalid blueprint
196
+ * @param {string[]} validationErrors - List of validation errors
197
+ * @returns {SquadGeneratorError}
198
+ */
199
+ static blueprintInvalid(validationErrors) {
200
+ return new SquadGeneratorError(
201
+ GeneratorErrorCodes.BLUEPRINT_INVALID,
202
+ `Blueprint validation failed:\n - ${validationErrors.join('\n - ')}`,
203
+ 'Fix the validation errors and try again',
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Create error for schema not found
209
+ * @param {string} schemaPath - Path to schema
210
+ * @returns {SquadGeneratorError}
211
+ */
212
+ static schemaNotFound(schemaPath) {
213
+ return new SquadGeneratorError(
214
+ GeneratorErrorCodes.SCHEMA_NOT_FOUND,
215
+ `Schema not found at "${schemaPath}"`,
216
+ 'Ensure AIOS is properly installed',
217
+ );
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Get git user name
223
+ * @returns {string} Git user name or 'Unknown'
224
+ */
225
+ function getGitUserName() {
226
+ try {
227
+ const name = execSync('git config user.name', { encoding: 'utf-8' }).trim();
228
+ return name || 'Unknown';
229
+ } catch {
230
+ return 'Unknown';
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Validate squad name (kebab-case)
236
+ * @param {string} name - Name to validate
237
+ * @returns {boolean} True if valid
238
+ */
239
+ function isValidSquadName(name) {
240
+ // Must be kebab-case: lowercase letters, numbers, and hyphens
241
+ // Must start with letter, end with letter or number
242
+ // Minimum 2 characters
243
+ return /^[a-z][a-z0-9-]*[a-z0-9]$/.test(name) && name.length >= 2;
244
+ }
245
+
246
+ /**
247
+ * Extract slash prefix from squad name
248
+ * @param {string} name - Squad name
249
+ * @returns {string} Slash prefix
250
+ */
251
+ function extractSlashPrefix(name) {
252
+ // Remove -squad suffix if present
253
+ return name.replace(/-squad$/, '');
254
+ }
255
+
256
+ /**
257
+ * Safely quote YAML values that may contain special characters
258
+ * @param {string} val - Value to quote
259
+ * @returns {string} Safely quoted value
260
+ */
261
+ function safeYamlValue(val) {
262
+ if (!val) return '""';
263
+ // Quote if contains special YAML characters or leading/trailing spaces
264
+ if (/[:\n\r"']/.test(val) || val.startsWith(' ') || val.endsWith(' ')) {
265
+ return `"${val.replace(/"/g, '\\"')}"`;
266
+ }
267
+ return val;
268
+ }
269
+
270
+ // =============================================================================
271
+ // TEMPLATES
272
+ // =============================================================================
273
+
274
+ /**
275
+ * Generate squad.yaml content
276
+ * @param {Object} config - Squad configuration
277
+ * @returns {string} YAML content
278
+ */
279
+ function generateSquadYaml(config) {
280
+ const components = {
281
+ tasks: config.includeTask ? ['example-agent-task.md'] : [],
282
+ agents: config.includeAgent ? ['example-agent.md'] : [],
283
+ workflows: [],
284
+ checklists: [],
285
+ templates: [],
286
+ tools: [],
287
+ scripts: [],
288
+ };
289
+
290
+ // For etl template, add more components
291
+ if (config.template === 'etl') {
292
+ components.agents = ['data-extractor.md', 'data-transformer.md'];
293
+ components.tasks = [
294
+ 'extract-data.md',
295
+ 'transform-data.md',
296
+ 'load-data.md',
297
+ ];
298
+ components.scripts = ['utils.js'];
299
+ }
300
+
301
+ // For agent-only template
302
+ if (config.template === 'agent-only') {
303
+ components.agents = ['primary-agent.md', 'helper-agent.md'];
304
+ components.tasks = [];
305
+ }
306
+
307
+ const configSection =
308
+ config.configMode === 'none'
309
+ ? {}
310
+ : {
311
+ extends: config.configMode,
312
+ 'coding-standards': 'config/coding-standards.md',
313
+ 'tech-stack': 'config/tech-stack.md',
314
+ 'source-tree': 'config/source-tree.md',
315
+ };
316
+
317
+ const yaml = `name: ${config.name}
318
+ version: 1.0.0
319
+ description: ${safeYamlValue(config.description || 'Custom squad')}
320
+ author: ${safeYamlValue(config.author || 'Unknown')}
321
+ license: ${config.license || 'MIT'}
322
+ slashPrefix: ${extractSlashPrefix(config.name)}
323
+
324
+ aios:
325
+ minVersion: "${config.aiosMinVersion || DEFAULT_AIOS_MIN_VERSION}"
326
+ type: squad
327
+
328
+ components:
329
+ tasks:${components.tasks.length ? '\n - ' + components.tasks.join('\n - ') : ' []'}
330
+ agents:${components.agents.length ? '\n - ' + components.agents.join('\n - ') : ' []'}
331
+ workflows: []
332
+ checklists: []
333
+ templates: []
334
+ tools: []
335
+ scripts:${components.scripts.length ? '\n - ' + components.scripts.join('\n - ') : ' []'}
336
+
337
+ config:${
338
+ config.configMode === 'none'
339
+ ? ' {}'
340
+ : `
341
+ extends: ${configSection.extends}
342
+ coding-standards: ${configSection['coding-standards']}
343
+ tech-stack: ${configSection['tech-stack']}
344
+ source-tree: ${configSection['source-tree']}`
345
+ }
346
+
347
+ dependencies:
348
+ node: []
349
+ python: []
350
+ squads: []
351
+
352
+ tags:
353
+ - custom
354
+ `;
355
+
356
+ return yaml;
357
+ }
358
+
359
+ /**
360
+ * Generate README.md content
361
+ * @param {Object} config - Squad configuration
362
+ * @returns {string} Markdown content
363
+ */
364
+ function generateReadme(config) {
365
+ return `# ${config.name}
366
+
367
+ ${config.description || 'Custom AIOS squad.'}
368
+
369
+ ## Installation
370
+
371
+ This squad is installed locally in your project:
372
+
373
+ \`\`\`
374
+ ./squads/${config.name}/
375
+ \`\`\`
376
+
377
+ ## Usage
378
+
379
+ Activate agents from this squad and use their commands.
380
+
381
+ ### Available Agents
382
+
383
+ ${config.includeAgent || config.template !== 'basic' ? '- **example-agent** - Example agent (customize or remove)' : '_No agents defined yet_'}
384
+
385
+ ### Available Tasks
386
+
387
+ ${config.includeTask || config.template === 'etl' ? '- **example-agent-task** - Example task (customize or remove)' : '_No tasks defined yet_'}
388
+
389
+ ## Configuration
390
+
391
+ This squad ${config.configMode === 'extend' ? 'extends' : config.configMode === 'override' ? 'overrides' : 'does not inherit'} the core AIOS configuration.
392
+
393
+ ## Development
394
+
395
+ 1. Add agents in \`agents/\` directory
396
+ 2. Add tasks in \`tasks/\` directory (task-first architecture!)
397
+ 3. Update \`squad.yaml\` components section
398
+ 4. Validate: \`@squad-creator *validate-squad ${config.name}\`
399
+
400
+ ## License
401
+
402
+ ${config.license || 'MIT'}
403
+ `;
404
+ }
405
+
406
+ /**
407
+ * Generate coding-standards.md content
408
+ * @param {Object} config - Squad configuration
409
+ * @returns {string} Markdown content
410
+ */
411
+ function generateCodingStandards(config) {
412
+ return `# Coding Standards - ${config.name}
413
+
414
+ > This file ${config.configMode === 'extend' ? 'extends' : config.configMode === 'override' ? 'overrides' : 'is independent of'} the core AIOS coding standards.
415
+
416
+ ## Code Style
417
+
418
+ - Follow consistent naming conventions
419
+ - Use meaningful variable and function names
420
+ - Keep functions small and focused
421
+ - Document complex logic with comments
422
+
423
+ ## File Organization
424
+
425
+ - Place agents in \`agents/\` directory
426
+ - Place tasks in \`tasks/\` directory
427
+ - Place utilities in \`scripts/\` directory
428
+
429
+ ## Testing
430
+
431
+ - Write tests for all new functionality
432
+ - Maintain test coverage above 80%
433
+ - Use descriptive test names
434
+
435
+ ## Documentation
436
+
437
+ - Document all public APIs
438
+ - Include examples in documentation
439
+ - Keep README.md up to date
440
+ `;
441
+ }
442
+
443
+ /**
444
+ * Generate tech-stack.md content
445
+ * @param {Object} config - Squad configuration
446
+ * @returns {string} Markdown content
447
+ */
448
+ function generateTechStack(config) {
449
+ return `# Tech Stack - ${config.name}
450
+
451
+ ## Runtime
452
+
453
+ - Node.js >= 18.x
454
+ - AIOS >= ${config.aiosMinVersion || DEFAULT_AIOS_MIN_VERSION}
455
+
456
+ ## Dependencies
457
+
458
+ _Add your squad's dependencies here_
459
+
460
+ ## Development Tools
461
+
462
+ - ESLint for code quality
463
+ - Jest for testing
464
+ - Prettier for formatting
465
+ `;
466
+ }
467
+
468
+ /**
469
+ * Generate source-tree.md content
470
+ * @param {Object} config - Squad configuration
471
+ * @returns {string} Markdown content
472
+ */
473
+ function generateSourceTree(config) {
474
+ return `# Source Tree - ${config.name}
475
+
476
+ \`\`\`
477
+ ${config.name}/
478
+ ├── squad.yaml # Squad manifest
479
+ ├── README.md # Documentation
480
+ ├── config/ # Configuration files
481
+ │ ├── coding-standards.md
482
+ │ ├── tech-stack.md
483
+ │ └── source-tree.md
484
+ ├── agents/ # Agent definitions
485
+ ├── tasks/ # Task definitions
486
+ ├── workflows/ # Multi-step workflows
487
+ ├── checklists/ # Validation checklists
488
+ ├── templates/ # Document templates
489
+ ├── tools/ # Custom tools
490
+ ├── scripts/ # Utility scripts
491
+ └── data/ # Static data
492
+ \`\`\`
493
+
494
+ ## Directory Purpose
495
+
496
+ | Directory | Purpose |
497
+ |-----------|---------|
498
+ | \`agents/\` | Agent persona definitions (.md) |
499
+ | \`tasks/\` | Executable task workflows (.md) |
500
+ | \`workflows/\` | Multi-step workflow definitions |
501
+ | \`checklists/\` | Validation and review checklists |
502
+ | \`templates/\` | Document and code templates |
503
+ | \`tools/\` | Custom tool definitions |
504
+ | \`scripts/\` | JavaScript/Python utilities |
505
+ | \`data/\` | Static data files |
506
+ `;
507
+ }
508
+
509
+ /**
510
+ * Generate example agent content
511
+ * @param {Object} config - Squad configuration
512
+ * @returns {string} Markdown content
513
+ */
514
+ function generateExampleAgent(config) {
515
+ const agentName = config.template === 'etl' ? 'data-extractor' : 'example-agent';
516
+ const title = config.template === 'etl' ? 'Data Extractor' : 'Example Agent';
517
+
518
+ return `# ${agentName}
519
+
520
+ ## Agent Definition
521
+
522
+ \`\`\`yaml
523
+ agent:
524
+ name: ${title.replace(/ /g, '')}
525
+ id: ${agentName}
526
+ title: ${title}
527
+ icon: "🤖"
528
+ whenToUse: "Use for ${config.template === 'etl' ? 'extracting data from sources' : 'example purposes - customize this'}"
529
+
530
+ persona:
531
+ role: ${config.template === 'etl' ? 'Data Extraction Specialist' : 'Example Specialist'}
532
+ style: Systematic, thorough
533
+ focus: ${config.template === 'etl' ? 'Extracting data efficiently' : 'Demonstrating squad structure'}
534
+
535
+ commands:
536
+ - name: help
537
+ description: "Show available commands"
538
+ - name: run
539
+ description: "${config.template === 'etl' ? 'Extract data from source' : 'Run example task'}"
540
+ task: ${config.template === 'etl' ? 'extract-data.md' : 'example-agent-task.md'}
541
+ \`\`\`
542
+
543
+ ## Usage
544
+
545
+ \`\`\`
546
+ @${agentName}
547
+ *help
548
+ *run
549
+ \`\`\`
550
+ `;
551
+ }
552
+
553
+ /**
554
+ * Generate example task content
555
+ * @param {Object} config - Squad configuration
556
+ * @returns {string} Markdown content
557
+ */
558
+ function generateExampleTask(config) {
559
+ const taskName = config.template === 'etl' ? 'extract-data' : 'example-agent-task';
560
+ const title = config.template === 'etl' ? 'Extract Data' : 'Example Task';
561
+
562
+ return `---
563
+ task: ${title}
564
+ responsavel: "@${config.template === 'etl' ? 'data-extractor' : 'example-agent'}"
565
+ responsavel_type: agent
566
+ atomic_layer: task
567
+ Entrada: |
568
+ - source: Data source path or URL
569
+ - format: Output format (json, csv, yaml)
570
+ Saida: |
571
+ - data: Extracted data
572
+ - status: Success or error message
573
+ Checklist:
574
+ - "[ ] Validate input parameters"
575
+ - "[ ] Connect to source"
576
+ - "[ ] Extract data"
577
+ - "[ ] Format output"
578
+ - "[ ] Return result"
579
+ ---
580
+
581
+ # *${taskName.replace(/-/g, '-')}
582
+
583
+ ${config.template === 'etl' ? 'Extracts data from the specified source.' : 'Example task demonstrating task-first architecture.'}
584
+
585
+ ## Usage
586
+
587
+ \`\`\`
588
+ @${config.template === 'etl' ? 'data-extractor' : 'example-agent'}
589
+ *${taskName.replace('example-agent-', '')} --source ./data/input.json --format json
590
+ \`\`\`
591
+
592
+ ## Parameters
593
+
594
+ | Parameter | Type | Required | Description |
595
+ |-----------|------|----------|-------------|
596
+ | \`--source\` | string | Yes | Data source path or URL |
597
+ | \`--format\` | string | No | Output format (default: json) |
598
+
599
+ ## Example
600
+
601
+ \`\`\`javascript
602
+ // This is a placeholder - implement your logic here
603
+ async function execute(options) {
604
+ const { source, format } = options;
605
+
606
+ // TODO: Implement extraction logic
607
+ console.log(\`Extracting from \${source} as \${format}\`);
608
+
609
+ return { status: 'success', data: {} };
610
+ }
611
+ \`\`\`
612
+ `;
613
+ }
614
+
615
+ // =============================================================================
616
+ // SQUAD GENERATOR CLASS
617
+ // =============================================================================
618
+
619
+ /**
620
+ * Squad Generator class
621
+ * Generates complete squad structure with all necessary files
622
+ */
623
+ class SquadGenerator {
624
+ /**
625
+ * Create a SquadGenerator
626
+ * @param {Object} options - Generator options
627
+ * @param {string} [options.squadsPath] - Path to squads directory
628
+ */
629
+ constructor(options = {}) {
630
+ this.squadsPath = options.squadsPath || DEFAULT_SQUADS_PATH;
631
+ }
632
+
633
+ /**
634
+ * Check if a path exists
635
+ * @param {string} filePath - Path to check
636
+ * @returns {Promise<boolean>} True if exists
637
+ */
638
+ async pathExists(filePath) {
639
+ try {
640
+ await fs.access(filePath);
641
+ return true;
642
+ } catch {
643
+ return false;
644
+ }
645
+ }
646
+
647
+ /**
648
+ * Validate generation configuration
649
+ * @param {Object} config - Configuration to validate
650
+ * @throws {SquadGeneratorError} If validation fails
651
+ */
652
+ validateConfig(config) {
653
+ // Validate name
654
+ if (!config.name) {
655
+ throw new SquadGeneratorError(
656
+ GeneratorErrorCodes.INVALID_NAME,
657
+ 'Squad name is required',
658
+ 'Provide a name: *create-squad my-squad-name',
659
+ );
660
+ }
661
+
662
+ if (!isValidSquadName(config.name)) {
663
+ throw SquadGeneratorError.invalidName(config.name);
664
+ }
665
+
666
+ // Validate template
667
+ if (config.template && !AVAILABLE_TEMPLATES.includes(config.template)) {
668
+ throw SquadGeneratorError.templateNotFound(config.template);
669
+ }
670
+
671
+ // Validate config mode
672
+ if (config.configMode && !CONFIG_MODES.includes(config.configMode)) {
673
+ throw SquadGeneratorError.invalidConfigMode(config.configMode);
674
+ }
675
+ }
676
+
677
+ /**
678
+ * Generate a new squad
679
+ * @param {Object} config - Squad configuration
680
+ * @param {string} config.name - Squad name (kebab-case)
681
+ * @param {string} [config.description] - Squad description
682
+ * @param {string} [config.author] - Author name
683
+ * @param {string} [config.license] - License type
684
+ * @param {string} [config.template='basic'] - Template type
685
+ * @param {string} [config.configMode='extend'] - Config inheritance mode
686
+ * @param {boolean} [config.includeAgent=true] - Include example agent
687
+ * @param {boolean} [config.includeTask=true] - Include example task
688
+ * @param {string} [config.aiosMinVersion] - Minimum AIOS version
689
+ * @returns {Promise<Object>} Generation result with path and files
690
+ * @throws {SquadGeneratorError} If generation fails
691
+ */
692
+ async generate(config) {
693
+ // Set defaults
694
+ const fullConfig = {
695
+ name: config.name,
696
+ description: config.description || 'Custom squad',
697
+ author: config.author || getGitUserName(),
698
+ license: config.license || 'MIT',
699
+ template: config.template || 'basic',
700
+ configMode: config.configMode || 'extend',
701
+ includeAgent: config.includeAgent !== false,
702
+ includeTask: config.includeTask !== false,
703
+ aiosMinVersion: config.aiosMinVersion || DEFAULT_AIOS_MIN_VERSION,
704
+ };
705
+
706
+ // Validate configuration
707
+ this.validateConfig(fullConfig);
708
+
709
+ const squadPath = path.join(this.squadsPath, fullConfig.name);
710
+
711
+ // Check if squad already exists
712
+ if (await this.pathExists(squadPath)) {
713
+ throw SquadGeneratorError.squadExists(fullConfig.name, squadPath);
714
+ }
715
+
716
+ // Create directories
717
+ for (const dir of SQUAD_DIRECTORIES) {
718
+ const dirPath = path.join(squadPath, dir);
719
+ await fs.mkdir(dirPath, { recursive: true });
720
+ }
721
+
722
+ // Track generated files
723
+ const files = [];
724
+
725
+ // Generate main files
726
+ const mainFiles = {
727
+ 'squad.yaml': generateSquadYaml(fullConfig),
728
+ 'README.md': generateReadme(fullConfig),
729
+ };
730
+
731
+ for (const [filename, content] of Object.entries(mainFiles)) {
732
+ const filePath = path.join(squadPath, filename);
733
+ await fs.writeFile(filePath, content, 'utf-8');
734
+ files.push(filePath);
735
+ }
736
+
737
+ // Generate config files
738
+ const configFiles = {
739
+ 'config/coding-standards.md': generateCodingStandards(fullConfig),
740
+ 'config/tech-stack.md': generateTechStack(fullConfig),
741
+ 'config/source-tree.md': generateSourceTree(fullConfig),
742
+ };
743
+
744
+ for (const [filename, content] of Object.entries(configFiles)) {
745
+ const filePath = path.join(squadPath, filename);
746
+ await fs.writeFile(filePath, content, 'utf-8');
747
+ files.push(filePath);
748
+ }
749
+
750
+ // Generate example agent if requested
751
+ if (fullConfig.includeAgent) {
752
+ const agentContent = generateExampleAgent(fullConfig);
753
+ const agentName =
754
+ fullConfig.template === 'etl' ? 'data-extractor.md' : 'example-agent.md';
755
+ const agentPath = path.join(squadPath, 'agents', agentName);
756
+ await fs.writeFile(agentPath, agentContent, 'utf-8');
757
+ files.push(agentPath);
758
+
759
+ // For ETL template, add second agent
760
+ if (fullConfig.template === 'etl') {
761
+ const transformerConfig = { ...fullConfig, template: 'basic' };
762
+ const transformerContent = generateExampleAgent(transformerConfig)
763
+ .replace(/data-extractor/g, 'data-transformer')
764
+ .replace(/Data Extractor/g, 'Data Transformer')
765
+ .replace(/extracting data/g, 'transforming data')
766
+ .replace(/extract-data/g, 'transform-data');
767
+ const transformerPath = path.join(squadPath, 'agents', 'data-transformer.md');
768
+ await fs.writeFile(transformerPath, transformerContent, 'utf-8');
769
+ files.push(transformerPath);
770
+ }
771
+
772
+ // For agent-only template, add agents
773
+ if (fullConfig.template === 'agent-only') {
774
+ const primaryContent = generateExampleAgent({ ...fullConfig, template: 'basic' })
775
+ .replace(/example-agent/g, 'primary-agent')
776
+ .replace(/Example Agent/g, 'Primary Agent');
777
+ const primaryPath = path.join(squadPath, 'agents', 'primary-agent.md');
778
+ await fs.writeFile(primaryPath, primaryContent, 'utf-8');
779
+ files.push(primaryPath);
780
+
781
+ const helperContent = generateExampleAgent({ ...fullConfig, template: 'basic' })
782
+ .replace(/example-agent/g, 'helper-agent')
783
+ .replace(/Example Agent/g, 'Helper Agent');
784
+ const helperPath = path.join(squadPath, 'agents', 'helper-agent.md');
785
+ await fs.writeFile(helperPath, helperContent, 'utf-8');
786
+ files.push(helperPath);
787
+ }
788
+ }
789
+
790
+ // Generate example task if requested
791
+ if (fullConfig.includeTask && fullConfig.template !== 'agent-only') {
792
+ const taskContent = generateExampleTask(fullConfig);
793
+ const taskName =
794
+ fullConfig.template === 'etl' ? 'extract-data.md' : 'example-agent-task.md';
795
+ const taskPath = path.join(squadPath, 'tasks', taskName);
796
+ await fs.writeFile(taskPath, taskContent, 'utf-8');
797
+ files.push(taskPath);
798
+
799
+ // For ETL template, add more tasks
800
+ if (fullConfig.template === 'etl') {
801
+ const transformTask = generateExampleTask(fullConfig)
802
+ .replace(/extract-data/g, 'transform-data')
803
+ .replace(/Extract Data/g, 'Transform Data')
804
+ .replace(/data-extractor/g, 'data-transformer')
805
+ .replace(/Extracts data/g, 'Transforms data');
806
+ const transformPath = path.join(squadPath, 'tasks', 'transform-data.md');
807
+ await fs.writeFile(transformPath, transformTask, 'utf-8');
808
+ files.push(transformPath);
809
+
810
+ const loadTask = generateExampleTask(fullConfig)
811
+ .replace(/extract-data/g, 'load-data')
812
+ .replace(/Extract Data/g, 'Load Data')
813
+ .replace(/data-extractor/g, 'data-loader')
814
+ .replace(/Extracts data/g, 'Loads data');
815
+ const loadPath = path.join(squadPath, 'tasks', 'load-data.md');
816
+ await fs.writeFile(loadPath, loadTask, 'utf-8');
817
+ files.push(loadPath);
818
+ }
819
+ }
820
+
821
+ // For ETL template, create utils.js script
822
+ if (fullConfig.template === 'etl') {
823
+ const utilsContent = `/**
824
+ * ETL Utilities
825
+ *
826
+ * Utility functions for ETL operations.
827
+ */
828
+
829
+ /**
830
+ * Format data for output
831
+ * @param {Object} data - Data to format
832
+ * @param {string} format - Output format (json, csv, yaml)
833
+ * @returns {string} Formatted data
834
+ */
835
+ function formatData(data, format = 'json') {
836
+ switch (format) {
837
+ case 'json':
838
+ return JSON.stringify(data, null, 2);
839
+ case 'csv':
840
+ // Simple CSV conversion
841
+ if (Array.isArray(data) && data.length > 0) {
842
+ const headers = Object.keys(data[0]);
843
+ const rows = data.map(row => headers.map(h => row[h]).join(','));
844
+ return [headers.join(','), ...rows].join('\\n');
845
+ }
846
+ return '';
847
+ case 'yaml':
848
+ // Simple YAML conversion
849
+ return Object.entries(data)
850
+ .map(([k, v]) => \`\${k}: \${JSON.stringify(v)}\`)
851
+ .join('\\n');
852
+ default:
853
+ return JSON.stringify(data);
854
+ }
855
+ }
856
+
857
+ module.exports = { formatData };
858
+ `;
859
+ const utilsPath = path.join(squadPath, 'scripts', 'utils.js');
860
+ await fs.writeFile(utilsPath, utilsContent, 'utf-8');
861
+ files.push(utilsPath);
862
+ }
863
+
864
+ // Add .gitkeep to empty directories
865
+ const emptyDirs = ['workflows', 'checklists', 'templates', 'tools', 'data'];
866
+ if (!fullConfig.includeAgent) {
867
+ emptyDirs.push('agents');
868
+ }
869
+ if (!fullConfig.includeTask || fullConfig.template === 'agent-only') {
870
+ emptyDirs.push('tasks');
871
+ }
872
+ if (fullConfig.template !== 'etl') {
873
+ emptyDirs.push('scripts');
874
+ }
875
+
876
+ for (const dir of emptyDirs) {
877
+ const gitkeepPath = path.join(squadPath, dir, '.gitkeep');
878
+ // Only create .gitkeep if directory is empty
879
+ try {
880
+ const dirContents = await fs.readdir(path.join(squadPath, dir));
881
+ if (dirContents.length === 0) {
882
+ await fs.writeFile(gitkeepPath, '', 'utf-8');
883
+ files.push(gitkeepPath);
884
+ }
885
+ } catch {
886
+ // Directory might not exist, create .gitkeep anyway
887
+ await fs.writeFile(gitkeepPath, '', 'utf-8');
888
+ files.push(gitkeepPath);
889
+ }
890
+ }
891
+
892
+ return {
893
+ path: squadPath,
894
+ files,
895
+ config: fullConfig,
896
+ };
897
+ }
898
+
899
+ /**
900
+ * List local squads
901
+ * @returns {Promise<Array>} List of squad info objects
902
+ */
903
+ async listLocal() {
904
+ const squads = [];
905
+
906
+ try {
907
+ const entries = await fs.readdir(this.squadsPath, { withFileTypes: true });
908
+
909
+ for (const entry of entries) {
910
+ if (!entry.isDirectory()) continue;
911
+
912
+ const squadPath = path.join(this.squadsPath, entry.name);
913
+
914
+ // Try to load manifest
915
+ try {
916
+ const manifestPath = path.join(squadPath, 'squad.yaml');
917
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
918
+
919
+ // Basic YAML parsing for key fields
920
+ const nameMatch = manifestContent.match(/^name:\s*(.+)$/m);
921
+ const versionMatch = manifestContent.match(/^version:\s*(.+)$/m);
922
+ const descriptionMatch = manifestContent.match(/^description:\s*(.+)$/m);
923
+
924
+ squads.push({
925
+ name: nameMatch ? nameMatch[1].trim() : entry.name,
926
+ version: versionMatch ? versionMatch[1].trim() : 'unknown',
927
+ description: descriptionMatch ? descriptionMatch[1].trim() : '',
928
+ path: squadPath,
929
+ });
930
+ } catch {
931
+ // Try config.yaml fallback
932
+ try {
933
+ const configPath = path.join(squadPath, 'config.yaml');
934
+ const configContent = await fs.readFile(configPath, 'utf-8');
935
+
936
+ const nameMatch = configContent.match(/^name:\s*(.+)$/m);
937
+ const versionMatch = configContent.match(/^version:\s*(.+)$/m);
938
+ const descriptionMatch = configContent.match(/^description:\s*(.+)$/m);
939
+
940
+ squads.push({
941
+ name: nameMatch ? nameMatch[1].trim() : entry.name,
942
+ version: versionMatch ? versionMatch[1].trim() : 'unknown',
943
+ description: descriptionMatch ? descriptionMatch[1].trim() : '',
944
+ path: squadPath,
945
+ deprecated: true, // Using config.yaml
946
+ });
947
+ } catch {
948
+ // No manifest found, still list but mark as invalid
949
+ squads.push({
950
+ name: entry.name,
951
+ version: 'unknown',
952
+ description: 'No manifest found',
953
+ path: squadPath,
954
+ invalid: true,
955
+ });
956
+ }
957
+ }
958
+ }
959
+ } catch (err) {
960
+ // Squads directory doesn't exist
961
+ if (err.code !== 'ENOENT') {
962
+ throw err;
963
+ }
964
+ }
965
+
966
+ return squads;
967
+ }
968
+
969
+ // ===========================================================================
970
+ // BLUEPRINT METHODS (--from-design support)
971
+ // ===========================================================================
972
+
973
+ /**
974
+ * Load a blueprint file from disk
975
+ * @param {string} blueprintPath - Path to blueprint YAML file
976
+ * @returns {Promise<Object>} Parsed blueprint object
977
+ * @throws {SquadGeneratorError} If file not found or parse error
978
+ */
979
+ async loadBlueprint(blueprintPath) {
980
+ // Check if blueprint exists
981
+ if (!(await this.pathExists(blueprintPath))) {
982
+ throw SquadGeneratorError.blueprintNotFound(blueprintPath);
983
+ }
984
+
985
+ try {
986
+ const content = await fs.readFile(blueprintPath, 'utf-8');
987
+ const blueprint = yaml.load(content);
988
+ return blueprint;
989
+ } catch (err) {
990
+ if (err.name === 'YAMLException') {
991
+ throw SquadGeneratorError.blueprintParseError(blueprintPath, err.message);
992
+ }
993
+ throw err;
994
+ }
995
+ }
996
+
997
+ /**
998
+ * Validate a blueprint against the schema
999
+ * @param {Object} blueprint - Blueprint object to validate
1000
+ * @returns {Object} Validation result with isValid and errors
1001
+ */
1002
+ async validateBlueprint(blueprint) {
1003
+ const errors = [];
1004
+
1005
+ // Required top-level fields
1006
+ if (!blueprint.squad) {
1007
+ errors.push('Missing required field: squad');
1008
+ } else {
1009
+ if (!blueprint.squad.name) {
1010
+ errors.push('Missing required field: squad.name');
1011
+ } else if (!isValidSquadName(blueprint.squad.name)) {
1012
+ errors.push(`Invalid squad name "${blueprint.squad.name}": must be kebab-case`);
1013
+ }
1014
+ if (!blueprint.squad.domain) {
1015
+ errors.push('Missing required field: squad.domain');
1016
+ }
1017
+ }
1018
+
1019
+ if (!blueprint.recommendations) {
1020
+ errors.push('Missing required field: recommendations');
1021
+ } else {
1022
+ if (!Array.isArray(blueprint.recommendations.agents)) {
1023
+ errors.push('recommendations.agents must be an array');
1024
+ } else {
1025
+ // Validate each agent
1026
+ blueprint.recommendations.agents.forEach((agent, idx) => {
1027
+ if (!agent.id) {
1028
+ errors.push(`recommendations.agents[${idx}]: missing required field "id"`);
1029
+ } else if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(agent.id)) {
1030
+ errors.push(`recommendations.agents[${idx}]: id "${agent.id}" must be kebab-case`);
1031
+ }
1032
+ if (!agent.role) {
1033
+ errors.push(`recommendations.agents[${idx}]: missing required field "role"`);
1034
+ }
1035
+ if (typeof agent.confidence !== 'number' || agent.confidence < 0 || agent.confidence > 1) {
1036
+ errors.push(`recommendations.agents[${idx}]: confidence must be a number between 0 and 1`);
1037
+ }
1038
+ });
1039
+ }
1040
+
1041
+ if (!Array.isArray(blueprint.recommendations.tasks)) {
1042
+ errors.push('recommendations.tasks must be an array');
1043
+ } else {
1044
+ // Validate each task
1045
+ blueprint.recommendations.tasks.forEach((task, idx) => {
1046
+ if (!task.name) {
1047
+ errors.push(`recommendations.tasks[${idx}]: missing required field "name"`);
1048
+ } else if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(task.name)) {
1049
+ errors.push(`recommendations.tasks[${idx}]: name "${task.name}" must be kebab-case`);
1050
+ }
1051
+ if (!task.agent) {
1052
+ errors.push(`recommendations.tasks[${idx}]: missing required field "agent"`);
1053
+ }
1054
+ if (typeof task.confidence !== 'number' || task.confidence < 0 || task.confidence > 1) {
1055
+ errors.push(`recommendations.tasks[${idx}]: confidence must be a number between 0 and 1`);
1056
+ }
1057
+ });
1058
+ }
1059
+ }
1060
+
1061
+ if (!blueprint.metadata) {
1062
+ errors.push('Missing required field: metadata');
1063
+ } else {
1064
+ if (!blueprint.metadata.created_at) {
1065
+ errors.push('Missing required field: metadata.created_at');
1066
+ }
1067
+ }
1068
+
1069
+ return {
1070
+ isValid: errors.length === 0,
1071
+ errors,
1072
+ };
1073
+ }
1074
+
1075
+ /**
1076
+ * Convert blueprint to SquadGenerator config format
1077
+ * @param {Object} blueprint - Validated blueprint object
1078
+ * @returns {Object} Config object for generate()
1079
+ */
1080
+ blueprintToConfig(blueprint) {
1081
+ const config = {
1082
+ name: blueprint.squad.name,
1083
+ description: blueprint.squad.description || `Squad for ${blueprint.squad.domain}`,
1084
+ template: blueprint.recommendations.template || 'custom',
1085
+ configMode: blueprint.recommendations.config_mode || 'extend',
1086
+ includeAgent: false, // We'll add custom agents, not example ones
1087
+ includeTask: false, // We'll add custom tasks, not example ones
1088
+ // Store blueprint data for custom generation
1089
+ _blueprint: blueprint,
1090
+ };
1091
+
1092
+ return config;
1093
+ }
1094
+
1095
+ /**
1096
+ * Generate agent markdown content from blueprint recommendation
1097
+ * @param {Object} agent - Agent recommendation from blueprint
1098
+ * @param {string} squadName - Name of the squad
1099
+ * @returns {string} Markdown content for agent file
1100
+ */
1101
+ generateAgentFromBlueprint(agent, squadName) {
1102
+ const commandsList = (agent.commands || [])
1103
+ .map(cmd => ` - name: ${cmd}\n description: "${cmd.replace(/-/g, ' ')} operation"`)
1104
+ .join('\n');
1105
+
1106
+ return `# ${agent.id}
1107
+
1108
+ ## Agent Definition
1109
+
1110
+ \`\`\`yaml
1111
+ agent:
1112
+ name: ${agent.id.replace(/-/g, '')}
1113
+ id: ${agent.id}
1114
+ title: "${agent.role}"
1115
+ icon: "🤖"
1116
+ whenToUse: "${agent.role}"
1117
+
1118
+ persona:
1119
+ role: ${agent.role}
1120
+ style: Systematic, thorough
1121
+ focus: Executing ${agent.id} responsibilities
1122
+
1123
+ commands:
1124
+ - name: help
1125
+ description: "Show available commands"
1126
+ ${commandsList}
1127
+ \`\`\`
1128
+
1129
+ ## Usage
1130
+
1131
+ \`\`\`
1132
+ @${agent.id}
1133
+ *help
1134
+ \`\`\`
1135
+
1136
+ ## Origin
1137
+
1138
+ Generated from squad design blueprint for ${squadName}.
1139
+ Confidence: ${Math.round(agent.confidence * 100)}%
1140
+ ${agent.user_added ? 'Added by user during design refinement.' : ''}
1141
+ ${agent.user_modified ? 'Modified by user during design refinement.' : ''}
1142
+ `;
1143
+ }
1144
+
1145
+ /**
1146
+ * Generate task markdown content from blueprint recommendation
1147
+ * @param {Object} task - Task recommendation from blueprint
1148
+ * @param {string} squadName - Name of the squad
1149
+ * @returns {string} Markdown content for task file
1150
+ */
1151
+ generateTaskFromBlueprint(task, squadName) {
1152
+ const entradaList = (task.entrada || []).map(e => ` - ${e}`).join('\n');
1153
+ const saidaList = (task.saida || []).map(s => ` - ${s}`).join('\n');
1154
+ const checklistItems = (task.checklist || [
1155
+ '[ ] Validate input parameters',
1156
+ '[ ] Execute main logic',
1157
+ '[ ] Format output',
1158
+ '[ ] Return result',
1159
+ ]).map(item => ` - "${item.startsWith('[') ? item : '[ ] ' + item}"`).join('\n');
1160
+
1161
+ return `---
1162
+ task: "${task.name.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}"
1163
+ responsavel: "@${task.agent}"
1164
+ responsavel_type: agent
1165
+ atomic_layer: task
1166
+ Entrada: |
1167
+ ${entradaList || ' - (no inputs defined)'}
1168
+ Saida: |
1169
+ ${saidaList || ' - (no outputs defined)'}
1170
+ Checklist:
1171
+ ${checklistItems}
1172
+ ---
1173
+
1174
+ # *${task.name}
1175
+
1176
+ Task generated from squad design blueprint for ${squadName}.
1177
+
1178
+ ## Usage
1179
+
1180
+ \`\`\`
1181
+ @${task.agent}
1182
+ *${task.name}
1183
+ \`\`\`
1184
+
1185
+ ## Parameters
1186
+
1187
+ | Parameter | Type | Required | Description |
1188
+ |-----------|------|----------|-------------|
1189
+ ${(task.entrada || []).map(e => `| \`${e}\` | string | Yes | ${e.replace(/_/g, ' ')} |`).join('\n') || '| - | - | - | No parameters defined |'}
1190
+
1191
+ ## Output
1192
+
1193
+ ${(task.saida || []).map(s => `- **${s}**: ${s.replace(/_/g, ' ')}`).join('\n') || '- No outputs defined'}
1194
+
1195
+ ## Origin
1196
+
1197
+ Confidence: ${Math.round(task.confidence * 100)}%
1198
+ `;
1199
+ }
1200
+
1201
+ /**
1202
+ * Generate a squad from a blueprint file
1203
+ * @param {string} blueprintPath - Path to blueprint YAML file
1204
+ * @param {Object} [options] - Additional options
1205
+ * @param {boolean} [options.force=false] - Force overwrite if squad exists
1206
+ * @returns {Promise<Object>} Generation result with path, files, and blueprint info
1207
+ * @throws {SquadGeneratorError} If blueprint is invalid or generation fails
1208
+ */
1209
+ async generateFromBlueprint(blueprintPath, options = {}) {
1210
+ // 1. Load blueprint
1211
+ const blueprint = await this.loadBlueprint(blueprintPath);
1212
+
1213
+ // 2. Validate blueprint
1214
+ const validation = await this.validateBlueprint(blueprint);
1215
+ if (!validation.isValid) {
1216
+ throw SquadGeneratorError.blueprintInvalid(validation.errors);
1217
+ }
1218
+
1219
+ // 3. Convert to config
1220
+ const config = this.blueprintToConfig(blueprint);
1221
+
1222
+ // Check for existing squad
1223
+ const squadPath = path.join(this.squadsPath, config.name);
1224
+ if (await this.pathExists(squadPath)) {
1225
+ if (!options.force) {
1226
+ throw SquadGeneratorError.squadExists(config.name, squadPath);
1227
+ }
1228
+ // If force, remove existing squad directory before regenerating
1229
+ await fs.rm(squadPath, { recursive: true, force: true });
1230
+ }
1231
+
1232
+ // 4. Generate base structure (without example agents/tasks)
1233
+ const result = await this.generate(config);
1234
+
1235
+ // 5. Generate custom agents from blueprint
1236
+ const agentFiles = [];
1237
+ for (const agent of blueprint.recommendations.agents || []) {
1238
+ const agentContent = this.generateAgentFromBlueprint(agent, config.name);
1239
+ const agentPath = path.join(squadPath, 'agents', `${agent.id}.md`);
1240
+ await fs.writeFile(agentPath, agentContent, 'utf-8');
1241
+ agentFiles.push(agentPath);
1242
+ }
1243
+
1244
+ // 6. Generate custom tasks from blueprint
1245
+ const taskFiles = [];
1246
+ for (const task of blueprint.recommendations.tasks || []) {
1247
+ const taskContent = this.generateTaskFromBlueprint(task, config.name);
1248
+ const taskPath = path.join(squadPath, 'tasks', `${task.name}.md`);
1249
+ await fs.writeFile(taskPath, taskContent, 'utf-8');
1250
+ taskFiles.push(taskPath);
1251
+ }
1252
+
1253
+ // 7. Update squad.yaml with actual components
1254
+ const squadYamlPath = path.join(squadPath, 'squad.yaml');
1255
+ await this.updateSquadYamlComponents(squadYamlPath, blueprint);
1256
+
1257
+ // 8. Return result with blueprint info
1258
+ return {
1259
+ ...result,
1260
+ files: [...result.files, ...agentFiles, ...taskFiles],
1261
+ blueprint: {
1262
+ path: blueprintPath,
1263
+ agents: blueprint.recommendations.agents?.length || 0,
1264
+ tasks: blueprint.recommendations.tasks?.length || 0,
1265
+ confidence: blueprint.metadata.overall_confidence || 0,
1266
+ source_docs: blueprint.metadata.source_docs || [],
1267
+ },
1268
+ };
1269
+ }
1270
+
1271
+ /**
1272
+ * Update squad.yaml with actual components from blueprint
1273
+ * @param {string} squadYamlPath - Path to squad.yaml
1274
+ * @param {Object} blueprint - Blueprint object
1275
+ */
1276
+ async updateSquadYamlComponents(squadYamlPath, blueprint) {
1277
+ const content = await fs.readFile(squadYamlPath, 'utf-8');
1278
+ const squadManifest = yaml.load(content);
1279
+
1280
+ // Update components
1281
+ squadManifest.components = squadManifest.components || {};
1282
+ squadManifest.components.agents = (blueprint.recommendations.agents || [])
1283
+ .map(a => `${a.id}.md`);
1284
+ squadManifest.components.tasks = (blueprint.recommendations.tasks || [])
1285
+ .map(t => `${t.name}.md`);
1286
+
1287
+ // Add blueprint reference
1288
+ squadManifest.blueprint = {
1289
+ source: blueprint.metadata.source_docs || [],
1290
+ created_at: blueprint.metadata.created_at,
1291
+ confidence: blueprint.metadata.overall_confidence || 0,
1292
+ };
1293
+
1294
+ // Write updated manifest
1295
+ const updatedContent = yaml.dump(squadManifest, {
1296
+ indent: 2,
1297
+ lineWidth: 120,
1298
+ quotingType: '"',
1299
+ });
1300
+ await fs.writeFile(squadYamlPath, updatedContent, 'utf-8');
1301
+ }
1302
+ }
1303
+
1304
+ module.exports = {
1305
+ SquadGenerator,
1306
+ SquadGeneratorError,
1307
+ GeneratorErrorCodes,
1308
+ AVAILABLE_TEMPLATES,
1309
+ AVAILABLE_LICENSES,
1310
+ CONFIG_MODES,
1311
+ DEFAULT_SQUADS_PATH,
1312
+ DEFAULT_DESIGNS_PATH,
1313
+ DEFAULT_AIOS_MIN_VERSION,
1314
+ SQUAD_DESIGN_SCHEMA_PATH,
1315
+ isValidSquadName,
1316
+ getGitUserName,
1317
+ };