aios-core 3.7.0 → 3.8.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/session/context-detector.js +3 -0
- package/.aios-core/core/session/context-loader.js +154 -0
- package/.aios-core/data/learned-patterns.yaml +3 -0
- package/.aios-core/data/workflow-patterns.yaml +347 -3
- package/.aios-core/development/agents/dev.md +6 -0
- package/.aios-core/development/agents/squad-creator.md +30 -0
- package/.aios-core/development/scripts/squad/squad-analyzer.js +638 -0
- package/.aios-core/development/scripts/squad/squad-extender.js +871 -0
- package/.aios-core/development/scripts/squad/squad-generator.js +107 -19
- package/.aios-core/development/scripts/squad/squad-migrator.js +3 -5
- package/.aios-core/development/scripts/squad/squad-validator.js +98 -0
- package/.aios-core/development/tasks/next.md +294 -0
- package/.aios-core/development/tasks/patterns.md +334 -0
- package/.aios-core/development/tasks/squad-creator-analyze.md +315 -0
- package/.aios-core/development/tasks/squad-creator-create.md +26 -3
- package/.aios-core/development/tasks/squad-creator-extend.md +411 -0
- package/.aios-core/development/tasks/squad-creator-validate.md +9 -1
- package/.aios-core/development/tasks/waves.md +205 -0
- package/.aios-core/development/templates/squad/agent-template.md +69 -0
- package/.aios-core/development/templates/squad/checklist-template.md +82 -0
- package/.aios-core/development/templates/squad/data-template.yaml +105 -0
- package/.aios-core/development/templates/squad/script-template.js +179 -0
- package/.aios-core/development/templates/squad/task-template.md +125 -0
- package/.aios-core/development/templates/squad/template-template.md +97 -0
- package/.aios-core/development/templates/squad/tool-template.js +103 -0
- package/.aios-core/development/templates/squad/workflow-template.yaml +108 -0
- package/.aios-core/install-manifest.yaml +89 -25
- package/.aios-core/quality/metrics-collector.js +27 -0
- package/.aios-core/scripts/session-context-loader.js +13 -254
- package/.aios-core/utils/aios-validator.js +25 -0
- package/.aios-core/workflow-intelligence/__tests__/confidence-scorer.test.js +334 -0
- package/.aios-core/workflow-intelligence/__tests__/integration.test.js +337 -0
- package/.aios-core/workflow-intelligence/__tests__/suggestion-engine.test.js +431 -0
- package/.aios-core/workflow-intelligence/__tests__/wave-analyzer.test.js +458 -0
- package/.aios-core/workflow-intelligence/__tests__/workflow-registry.test.js +302 -0
- package/.aios-core/workflow-intelligence/engine/confidence-scorer.js +305 -0
- package/.aios-core/workflow-intelligence/engine/output-formatter.js +285 -0
- package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +603 -0
- package/.aios-core/workflow-intelligence/engine/wave-analyzer.js +676 -0
- package/.aios-core/workflow-intelligence/index.js +327 -0
- package/.aios-core/workflow-intelligence/learning/capture-hook.js +147 -0
- package/.aios-core/workflow-intelligence/learning/index.js +230 -0
- package/.aios-core/workflow-intelligence/learning/pattern-capture.js +340 -0
- package/.aios-core/workflow-intelligence/learning/pattern-store.js +498 -0
- package/.aios-core/workflow-intelligence/learning/pattern-validator.js +309 -0
- package/.aios-core/workflow-intelligence/registry/workflow-registry.js +358 -0
- package/package.json +1 -1
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Squad Extender Utility
|
|
3
|
+
*
|
|
4
|
+
* Extends existing squads with new components (agents, tasks, workflows, etc.)
|
|
5
|
+
* with automatic manifest updates and validation.
|
|
6
|
+
*
|
|
7
|
+
* Used by: squad-creator agent (*extend-squad task)
|
|
8
|
+
*
|
|
9
|
+
* @module squad-extender
|
|
10
|
+
* @version 1.0.0
|
|
11
|
+
* @see Story SQS-11: Squad Analyze & Extend
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs').promises;
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const yaml = require('js-yaml');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default path for squads directory
|
|
20
|
+
* @constant {string}
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_SQUADS_PATH = './squads';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default templates directory path
|
|
26
|
+
* @constant {string}
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_TEMPLATES_PATH = './.aios-core/development/templates/squad';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Component types and their configurations
|
|
32
|
+
* @constant {Object}
|
|
33
|
+
*/
|
|
34
|
+
const COMPONENT_CONFIG = {
|
|
35
|
+
agent: {
|
|
36
|
+
directory: 'agents',
|
|
37
|
+
extension: '.md',
|
|
38
|
+
template: 'agent-template.md',
|
|
39
|
+
manifestKey: 'agents',
|
|
40
|
+
},
|
|
41
|
+
task: {
|
|
42
|
+
directory: 'tasks',
|
|
43
|
+
extension: '.md',
|
|
44
|
+
template: 'task-template.md',
|
|
45
|
+
manifestKey: 'tasks',
|
|
46
|
+
},
|
|
47
|
+
workflow: {
|
|
48
|
+
directory: 'workflows',
|
|
49
|
+
extension: '.yaml',
|
|
50
|
+
template: 'workflow-template.yaml',
|
|
51
|
+
manifestKey: 'workflows',
|
|
52
|
+
},
|
|
53
|
+
checklist: {
|
|
54
|
+
directory: 'checklists',
|
|
55
|
+
extension: '.md',
|
|
56
|
+
template: 'checklist-template.md',
|
|
57
|
+
manifestKey: 'checklists',
|
|
58
|
+
},
|
|
59
|
+
template: {
|
|
60
|
+
directory: 'templates',
|
|
61
|
+
extension: '.md',
|
|
62
|
+
template: 'template-template.md',
|
|
63
|
+
manifestKey: 'templates',
|
|
64
|
+
},
|
|
65
|
+
tool: {
|
|
66
|
+
directory: 'tools',
|
|
67
|
+
extension: '.js',
|
|
68
|
+
template: 'tool-template.js',
|
|
69
|
+
manifestKey: 'tools',
|
|
70
|
+
},
|
|
71
|
+
script: {
|
|
72
|
+
directory: 'scripts',
|
|
73
|
+
extension: '.js',
|
|
74
|
+
template: 'script-template.js',
|
|
75
|
+
manifestKey: 'scripts',
|
|
76
|
+
},
|
|
77
|
+
data: {
|
|
78
|
+
directory: 'data',
|
|
79
|
+
extension: '.yaml',
|
|
80
|
+
template: 'data-template.yaml',
|
|
81
|
+
manifestKey: 'data',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Manifest file names in order of preference
|
|
87
|
+
* @constant {string[]}
|
|
88
|
+
*/
|
|
89
|
+
const MANIFEST_FILES = ['squad.yaml', 'config.yaml'];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Error codes for SquadExtenderError
|
|
93
|
+
* @enum {string}
|
|
94
|
+
*/
|
|
95
|
+
const ErrorCodes = {
|
|
96
|
+
SQUAD_NOT_FOUND: 'SQUAD_NOT_FOUND',
|
|
97
|
+
MANIFEST_NOT_FOUND: 'MANIFEST_NOT_FOUND',
|
|
98
|
+
MANIFEST_UPDATE_FAILED: 'MANIFEST_UPDATE_FAILED',
|
|
99
|
+
COMPONENT_EXISTS: 'COMPONENT_EXISTS',
|
|
100
|
+
INVALID_COMPONENT_NAME: 'INVALID_COMPONENT_NAME',
|
|
101
|
+
INVALID_COMPONENT_TYPE: 'INVALID_COMPONENT_TYPE',
|
|
102
|
+
AGENT_NOT_FOUND: 'AGENT_NOT_FOUND',
|
|
103
|
+
TEMPLATE_NOT_FOUND: 'TEMPLATE_NOT_FOUND',
|
|
104
|
+
PATH_TRAVERSAL: 'PATH_TRAVERSAL',
|
|
105
|
+
CREATION_FAILED: 'CREATION_FAILED',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Custom error class for Squad Extender operations
|
|
110
|
+
* @extends Error
|
|
111
|
+
*/
|
|
112
|
+
class SquadExtenderError extends Error {
|
|
113
|
+
/**
|
|
114
|
+
* Create a SquadExtenderError
|
|
115
|
+
* @param {string} code - Error code from ErrorCodes enum
|
|
116
|
+
* @param {string} message - Human-readable error message
|
|
117
|
+
* @param {string} [suggestion] - Suggested fix for the error
|
|
118
|
+
*/
|
|
119
|
+
constructor(code, message, suggestion) {
|
|
120
|
+
super(message);
|
|
121
|
+
this.name = 'SquadExtenderError';
|
|
122
|
+
this.code = code;
|
|
123
|
+
this.suggestion = suggestion || '';
|
|
124
|
+
|
|
125
|
+
if (Error.captureStackTrace) {
|
|
126
|
+
Error.captureStackTrace(this, SquadExtenderError);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create error for squad not found
|
|
132
|
+
* @param {string} squadName - Name of the squad
|
|
133
|
+
* @returns {SquadExtenderError}
|
|
134
|
+
*/
|
|
135
|
+
static squadNotFound(squadName) {
|
|
136
|
+
return new SquadExtenderError(
|
|
137
|
+
ErrorCodes.SQUAD_NOT_FOUND,
|
|
138
|
+
`Squad "${squadName}" not found`,
|
|
139
|
+
`Use *list-squads to see available squads, or *create-squad ${squadName} to create it`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create error for manifest not found
|
|
145
|
+
* @param {string} squadPath - Path to squad directory
|
|
146
|
+
* @returns {SquadExtenderError}
|
|
147
|
+
*/
|
|
148
|
+
static manifestNotFound(squadPath) {
|
|
149
|
+
return new SquadExtenderError(
|
|
150
|
+
ErrorCodes.MANIFEST_NOT_FOUND,
|
|
151
|
+
`No squad.yaml or config.yaml found in ${squadPath}`,
|
|
152
|
+
'Create squad.yaml with squad metadata'
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create error for component already exists
|
|
158
|
+
* @param {string} filePath - Path to existing component
|
|
159
|
+
* @returns {SquadExtenderError}
|
|
160
|
+
*/
|
|
161
|
+
static componentExists(filePath) {
|
|
162
|
+
return new SquadExtenderError(
|
|
163
|
+
ErrorCodes.COMPONENT_EXISTS,
|
|
164
|
+
`Component already exists at ${filePath}`,
|
|
165
|
+
'Use --force to overwrite, or choose a different name'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Create error for invalid component name
|
|
171
|
+
* @param {string} name - Invalid component name
|
|
172
|
+
* @returns {SquadExtenderError}
|
|
173
|
+
*/
|
|
174
|
+
static invalidComponentName(name) {
|
|
175
|
+
return new SquadExtenderError(
|
|
176
|
+
ErrorCodes.INVALID_COMPONENT_NAME,
|
|
177
|
+
`Invalid component name: "${name}"`,
|
|
178
|
+
'Use kebab-case (lowercase letters, numbers, and hyphens only)'
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create error for invalid component type
|
|
184
|
+
* @param {string} type - Invalid component type
|
|
185
|
+
* @returns {SquadExtenderError}
|
|
186
|
+
*/
|
|
187
|
+
static invalidComponentType(type) {
|
|
188
|
+
const validTypes = Object.keys(COMPONENT_CONFIG).join(', ');
|
|
189
|
+
return new SquadExtenderError(
|
|
190
|
+
ErrorCodes.INVALID_COMPONENT_TYPE,
|
|
191
|
+
`Invalid component type: "${type}"`,
|
|
192
|
+
`Valid types are: ${validTypes}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Create error for agent not found
|
|
198
|
+
* @param {string} agentId - Agent ID not found
|
|
199
|
+
* @param {string[]} availableAgents - List of available agents
|
|
200
|
+
* @returns {SquadExtenderError}
|
|
201
|
+
*/
|
|
202
|
+
static agentNotFound(agentId, availableAgents) {
|
|
203
|
+
return new SquadExtenderError(
|
|
204
|
+
ErrorCodes.AGENT_NOT_FOUND,
|
|
205
|
+
`Agent "${agentId}" not found in squad`,
|
|
206
|
+
`Available agents: ${availableAgents.join(', ')}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create error for path traversal attempt
|
|
212
|
+
* @param {string} name - Component name with path characters
|
|
213
|
+
* @returns {SquadExtenderError}
|
|
214
|
+
*/
|
|
215
|
+
static pathTraversal(name) {
|
|
216
|
+
return new SquadExtenderError(
|
|
217
|
+
ErrorCodes.PATH_TRAVERSAL,
|
|
218
|
+
`Invalid component name - path traversal not allowed: "${name}"`,
|
|
219
|
+
'Component names cannot contain path separators or ".."'
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Squad Extender class for adding new components to squads
|
|
226
|
+
*/
|
|
227
|
+
class SquadExtender {
|
|
228
|
+
/**
|
|
229
|
+
* Create a SquadExtender instance
|
|
230
|
+
* @param {Object} [options={}] - Configuration options
|
|
231
|
+
* @param {string} [options.squadsPath] - Custom squads directory path
|
|
232
|
+
* @param {string} [options.templatesPath] - Custom templates directory path
|
|
233
|
+
* @param {boolean} [options.verbose=false] - Enable verbose output
|
|
234
|
+
*/
|
|
235
|
+
constructor(options = {}) {
|
|
236
|
+
this.squadsPath = options.squadsPath || DEFAULT_SQUADS_PATH;
|
|
237
|
+
this.templatesPath = options.templatesPath || DEFAULT_TEMPLATES_PATH;
|
|
238
|
+
this.verbose = options.verbose || false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Add a new component to a squad
|
|
243
|
+
* @param {string} squadName - Name of the squad
|
|
244
|
+
* @param {Object} componentInfo - Component configuration
|
|
245
|
+
* @param {string} componentInfo.type - Component type (agent, task, workflow, etc.)
|
|
246
|
+
* @param {string} componentInfo.name - Component name (kebab-case)
|
|
247
|
+
* @param {string} [componentInfo.agentId] - Agent ID (required for tasks)
|
|
248
|
+
* @param {string} [componentInfo.description] - Component description
|
|
249
|
+
* @param {string} [componentInfo.storyId] - Related story ID for traceability
|
|
250
|
+
* @param {Object} [options={}] - Add options
|
|
251
|
+
* @param {boolean} [options.force=false] - Overwrite existing component
|
|
252
|
+
* @returns {Promise<Object>} Result object with file path and status
|
|
253
|
+
*/
|
|
254
|
+
async addComponent(squadName, componentInfo, options = {}) {
|
|
255
|
+
const { type, name, agentId, description, storyId } = componentInfo;
|
|
256
|
+
const { force = false } = options;
|
|
257
|
+
|
|
258
|
+
// Validate inputs
|
|
259
|
+
this._validateComponentType(type);
|
|
260
|
+
this._validateComponentName(name);
|
|
261
|
+
|
|
262
|
+
const squadPath = path.join(this.squadsPath, squadName);
|
|
263
|
+
|
|
264
|
+
// Check squad exists
|
|
265
|
+
const exists = await this._directoryExists(squadPath);
|
|
266
|
+
if (!exists) {
|
|
267
|
+
throw SquadExtenderError.squadNotFound(squadName);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// For tasks, validate agent exists
|
|
271
|
+
if (type === 'task' && agentId) {
|
|
272
|
+
await this._validateAgentExists(squadPath, agentId);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Get component config
|
|
276
|
+
const config = COMPONENT_CONFIG[type];
|
|
277
|
+
|
|
278
|
+
// Build file name
|
|
279
|
+
const fileName = this._buildFileName(type, name, agentId, config.extension);
|
|
280
|
+
|
|
281
|
+
// Build target path
|
|
282
|
+
const targetDir = path.join(squadPath, config.directory);
|
|
283
|
+
const targetPath = path.join(targetDir, fileName);
|
|
284
|
+
const relativePath = path.join(config.directory, fileName);
|
|
285
|
+
|
|
286
|
+
// Check if file already exists
|
|
287
|
+
if (await this._fileExists(targetPath)) {
|
|
288
|
+
if (!force) {
|
|
289
|
+
throw SquadExtenderError.componentExists(relativePath);
|
|
290
|
+
}
|
|
291
|
+
// Create backup before overwriting
|
|
292
|
+
await this._createBackup(targetPath);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Ensure target directory exists
|
|
296
|
+
await this._ensureDirectory(targetDir);
|
|
297
|
+
|
|
298
|
+
// Load and render template
|
|
299
|
+
const content = await this._renderTemplate(type, {
|
|
300
|
+
componentName: name,
|
|
301
|
+
agentId,
|
|
302
|
+
description: description || `${type} component: ${name}`,
|
|
303
|
+
storyId: storyId || '',
|
|
304
|
+
squadName,
|
|
305
|
+
createdAt: new Date().toISOString().split('T')[0],
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Write component file
|
|
309
|
+
await fs.writeFile(targetPath, content, 'utf8');
|
|
310
|
+
|
|
311
|
+
// Update manifest
|
|
312
|
+
const manifestUpdated = await this.updateManifest(squadPath, {
|
|
313
|
+
type,
|
|
314
|
+
file: fileName,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
success: true,
|
|
319
|
+
filePath: targetPath,
|
|
320
|
+
relativePath,
|
|
321
|
+
fileName,
|
|
322
|
+
type,
|
|
323
|
+
templateUsed: config.template,
|
|
324
|
+
manifestUpdated,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Update squad manifest with new component
|
|
330
|
+
* @param {string} squadPath - Path to squad directory
|
|
331
|
+
* @param {Object} componentInfo - Component information
|
|
332
|
+
* @param {string} componentInfo.type - Component type
|
|
333
|
+
* @param {string} componentInfo.file - Component file name
|
|
334
|
+
* @returns {Promise<boolean>} True if manifest was updated
|
|
335
|
+
*/
|
|
336
|
+
async updateManifest(squadPath, componentInfo) {
|
|
337
|
+
const { type, file } = componentInfo;
|
|
338
|
+
const config = COMPONENT_CONFIG[type];
|
|
339
|
+
const manifestKey = config.manifestKey;
|
|
340
|
+
|
|
341
|
+
// Find manifest file
|
|
342
|
+
const { manifestPath, manifest } = await this._loadManifest(squadPath);
|
|
343
|
+
|
|
344
|
+
// Create backup
|
|
345
|
+
await this._createBackup(manifestPath);
|
|
346
|
+
|
|
347
|
+
// Ensure components section exists
|
|
348
|
+
if (!manifest.components) {
|
|
349
|
+
manifest.components = {};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Ensure component type array exists
|
|
353
|
+
if (!manifest.components[manifestKey]) {
|
|
354
|
+
manifest.components[manifestKey] = [];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Add file if not already present
|
|
358
|
+
if (!manifest.components[manifestKey].includes(file)) {
|
|
359
|
+
manifest.components[manifestKey].push(file);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Write updated manifest
|
|
363
|
+
const yamlContent = yaml.dump(manifest, {
|
|
364
|
+
indent: 2,
|
|
365
|
+
lineWidth: -1,
|
|
366
|
+
noRefs: true,
|
|
367
|
+
sortKeys: false,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
await fs.writeFile(manifestPath, yamlContent, 'utf8');
|
|
371
|
+
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* List agents in a squad
|
|
377
|
+
* @param {string} squadPath - Path to squad directory
|
|
378
|
+
* @returns {Promise<string[]>} List of agent IDs
|
|
379
|
+
*/
|
|
380
|
+
async listAgents(squadPath) {
|
|
381
|
+
const agentsDir = path.join(squadPath, 'agents');
|
|
382
|
+
try {
|
|
383
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
384
|
+
return entries
|
|
385
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
386
|
+
.map((entry) => entry.name.replace('.md', ''));
|
|
387
|
+
} catch {
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ============================================
|
|
393
|
+
// Private Helper Methods
|
|
394
|
+
// ============================================
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Validate component type
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
_validateComponentType(type) {
|
|
401
|
+
if (!COMPONENT_CONFIG[type]) {
|
|
402
|
+
throw SquadExtenderError.invalidComponentType(type);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Validate component name (kebab-case)
|
|
408
|
+
* @private
|
|
409
|
+
*/
|
|
410
|
+
_validateComponentName(name) {
|
|
411
|
+
// Check for path traversal
|
|
412
|
+
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
|
|
413
|
+
throw SquadExtenderError.pathTraversal(name);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Check kebab-case format
|
|
417
|
+
const kebabCasePattern = /^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$/;
|
|
418
|
+
if (!kebabCasePattern.test(name)) {
|
|
419
|
+
throw SquadExtenderError.invalidComponentName(name);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Validate agent exists in squad
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
async _validateAgentExists(squadPath, agentId) {
|
|
428
|
+
const agents = await this.listAgents(squadPath);
|
|
429
|
+
if (!agents.includes(agentId)) {
|
|
430
|
+
throw SquadExtenderError.agentNotFound(agentId, agents);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Build file name for component
|
|
436
|
+
* @private
|
|
437
|
+
*/
|
|
438
|
+
_buildFileName(type, name, agentId, extension) {
|
|
439
|
+
// For tasks, prepend agent ID
|
|
440
|
+
if (type === 'task' && agentId) {
|
|
441
|
+
return `${agentId}-${name}${extension}`;
|
|
442
|
+
}
|
|
443
|
+
return `${name}${extension}`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Check if directory exists
|
|
448
|
+
* @private
|
|
449
|
+
*/
|
|
450
|
+
async _directoryExists(dirPath) {
|
|
451
|
+
try {
|
|
452
|
+
const stats = await fs.stat(dirPath);
|
|
453
|
+
return stats.isDirectory();
|
|
454
|
+
} catch {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Check if file exists
|
|
461
|
+
* @private
|
|
462
|
+
*/
|
|
463
|
+
async _fileExists(filePath) {
|
|
464
|
+
try {
|
|
465
|
+
await fs.access(filePath);
|
|
466
|
+
return true;
|
|
467
|
+
} catch {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Ensure directory exists, create if not
|
|
474
|
+
* @private
|
|
475
|
+
*/
|
|
476
|
+
async _ensureDirectory(dirPath) {
|
|
477
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Create backup of file
|
|
482
|
+
* @private
|
|
483
|
+
*/
|
|
484
|
+
async _createBackup(filePath) {
|
|
485
|
+
try {
|
|
486
|
+
const backupPath = `${filePath}.bak`;
|
|
487
|
+
await fs.copyFile(filePath, backupPath);
|
|
488
|
+
if (this.verbose) {
|
|
489
|
+
console.log(`Backup created: ${backupPath}`);
|
|
490
|
+
}
|
|
491
|
+
} catch {
|
|
492
|
+
// File might not exist yet, that's ok
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Load squad manifest
|
|
498
|
+
* @private
|
|
499
|
+
*/
|
|
500
|
+
async _loadManifest(squadPath) {
|
|
501
|
+
for (const manifestFile of MANIFEST_FILES) {
|
|
502
|
+
const manifestPath = path.join(squadPath, manifestFile);
|
|
503
|
+
try {
|
|
504
|
+
const content = await fs.readFile(manifestPath, 'utf8');
|
|
505
|
+
const manifest = yaml.load(content);
|
|
506
|
+
return { manifestPath, manifest };
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (error.code !== 'ENOENT') {
|
|
509
|
+
throw new SquadExtenderError(
|
|
510
|
+
ErrorCodes.MANIFEST_UPDATE_FAILED,
|
|
511
|
+
`Failed to parse ${manifestFile}: ${error.message}`,
|
|
512
|
+
'Check YAML syntax - use a YAML linter'
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
throw SquadExtenderError.manifestNotFound(squadPath);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Load and render template
|
|
523
|
+
* @private
|
|
524
|
+
*/
|
|
525
|
+
async _renderTemplate(type, context) {
|
|
526
|
+
const config = COMPONENT_CONFIG[type];
|
|
527
|
+
const templatePath = path.join(this.templatesPath, config.template);
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
// Try to load template from templates path
|
|
531
|
+
const template = await fs.readFile(templatePath, 'utf8');
|
|
532
|
+
return this._interpolateTemplate(template, context);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (error.code === 'ENOENT') {
|
|
535
|
+
// Template not found, use default template
|
|
536
|
+
return this._getDefaultTemplate(type, context);
|
|
537
|
+
}
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Interpolate template variables
|
|
544
|
+
* @private
|
|
545
|
+
*/
|
|
546
|
+
_interpolateTemplate(template, context) {
|
|
547
|
+
let result = template;
|
|
548
|
+
for (const [key, value] of Object.entries(context)) {
|
|
549
|
+
const placeholder = new RegExp(`\\{\\{${key.toUpperCase()}\\}\\}`, 'g');
|
|
550
|
+
result = result.replace(placeholder, value || '');
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Get default template for component type
|
|
557
|
+
* @private
|
|
558
|
+
*/
|
|
559
|
+
_getDefaultTemplate(type, context) {
|
|
560
|
+
const templates = {
|
|
561
|
+
agent: `# ${context.componentName}
|
|
562
|
+
|
|
563
|
+
> Agent definition for ${context.squadName}
|
|
564
|
+
> Created: ${context.createdAt}
|
|
565
|
+
${context.storyId ? `> Story: ${context.storyId}` : ''}
|
|
566
|
+
|
|
567
|
+
## Description
|
|
568
|
+
|
|
569
|
+
${context.description}
|
|
570
|
+
|
|
571
|
+
## Configuration
|
|
572
|
+
|
|
573
|
+
\`\`\`yaml
|
|
574
|
+
agent:
|
|
575
|
+
name: ${context.componentName}
|
|
576
|
+
id: ${context.componentName}
|
|
577
|
+
title: "Agent Title"
|
|
578
|
+
icon: "🤖"
|
|
579
|
+
|
|
580
|
+
persona:
|
|
581
|
+
role: "Describe the agent's role"
|
|
582
|
+
style: "Communication style"
|
|
583
|
+
|
|
584
|
+
commands:
|
|
585
|
+
- help: "Show available commands"
|
|
586
|
+
- exit: "Exit agent mode"
|
|
587
|
+
|
|
588
|
+
dependencies:
|
|
589
|
+
tasks: []
|
|
590
|
+
templates: []
|
|
591
|
+
\`\`\`
|
|
592
|
+
`,
|
|
593
|
+
|
|
594
|
+
task: `---
|
|
595
|
+
task: ${context.componentName}
|
|
596
|
+
responsavel: "@${context.agentId || 'agent'}"
|
|
597
|
+
responsavel_type: Agent
|
|
598
|
+
atomic_layer: Task
|
|
599
|
+
elicit: false
|
|
600
|
+
|
|
601
|
+
Entrada:
|
|
602
|
+
- campo: input_param
|
|
603
|
+
tipo: string
|
|
604
|
+
origem: User Input
|
|
605
|
+
obrigatorio: true
|
|
606
|
+
validacao: "Description of validation"
|
|
607
|
+
|
|
608
|
+
Saida:
|
|
609
|
+
- campo: output_result
|
|
610
|
+
tipo: object
|
|
611
|
+
destino: Return value
|
|
612
|
+
persistido: false
|
|
613
|
+
|
|
614
|
+
Checklist:
|
|
615
|
+
- "[ ] Step 1"
|
|
616
|
+
- "[ ] Step 2"
|
|
617
|
+
- "[ ] Step 3"
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
# ${context.componentName}
|
|
621
|
+
|
|
622
|
+
## Description
|
|
623
|
+
|
|
624
|
+
${context.description}
|
|
625
|
+
|
|
626
|
+
${context.storyId ? `## Story Reference\n\n- **Story:** ${context.storyId}\n` : ''}
|
|
627
|
+
|
|
628
|
+
## Execution Steps
|
|
629
|
+
|
|
630
|
+
### Step 1: Initialize
|
|
631
|
+
|
|
632
|
+
\`\`\`javascript
|
|
633
|
+
// Implementation here
|
|
634
|
+
\`\`\`
|
|
635
|
+
|
|
636
|
+
### Step 2: Process
|
|
637
|
+
|
|
638
|
+
\`\`\`javascript
|
|
639
|
+
// Implementation here
|
|
640
|
+
\`\`\`
|
|
641
|
+
|
|
642
|
+
### Step 3: Complete
|
|
643
|
+
|
|
644
|
+
\`\`\`javascript
|
|
645
|
+
// Implementation here
|
|
646
|
+
\`\`\`
|
|
647
|
+
|
|
648
|
+
## Error Handling
|
|
649
|
+
|
|
650
|
+
\`\`\`yaml
|
|
651
|
+
error: ERROR_CODE
|
|
652
|
+
cause: Description of cause
|
|
653
|
+
resolution: How to resolve
|
|
654
|
+
\`\`\`
|
|
655
|
+
|
|
656
|
+
## Metadata
|
|
657
|
+
|
|
658
|
+
\`\`\`yaml
|
|
659
|
+
${context.storyId ? `story: ${context.storyId}` : 'story: N/A'}
|
|
660
|
+
version: 1.0.0
|
|
661
|
+
created: ${context.createdAt}
|
|
662
|
+
author: squad-creator
|
|
663
|
+
\`\`\`
|
|
664
|
+
`,
|
|
665
|
+
|
|
666
|
+
workflow: `# ${context.componentName} Workflow
|
|
667
|
+
|
|
668
|
+
name: ${context.componentName}
|
|
669
|
+
description: ${context.description}
|
|
670
|
+
version: 1.0.0
|
|
671
|
+
${context.storyId ? `story: ${context.storyId}` : ''}
|
|
672
|
+
created: ${context.createdAt}
|
|
673
|
+
|
|
674
|
+
# Trigger conditions
|
|
675
|
+
triggers:
|
|
676
|
+
- manual
|
|
677
|
+
|
|
678
|
+
# Workflow steps
|
|
679
|
+
steps:
|
|
680
|
+
- id: step-1
|
|
681
|
+
name: "Step 1"
|
|
682
|
+
description: "Description of step 1"
|
|
683
|
+
action: task
|
|
684
|
+
task: task-name
|
|
685
|
+
|
|
686
|
+
- id: step-2
|
|
687
|
+
name: "Step 2"
|
|
688
|
+
description: "Description of step 2"
|
|
689
|
+
action: task
|
|
690
|
+
task: task-name
|
|
691
|
+
depends_on:
|
|
692
|
+
- step-1
|
|
693
|
+
|
|
694
|
+
# Completion criteria
|
|
695
|
+
completion:
|
|
696
|
+
success_message: "Workflow completed successfully"
|
|
697
|
+
failure_message: "Workflow failed"
|
|
698
|
+
`,
|
|
699
|
+
|
|
700
|
+
checklist: `# ${context.componentName} Checklist
|
|
701
|
+
|
|
702
|
+
> ${context.description}
|
|
703
|
+
> Created: ${context.createdAt}
|
|
704
|
+
${context.storyId ? `> Story: ${context.storyId}` : ''}
|
|
705
|
+
|
|
706
|
+
## Pre-Conditions
|
|
707
|
+
|
|
708
|
+
- [ ] Pre-condition 1
|
|
709
|
+
- [ ] Pre-condition 2
|
|
710
|
+
|
|
711
|
+
## Validation Items
|
|
712
|
+
|
|
713
|
+
### Category 1
|
|
714
|
+
|
|
715
|
+
- [ ] Item 1
|
|
716
|
+
- [ ] Item 2
|
|
717
|
+
- [ ] Item 3
|
|
718
|
+
|
|
719
|
+
### Category 2
|
|
720
|
+
|
|
721
|
+
- [ ] Item 4
|
|
722
|
+
- [ ] Item 5
|
|
723
|
+
|
|
724
|
+
## Post-Conditions
|
|
725
|
+
|
|
726
|
+
- [ ] Post-condition 1
|
|
727
|
+
- [ ] Post-condition 2
|
|
728
|
+
`,
|
|
729
|
+
|
|
730
|
+
template: `# ${context.componentName} Template
|
|
731
|
+
|
|
732
|
+
> ${context.description}
|
|
733
|
+
> Created: ${context.createdAt}
|
|
734
|
+
${context.storyId ? `> Story: ${context.storyId}` : ''}
|
|
735
|
+
|
|
736
|
+
## Template Content
|
|
737
|
+
|
|
738
|
+
Replace placeholders with actual values:
|
|
739
|
+
|
|
740
|
+
- {{PLACEHOLDER_1}}: Description
|
|
741
|
+
- {{PLACEHOLDER_2}}: Description
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## Content
|
|
746
|
+
|
|
747
|
+
{{PLACEHOLDER_1}}
|
|
748
|
+
|
|
749
|
+
### Section 1
|
|
750
|
+
|
|
751
|
+
{{PLACEHOLDER_2}}
|
|
752
|
+
|
|
753
|
+
---
|
|
754
|
+
|
|
755
|
+
*Template generated by squad-creator*
|
|
756
|
+
`,
|
|
757
|
+
|
|
758
|
+
tool: `/**
|
|
759
|
+
* ${context.componentName} Tool
|
|
760
|
+
*
|
|
761
|
+
* ${context.description}
|
|
762
|
+
*
|
|
763
|
+
* @module ${context.componentName}
|
|
764
|
+
* @version 1.0.0
|
|
765
|
+
${context.storyId ? `* @see ${context.storyId}` : ''}
|
|
766
|
+
*/
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Main function for ${context.componentName}
|
|
770
|
+
* @param {Object} input - Input parameters
|
|
771
|
+
* @returns {Object} Result
|
|
772
|
+
*/
|
|
773
|
+
function ${this._toCamelCase(context.componentName)}(input) {
|
|
774
|
+
// Implementation here
|
|
775
|
+
return {
|
|
776
|
+
success: true,
|
|
777
|
+
data: {},
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
module.exports = {
|
|
782
|
+
${this._toCamelCase(context.componentName)},
|
|
783
|
+
};
|
|
784
|
+
`,
|
|
785
|
+
|
|
786
|
+
script: `#!/usr/bin/env node
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* ${context.componentName} Script
|
|
790
|
+
*
|
|
791
|
+
* ${context.description}
|
|
792
|
+
*
|
|
793
|
+
* @module ${context.componentName}
|
|
794
|
+
* @version 1.0.0
|
|
795
|
+
${context.storyId ? `* @see ${context.storyId}` : ''}
|
|
796
|
+
*/
|
|
797
|
+
|
|
798
|
+
const fs = require('fs').promises;
|
|
799
|
+
const path = require('path');
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Main entry point
|
|
803
|
+
* @param {string[]} args - Command line arguments
|
|
804
|
+
*/
|
|
805
|
+
async function main(args) {
|
|
806
|
+
console.log('${context.componentName} script started');
|
|
807
|
+
|
|
808
|
+
// Implementation here
|
|
809
|
+
|
|
810
|
+
console.log('${context.componentName} script completed');
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Run if called directly
|
|
814
|
+
if (require.main === module) {
|
|
815
|
+
main(process.argv.slice(2))
|
|
816
|
+
.catch((error) => {
|
|
817
|
+
console.error('Error:', error.message);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
module.exports = { main };
|
|
823
|
+
`,
|
|
824
|
+
|
|
825
|
+
data: `# ${context.componentName} Data
|
|
826
|
+
|
|
827
|
+
name: ${context.componentName}
|
|
828
|
+
description: ${context.description}
|
|
829
|
+
version: 1.0.0
|
|
830
|
+
${context.storyId ? `story: ${context.storyId}` : ''}
|
|
831
|
+
created: ${context.createdAt}
|
|
832
|
+
|
|
833
|
+
# Data schema
|
|
834
|
+
schema:
|
|
835
|
+
type: object
|
|
836
|
+
properties:
|
|
837
|
+
field1:
|
|
838
|
+
type: string
|
|
839
|
+
description: "Field description"
|
|
840
|
+
field2:
|
|
841
|
+
type: number
|
|
842
|
+
description: "Field description"
|
|
843
|
+
|
|
844
|
+
# Default values
|
|
845
|
+
defaults:
|
|
846
|
+
field1: "default value"
|
|
847
|
+
field2: 0
|
|
848
|
+
|
|
849
|
+
# Data entries
|
|
850
|
+
entries: []
|
|
851
|
+
`,
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
return templates[type] || `# ${context.componentName}\n\n${context.description}`;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Convert kebab-case to camelCase
|
|
859
|
+
* @private
|
|
860
|
+
*/
|
|
861
|
+
_toCamelCase(str) {
|
|
862
|
+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
module.exports = {
|
|
867
|
+
SquadExtender,
|
|
868
|
+
SquadExtenderError,
|
|
869
|
+
ErrorCodes,
|
|
870
|
+
COMPONENT_CONFIG,
|
|
871
|
+
};
|