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.
- package/.aios-core/core-config.yaml +54 -0
- package/.aios-core/development/agents/squad-creator.md +261 -0
- package/.aios-core/development/scripts/squad/index.js +36 -2
- package/.aios-core/development/scripts/squad/squad-designer.js +1010 -0
- package/.aios-core/development/scripts/squad/squad-generator.js +1317 -0
- package/.aios-core/development/tasks/github-devops-github-pr-automation.md +240 -3
- package/.aios-core/development/tasks/squad-creator-create.md +289 -0
- package/.aios-core/development/tasks/squad-creator-design.md +334 -0
- package/.aios-core/development/tasks/squad-creator-download.md +65 -0
- package/.aios-core/development/tasks/squad-creator-list.md +225 -0
- package/.aios-core/development/tasks/squad-creator-publish.md +86 -0
- package/.aios-core/development/tasks/squad-creator-sync-synkra.md +83 -0
- package/.aios-core/infrastructure/templates/core-config/core-config-greenfield.tmpl.yaml +41 -0
- package/.aios-core/schemas/squad-design-schema.json +299 -0
- package/package.json +1 -1
- package/squads/.designs/duplicate-test-design.yaml +23 -0
- package/squads/.designs/force-test-design.yaml +23 -0
- package/squads/.designs/nested-test-design.yaml +23 -0
- package/squads/.designs/test-squad-design.yaml +23 -0
|
@@ -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
|
+
};
|