aios-core 4.2.13 → 4.2.15
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/code-intel/helpers/dev-helper.js +206 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/data/entity-registry.yaml +27 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +65 -61
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +3 -3
- package/pro/license/degradation.js +220 -220
- package/pro/license/errors.js +450 -450
- package/pro/license/feature-gate.js +354 -354
- package/pro/license/index.js +181 -181
- package/pro/license/license-cache.js +523 -523
- package/pro/license/license-crypto.js +303 -303
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -191
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
|
@@ -1,638 +1,638 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dependency Analyzer for AIOS-FULLSTACK
|
|
3
|
-
* Analyzes and resolves dependencies between components
|
|
4
|
-
* @module dependency-analyzer
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs-extra');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const _yaml = require('js-yaml');
|
|
10
|
-
const chalk = require('chalk');
|
|
11
|
-
|
|
12
|
-
class DependencyAnalyzer {
|
|
13
|
-
constructor(options = {}) {
|
|
14
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
15
|
-
this.manifestPath = path.join(this.rootPath, 'aios-core', 'team-manifest.yaml');
|
|
16
|
-
|
|
17
|
-
// Component paths
|
|
18
|
-
this.paths = {
|
|
19
|
-
agents: path.join(this.rootPath, 'aios-core', 'agents'),
|
|
20
|
-
tasks: path.join(this.rootPath, 'aios-core', 'tasks'),
|
|
21
|
-
workflows: path.join(this.rootPath, 'aios-core', 'workflows')
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Dependency cache
|
|
25
|
-
this.dependencyCache = new Map();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Analyze dependencies for a component
|
|
30
|
-
* @param {string} componentType - Type of component (agent/task/workflow)
|
|
31
|
-
* @param {Object} componentData - Component configuration data
|
|
32
|
-
* @returns {Promise<Object>} Dependency analysis result
|
|
33
|
-
*/
|
|
34
|
-
async analyzeDependencies(componentType, componentData) {
|
|
35
|
-
const dependencies = {
|
|
36
|
-
required: [],
|
|
37
|
-
optional: [],
|
|
38
|
-
missing: [],
|
|
39
|
-
circular: false,
|
|
40
|
-
graph: new Map()
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
switch (componentType) {
|
|
44
|
-
case 'agent':
|
|
45
|
-
await this.analyzeAgentDependencies(componentData, dependencies);
|
|
46
|
-
break;
|
|
47
|
-
case 'task':
|
|
48
|
-
await this.analyzeTaskDependencies(componentData, dependencies);
|
|
49
|
-
break;
|
|
50
|
-
case 'workflow':
|
|
51
|
-
await this.analyzeWorkflowDependencies(componentData, dependencies);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Check for circular dependencies
|
|
56
|
-
dependencies.circular = this.detectCircularDependencies(dependencies.graph);
|
|
57
|
-
|
|
58
|
-
return dependencies;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Analyze agent dependencies
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
async analyzeAgentDependencies(agentData, dependencies) {
|
|
66
|
-
// Check for task dependencies from commands
|
|
67
|
-
if (agentData.commands && Array.isArray(agentData.commands)) {
|
|
68
|
-
for (const command of agentData.commands) {
|
|
69
|
-
const taskId = this.commandToTaskId(command);
|
|
70
|
-
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
71
|
-
|
|
72
|
-
if (await fs.pathExists(taskPath)) {
|
|
73
|
-
dependencies.required.push({
|
|
74
|
-
type: 'task',
|
|
75
|
-
id: taskId,
|
|
76
|
-
path: taskPath,
|
|
77
|
-
reason: `Command '${command}' requires task`
|
|
78
|
-
});
|
|
79
|
-
} else {
|
|
80
|
-
dependencies.missing.push({
|
|
81
|
-
type: 'task',
|
|
82
|
-
id: taskId,
|
|
83
|
-
reason: `Command '${command}' requires task file`
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Check for workflow dependencies
|
|
90
|
-
if (agentData.workflows && Array.isArray(agentData.workflows)) {
|
|
91
|
-
for (const workflowId of agentData.workflows) {
|
|
92
|
-
const workflowPath = path.join(this.paths.workflows, `${workflowId}.yaml`);
|
|
93
|
-
|
|
94
|
-
if (await fs.pathExists(workflowPath)) {
|
|
95
|
-
dependencies.optional.push({
|
|
96
|
-
type: 'workflow',
|
|
97
|
-
id: workflowId,
|
|
98
|
-
path: workflowPath,
|
|
99
|
-
reason: 'Agent workflow reference'
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Check for agent dependencies
|
|
106
|
-
if (agentData.dependencies?.agents) {
|
|
107
|
-
for (const agentId of agentData.dependencies.agents) {
|
|
108
|
-
const agentPath = path.join(this.paths.agents, `${agentId}.md`);
|
|
109
|
-
|
|
110
|
-
if (await fs.pathExists(agentPath)) {
|
|
111
|
-
dependencies.required.push({
|
|
112
|
-
type: 'agent',
|
|
113
|
-
id: agentId,
|
|
114
|
-
path: agentPath,
|
|
115
|
-
reason: 'Explicit agent dependency'
|
|
116
|
-
});
|
|
117
|
-
} else {
|
|
118
|
-
dependencies.missing.push({
|
|
119
|
-
type: 'agent',
|
|
120
|
-
id: agentId,
|
|
121
|
-
reason: 'Required agent not found'
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Analyze task dependencies
|
|
130
|
-
* @private
|
|
131
|
-
*/
|
|
132
|
-
async analyzeTaskDependencies(taskData, dependencies) {
|
|
133
|
-
// Check for agent dependency
|
|
134
|
-
if (taskData.agentName) {
|
|
135
|
-
const agentPath = path.join(this.paths.agents, `${taskData.agentName}.md`);
|
|
136
|
-
|
|
137
|
-
if (await fs.pathExists(agentPath)) {
|
|
138
|
-
dependencies.required.push({
|
|
139
|
-
type: 'agent',
|
|
140
|
-
id: taskData.agentName,
|
|
141
|
-
path: agentPath,
|
|
142
|
-
reason: 'Task belongs to agent'
|
|
143
|
-
});
|
|
144
|
-
} else {
|
|
145
|
-
dependencies.missing.push({
|
|
146
|
-
type: 'agent',
|
|
147
|
-
id: taskData.agentName,
|
|
148
|
-
reason: 'Agent not found for task'
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Check for other task dependencies
|
|
154
|
-
if (taskData.dependencies?.tasks) {
|
|
155
|
-
for (const taskId of taskData.dependencies.tasks) {
|
|
156
|
-
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
157
|
-
|
|
158
|
-
if (await fs.pathExists(taskPath)) {
|
|
159
|
-
dependencies.required.push({
|
|
160
|
-
type: 'task',
|
|
161
|
-
id: taskId,
|
|
162
|
-
path: taskPath,
|
|
163
|
-
reason: 'Task dependency'
|
|
164
|
-
});
|
|
165
|
-
} else {
|
|
166
|
-
dependencies.missing.push({
|
|
167
|
-
type: 'task',
|
|
168
|
-
id: taskId,
|
|
169
|
-
reason: 'Required task not found'
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Analyze workflow dependencies
|
|
178
|
-
* @private
|
|
179
|
-
*/
|
|
180
|
-
async analyzeWorkflowDependencies(workflowData, dependencies) {
|
|
181
|
-
// Extract task references from workflow steps
|
|
182
|
-
const taskIds = new Set();
|
|
183
|
-
|
|
184
|
-
if (workflowData.steps && Array.isArray(workflowData.steps)) {
|
|
185
|
-
for (const step of workflowData.steps) {
|
|
186
|
-
if (step.type === 'task' && step.taskId) {
|
|
187
|
-
taskIds.add(step.taskId);
|
|
188
|
-
} else if (step.action?.includes('task:')) {
|
|
189
|
-
const taskMatch = step.action.match(/task:([a-z0-9-]+)/);
|
|
190
|
-
if (taskMatch) {
|
|
191
|
-
taskIds.add(taskMatch[1]);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Check each task dependency
|
|
198
|
-
for (const taskId of taskIds) {
|
|
199
|
-
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
200
|
-
|
|
201
|
-
if (await fs.pathExists(taskPath)) {
|
|
202
|
-
dependencies.required.push({
|
|
203
|
-
type: 'task',
|
|
204
|
-
id: taskId,
|
|
205
|
-
path: taskPath,
|
|
206
|
-
reason: 'Workflow step requires task'
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Also check the task's agent dependency
|
|
210
|
-
const taskContent = await fs.readFile(taskPath, 'utf8');
|
|
211
|
-
const agentMatch = taskContent.match(/\*\*Agent:\*\*\s*([a-z0-9-]+)/);
|
|
212
|
-
if (agentMatch) {
|
|
213
|
-
const agentId = agentMatch[1];
|
|
214
|
-
const agentPath = path.join(this.paths.agents, `${agentId}.md`);
|
|
215
|
-
|
|
216
|
-
if (await fs.pathExists(agentPath)) {
|
|
217
|
-
dependencies.required.push({
|
|
218
|
-
type: 'agent',
|
|
219
|
-
id: agentId,
|
|
220
|
-
path: agentPath,
|
|
221
|
-
reason: 'Task requires agent'
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} else {
|
|
226
|
-
dependencies.missing.push({
|
|
227
|
-
type: 'task',
|
|
228
|
-
id: taskId,
|
|
229
|
-
reason: 'Workflow step requires task'
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Check for sub-workflow dependencies
|
|
235
|
-
if (workflowData.dependencies?.workflows) {
|
|
236
|
-
for (const workflowId of workflowData.dependencies.workflows) {
|
|
237
|
-
const workflowPath = path.join(this.paths.workflows, `${workflowId}.yaml`);
|
|
238
|
-
|
|
239
|
-
if (await fs.pathExists(workflowPath)) {
|
|
240
|
-
dependencies.required.push({
|
|
241
|
-
type: 'workflow',
|
|
242
|
-
id: workflowId,
|
|
243
|
-
path: workflowPath,
|
|
244
|
-
reason: 'Sub-workflow dependency'
|
|
245
|
-
});
|
|
246
|
-
} else {
|
|
247
|
-
dependencies.missing.push({
|
|
248
|
-
type: 'workflow',
|
|
249
|
-
id: workflowId,
|
|
250
|
-
reason: 'Required workflow not found'
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Convert command name to task ID
|
|
259
|
-
* @private
|
|
260
|
-
*/
|
|
261
|
-
commandToTaskId(command) {
|
|
262
|
-
// Remove asterisk if present
|
|
263
|
-
const cleanCommand = command.replace(/^\*/, '');
|
|
264
|
-
|
|
265
|
-
// Handle common patterns
|
|
266
|
-
if (cleanCommand.startsWith('create-')) {
|
|
267
|
-
return cleanCommand;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Convert to task ID format
|
|
271
|
-
return cleanCommand.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Detect circular dependencies
|
|
276
|
-
* @private
|
|
277
|
-
*/
|
|
278
|
-
detectCircularDependencies(graph) {
|
|
279
|
-
const visited = new Set();
|
|
280
|
-
const recursionStack = new Set();
|
|
281
|
-
|
|
282
|
-
const hasCycle = (node, path = []) => {
|
|
283
|
-
if (recursionStack.has(node)) {
|
|
284
|
-
console.log(chalk.red(`\n⚠️ Circular dependency detected: ${[...path, node].join(' → ')}`));
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (visited.has(node)) {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
visited.add(node);
|
|
293
|
-
recursionStack.add(node);
|
|
294
|
-
|
|
295
|
-
const neighbors = graph.get(node) || [];
|
|
296
|
-
for (const neighbor of neighbors) {
|
|
297
|
-
if (hasCycle(neighbor, [...path, node])) {
|
|
298
|
-
return true;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
recursionStack.delete(node);
|
|
303
|
-
return false;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
for (const node of graph.keys()) {
|
|
307
|
-
if (hasCycle(node)) {
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Validate all dependencies exist
|
|
317
|
-
* @param {Array} components - Components to validate
|
|
318
|
-
* @returns {Promise<Object>} Validation result
|
|
319
|
-
*/
|
|
320
|
-
async validateDependencies(components) {
|
|
321
|
-
const results = {
|
|
322
|
-
valid: true,
|
|
323
|
-
issues: [],
|
|
324
|
-
resolutions: []
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
for (const component of components) {
|
|
328
|
-
const deps = await this.analyzeDependencies(component.type, component.config);
|
|
329
|
-
|
|
330
|
-
if (deps.missing.length > 0) {
|
|
331
|
-
results.valid = false;
|
|
332
|
-
results.issues.push({
|
|
333
|
-
component: component.config.name || component.config.id,
|
|
334
|
-
missing: deps.missing
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// Suggest resolutions
|
|
338
|
-
for (const missing of deps.missing) {
|
|
339
|
-
results.resolutions.push({
|
|
340
|
-
action: 'create',
|
|
341
|
-
type: missing.type,
|
|
342
|
-
id: missing.id,
|
|
343
|
-
reason: missing.reason
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (deps.circular) {
|
|
349
|
-
results.valid = false;
|
|
350
|
-
results.issues.push({
|
|
351
|
-
component: component.config.name || component.config.id,
|
|
352
|
-
issue: 'Circular dependency detected'
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return results;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Get creation order for components based on dependencies
|
|
362
|
-
* @param {Array} components - Components to order
|
|
363
|
-
* @returns {Promise<Array>} Ordered components
|
|
364
|
-
*/
|
|
365
|
-
async getCreationOrder(components) {
|
|
366
|
-
const graph = new Map();
|
|
367
|
-
const inDegree = new Map();
|
|
368
|
-
|
|
369
|
-
// Initialize graph
|
|
370
|
-
for (const component of components) {
|
|
371
|
-
const id = this.getComponentId(component);
|
|
372
|
-
graph.set(id, []);
|
|
373
|
-
inDegree.set(id, 0);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Build dependency graph
|
|
377
|
-
for (const component of components) {
|
|
378
|
-
const id = this.getComponentId(component);
|
|
379
|
-
const deps = await this.analyzeDependencies(component.type, component.config);
|
|
380
|
-
|
|
381
|
-
for (const dep of deps.required) {
|
|
382
|
-
const depId = `${dep.type}:${dep.id}`;
|
|
383
|
-
|
|
384
|
-
// Only add edge if dependency is in our component list
|
|
385
|
-
if (graph.has(depId)) {
|
|
386
|
-
graph.get(depId).push(id);
|
|
387
|
-
inDegree.set(id, inDegree.get(id) + 1);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Topological sort using Kahn's algorithm
|
|
393
|
-
const queue = [];
|
|
394
|
-
const ordered = [];
|
|
395
|
-
|
|
396
|
-
// Find nodes with no dependencies
|
|
397
|
-
for (const [id, degree] of inDegree.entries()) {
|
|
398
|
-
if (degree === 0) {
|
|
399
|
-
queue.push(id);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
while (queue.length > 0) {
|
|
404
|
-
const current = queue.shift();
|
|
405
|
-
ordered.push(current);
|
|
406
|
-
|
|
407
|
-
// Process neighbors
|
|
408
|
-
for (const neighbor of graph.get(current) || []) {
|
|
409
|
-
inDegree.set(neighbor, inDegree.get(neighbor) - 1);
|
|
410
|
-
|
|
411
|
-
if (inDegree.get(neighbor) === 0) {
|
|
412
|
-
queue.push(neighbor);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Check for cycles
|
|
418
|
-
if (ordered.length !== components.length) {
|
|
419
|
-
throw new Error('Circular dependency detected - cannot determine creation order');
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Map back to components
|
|
423
|
-
const componentMap = new Map();
|
|
424
|
-
for (const component of components) {
|
|
425
|
-
const id = this.getComponentId(component);
|
|
426
|
-
componentMap.set(id, component);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return ordered.map(id => componentMap.get(id));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Get component ID for graph
|
|
434
|
-
* @private
|
|
435
|
-
*/
|
|
436
|
-
getComponentId(component) {
|
|
437
|
-
const name = component.config.agentName ||
|
|
438
|
-
component.config.taskId ||
|
|
439
|
-
component.config.workflowId ||
|
|
440
|
-
component.config.name ||
|
|
441
|
-
component.config.id;
|
|
442
|
-
return `${component.type}:${name}`;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Create missing dependencies interactively
|
|
447
|
-
* @param {Array} missing - Missing dependencies
|
|
448
|
-
* @returns {Promise<Array>} Components to create
|
|
449
|
-
*/
|
|
450
|
-
async promptForMissingDependencies(missing) {
|
|
451
|
-
const inquirer = require('inquirer');
|
|
452
|
-
const componentsToCreate = [];
|
|
453
|
-
|
|
454
|
-
console.log(chalk.yellow('\n⚠️ Missing dependencies detected:'));
|
|
455
|
-
|
|
456
|
-
for (const dep of missing) {
|
|
457
|
-
console.log(chalk.gray(` - ${dep.type}: ${dep.id} (${dep.reason})`));
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const { action } = await inquirer.prompt([{
|
|
461
|
-
type: 'list',
|
|
462
|
-
name: 'action',
|
|
463
|
-
message: 'How would you like to handle missing dependencies?',
|
|
464
|
-
choices: [
|
|
465
|
-
{ name: 'Create all missing dependencies', value: 'create-all' },
|
|
466
|
-
{ name: 'Select which to create', value: 'select' },
|
|
467
|
-
{ name: 'Skip dependency creation', value: 'skip' }
|
|
468
|
-
]
|
|
469
|
-
}]);
|
|
470
|
-
|
|
471
|
-
if (action === 'skip') {
|
|
472
|
-
return [];
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (action === 'create-all') {
|
|
476
|
-
for (const dep of missing) {
|
|
477
|
-
componentsToCreate.push({
|
|
478
|
-
type: dep.type,
|
|
479
|
-
config: await this.getMinimalConfig(dep.type, dep.id)
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
} else {
|
|
483
|
-
// Select which to create
|
|
484
|
-
const { selected } = await inquirer.prompt([{
|
|
485
|
-
type: 'checkbox',
|
|
486
|
-
name: 'selected',
|
|
487
|
-
message: 'Select dependencies to create:',
|
|
488
|
-
choices: missing.map(dep => ({
|
|
489
|
-
name: `${dep.type}: ${dep.id}`,
|
|
490
|
-
value: dep,
|
|
491
|
-
checked: true
|
|
492
|
-
}))
|
|
493
|
-
}]);
|
|
494
|
-
|
|
495
|
-
for (const dep of selected) {
|
|
496
|
-
componentsToCreate.push({
|
|
497
|
-
type: dep.type,
|
|
498
|
-
config: await this.getMinimalConfig(dep.type, dep.id)
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return componentsToCreate;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Validate workflow dependencies
|
|
508
|
-
* @param {Object} workflowData - Workflow configuration
|
|
509
|
-
* @returns {Promise<Object>} Validation result
|
|
510
|
-
*/
|
|
511
|
-
async validateWorkflowDependencies(workflowData) {
|
|
512
|
-
const result = {
|
|
513
|
-
valid: true,
|
|
514
|
-
issues: [],
|
|
515
|
-
taskDependencies: [],
|
|
516
|
-
missingTasks: []
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
// Extract all task references
|
|
520
|
-
const taskRefs = new Set();
|
|
521
|
-
|
|
522
|
-
if (workflowData.steps && Array.isArray(workflowData.steps)) {
|
|
523
|
-
for (const step of workflowData.steps) {
|
|
524
|
-
if (step.type === 'task' && step.taskId) {
|
|
525
|
-
taskRefs.add(step.taskId);
|
|
526
|
-
} else if (step.action && typeof step.action === 'string') {
|
|
527
|
-
// Extract task references from action strings
|
|
528
|
-
const taskMatches = step.action.match(/task:([a-z0-9-]+)/g);
|
|
529
|
-
if (taskMatches) {
|
|
530
|
-
taskMatches.forEach(match => {
|
|
531
|
-
const taskId = match.replace('task:', '');
|
|
532
|
-
taskRefs.add(taskId);
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Check step dependencies
|
|
538
|
-
if (step.dependencies && Array.isArray(step.dependencies)) {
|
|
539
|
-
for (const depId of step.dependencies) {
|
|
540
|
-
if (!workflowData.steps.find(s => s.id === depId)) {
|
|
541
|
-
result.valid = false;
|
|
542
|
-
result.issues.push({
|
|
543
|
-
step: step.id || step.name,
|
|
544
|
-
issue: `References non-existent step: ${depId}`
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Validate each task reference
|
|
553
|
-
for (const taskId of taskRefs) {
|
|
554
|
-
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
555
|
-
|
|
556
|
-
if (await fs.pathExists(taskPath)) {
|
|
557
|
-
result.taskDependencies.push({
|
|
558
|
-
taskId,
|
|
559
|
-
path: taskPath,
|
|
560
|
-
exists: true
|
|
561
|
-
});
|
|
562
|
-
} else {
|
|
563
|
-
result.valid = false;
|
|
564
|
-
result.missingTasks.push(taskId);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Check for circular step dependencies
|
|
569
|
-
if (workflowData.steps) {
|
|
570
|
-
const stepGraph = new Map();
|
|
571
|
-
|
|
572
|
-
for (const step of workflowData.steps) {
|
|
573
|
-
const stepId = step.id || step.name;
|
|
574
|
-
const deps = step.dependencies || [];
|
|
575
|
-
stepGraph.set(stepId, deps);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
if (this.detectCircularDependencies(stepGraph)) {
|
|
579
|
-
result.valid = false;
|
|
580
|
-
result.issues.push({
|
|
581
|
-
issue: 'Circular dependency detected in workflow steps'
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
return result;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Get minimal config for dependency creation
|
|
591
|
-
* @private
|
|
592
|
-
*/
|
|
593
|
-
async getMinimalConfig(type, id) {
|
|
594
|
-
const inquirer = require('inquirer');
|
|
595
|
-
|
|
596
|
-
switch (type) {
|
|
597
|
-
case 'agent':
|
|
598
|
-
const { agentTitle } = await inquirer.prompt([{
|
|
599
|
-
type: 'input',
|
|
600
|
-
name: 'agentTitle',
|
|
601
|
-
message: `Title for agent '${id}':`,
|
|
602
|
-
default: id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
603
|
-
}]);
|
|
604
|
-
|
|
605
|
-
return {
|
|
606
|
-
agentName: id,
|
|
607
|
-
agentTitle,
|
|
608
|
-
whenToUse: `Dependency for ${id}`,
|
|
609
|
-
commands: []
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
case 'task':
|
|
613
|
-
const { taskTitle } = await inquirer.prompt([{
|
|
614
|
-
type: 'input',
|
|
615
|
-
name: 'taskTitle',
|
|
616
|
-
message: `Title for task '${id}':`,
|
|
617
|
-
default: id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
618
|
-
}]);
|
|
619
|
-
|
|
620
|
-
return {
|
|
621
|
-
taskId: id,
|
|
622
|
-
taskTitle,
|
|
623
|
-
taskDescription: `Dependency task for ${id}`,
|
|
624
|
-
agentName: 'aios-developer'
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
case 'workflow':
|
|
628
|
-
return {
|
|
629
|
-
workflowId: id,
|
|
630
|
-
workflowName: id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
631
|
-
workflowType: 'standard',
|
|
632
|
-
steps: []
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Analyzer for AIOS-FULLSTACK
|
|
3
|
+
* Analyzes and resolves dependencies between components
|
|
4
|
+
* @module dependency-analyzer
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const _yaml = require('js-yaml');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
|
|
12
|
+
class DependencyAnalyzer {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.rootPath = options.rootPath || process.cwd();
|
|
15
|
+
this.manifestPath = path.join(this.rootPath, 'aios-core', 'team-manifest.yaml');
|
|
16
|
+
|
|
17
|
+
// Component paths
|
|
18
|
+
this.paths = {
|
|
19
|
+
agents: path.join(this.rootPath, 'aios-core', 'agents'),
|
|
20
|
+
tasks: path.join(this.rootPath, 'aios-core', 'tasks'),
|
|
21
|
+
workflows: path.join(this.rootPath, 'aios-core', 'workflows')
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Dependency cache
|
|
25
|
+
this.dependencyCache = new Map();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Analyze dependencies for a component
|
|
30
|
+
* @param {string} componentType - Type of component (agent/task/workflow)
|
|
31
|
+
* @param {Object} componentData - Component configuration data
|
|
32
|
+
* @returns {Promise<Object>} Dependency analysis result
|
|
33
|
+
*/
|
|
34
|
+
async analyzeDependencies(componentType, componentData) {
|
|
35
|
+
const dependencies = {
|
|
36
|
+
required: [],
|
|
37
|
+
optional: [],
|
|
38
|
+
missing: [],
|
|
39
|
+
circular: false,
|
|
40
|
+
graph: new Map()
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
switch (componentType) {
|
|
44
|
+
case 'agent':
|
|
45
|
+
await this.analyzeAgentDependencies(componentData, dependencies);
|
|
46
|
+
break;
|
|
47
|
+
case 'task':
|
|
48
|
+
await this.analyzeTaskDependencies(componentData, dependencies);
|
|
49
|
+
break;
|
|
50
|
+
case 'workflow':
|
|
51
|
+
await this.analyzeWorkflowDependencies(componentData, dependencies);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check for circular dependencies
|
|
56
|
+
dependencies.circular = this.detectCircularDependencies(dependencies.graph);
|
|
57
|
+
|
|
58
|
+
return dependencies;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Analyze agent dependencies
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
async analyzeAgentDependencies(agentData, dependencies) {
|
|
66
|
+
// Check for task dependencies from commands
|
|
67
|
+
if (agentData.commands && Array.isArray(agentData.commands)) {
|
|
68
|
+
for (const command of agentData.commands) {
|
|
69
|
+
const taskId = this.commandToTaskId(command);
|
|
70
|
+
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
71
|
+
|
|
72
|
+
if (await fs.pathExists(taskPath)) {
|
|
73
|
+
dependencies.required.push({
|
|
74
|
+
type: 'task',
|
|
75
|
+
id: taskId,
|
|
76
|
+
path: taskPath,
|
|
77
|
+
reason: `Command '${command}' requires task`
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
dependencies.missing.push({
|
|
81
|
+
type: 'task',
|
|
82
|
+
id: taskId,
|
|
83
|
+
reason: `Command '${command}' requires task file`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for workflow dependencies
|
|
90
|
+
if (agentData.workflows && Array.isArray(agentData.workflows)) {
|
|
91
|
+
for (const workflowId of agentData.workflows) {
|
|
92
|
+
const workflowPath = path.join(this.paths.workflows, `${workflowId}.yaml`);
|
|
93
|
+
|
|
94
|
+
if (await fs.pathExists(workflowPath)) {
|
|
95
|
+
dependencies.optional.push({
|
|
96
|
+
type: 'workflow',
|
|
97
|
+
id: workflowId,
|
|
98
|
+
path: workflowPath,
|
|
99
|
+
reason: 'Agent workflow reference'
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for agent dependencies
|
|
106
|
+
if (agentData.dependencies?.agents) {
|
|
107
|
+
for (const agentId of agentData.dependencies.agents) {
|
|
108
|
+
const agentPath = path.join(this.paths.agents, `${agentId}.md`);
|
|
109
|
+
|
|
110
|
+
if (await fs.pathExists(agentPath)) {
|
|
111
|
+
dependencies.required.push({
|
|
112
|
+
type: 'agent',
|
|
113
|
+
id: agentId,
|
|
114
|
+
path: agentPath,
|
|
115
|
+
reason: 'Explicit agent dependency'
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
dependencies.missing.push({
|
|
119
|
+
type: 'agent',
|
|
120
|
+
id: agentId,
|
|
121
|
+
reason: 'Required agent not found'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Analyze task dependencies
|
|
130
|
+
* @private
|
|
131
|
+
*/
|
|
132
|
+
async analyzeTaskDependencies(taskData, dependencies) {
|
|
133
|
+
// Check for agent dependency
|
|
134
|
+
if (taskData.agentName) {
|
|
135
|
+
const agentPath = path.join(this.paths.agents, `${taskData.agentName}.md`);
|
|
136
|
+
|
|
137
|
+
if (await fs.pathExists(agentPath)) {
|
|
138
|
+
dependencies.required.push({
|
|
139
|
+
type: 'agent',
|
|
140
|
+
id: taskData.agentName,
|
|
141
|
+
path: agentPath,
|
|
142
|
+
reason: 'Task belongs to agent'
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
dependencies.missing.push({
|
|
146
|
+
type: 'agent',
|
|
147
|
+
id: taskData.agentName,
|
|
148
|
+
reason: 'Agent not found for task'
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check for other task dependencies
|
|
154
|
+
if (taskData.dependencies?.tasks) {
|
|
155
|
+
for (const taskId of taskData.dependencies.tasks) {
|
|
156
|
+
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
157
|
+
|
|
158
|
+
if (await fs.pathExists(taskPath)) {
|
|
159
|
+
dependencies.required.push({
|
|
160
|
+
type: 'task',
|
|
161
|
+
id: taskId,
|
|
162
|
+
path: taskPath,
|
|
163
|
+
reason: 'Task dependency'
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
dependencies.missing.push({
|
|
167
|
+
type: 'task',
|
|
168
|
+
id: taskId,
|
|
169
|
+
reason: 'Required task not found'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Analyze workflow dependencies
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
async analyzeWorkflowDependencies(workflowData, dependencies) {
|
|
181
|
+
// Extract task references from workflow steps
|
|
182
|
+
const taskIds = new Set();
|
|
183
|
+
|
|
184
|
+
if (workflowData.steps && Array.isArray(workflowData.steps)) {
|
|
185
|
+
for (const step of workflowData.steps) {
|
|
186
|
+
if (step.type === 'task' && step.taskId) {
|
|
187
|
+
taskIds.add(step.taskId);
|
|
188
|
+
} else if (step.action?.includes('task:')) {
|
|
189
|
+
const taskMatch = step.action.match(/task:([a-z0-9-]+)/);
|
|
190
|
+
if (taskMatch) {
|
|
191
|
+
taskIds.add(taskMatch[1]);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check each task dependency
|
|
198
|
+
for (const taskId of taskIds) {
|
|
199
|
+
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
200
|
+
|
|
201
|
+
if (await fs.pathExists(taskPath)) {
|
|
202
|
+
dependencies.required.push({
|
|
203
|
+
type: 'task',
|
|
204
|
+
id: taskId,
|
|
205
|
+
path: taskPath,
|
|
206
|
+
reason: 'Workflow step requires task'
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Also check the task's agent dependency
|
|
210
|
+
const taskContent = await fs.readFile(taskPath, 'utf8');
|
|
211
|
+
const agentMatch = taskContent.match(/\*\*Agent:\*\*\s*([a-z0-9-]+)/);
|
|
212
|
+
if (agentMatch) {
|
|
213
|
+
const agentId = agentMatch[1];
|
|
214
|
+
const agentPath = path.join(this.paths.agents, `${agentId}.md`);
|
|
215
|
+
|
|
216
|
+
if (await fs.pathExists(agentPath)) {
|
|
217
|
+
dependencies.required.push({
|
|
218
|
+
type: 'agent',
|
|
219
|
+
id: agentId,
|
|
220
|
+
path: agentPath,
|
|
221
|
+
reason: 'Task requires agent'
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
dependencies.missing.push({
|
|
227
|
+
type: 'task',
|
|
228
|
+
id: taskId,
|
|
229
|
+
reason: 'Workflow step requires task'
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check for sub-workflow dependencies
|
|
235
|
+
if (workflowData.dependencies?.workflows) {
|
|
236
|
+
for (const workflowId of workflowData.dependencies.workflows) {
|
|
237
|
+
const workflowPath = path.join(this.paths.workflows, `${workflowId}.yaml`);
|
|
238
|
+
|
|
239
|
+
if (await fs.pathExists(workflowPath)) {
|
|
240
|
+
dependencies.required.push({
|
|
241
|
+
type: 'workflow',
|
|
242
|
+
id: workflowId,
|
|
243
|
+
path: workflowPath,
|
|
244
|
+
reason: 'Sub-workflow dependency'
|
|
245
|
+
});
|
|
246
|
+
} else {
|
|
247
|
+
dependencies.missing.push({
|
|
248
|
+
type: 'workflow',
|
|
249
|
+
id: workflowId,
|
|
250
|
+
reason: 'Required workflow not found'
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Convert command name to task ID
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
commandToTaskId(command) {
|
|
262
|
+
// Remove asterisk if present
|
|
263
|
+
const cleanCommand = command.replace(/^\*/, '');
|
|
264
|
+
|
|
265
|
+
// Handle common patterns
|
|
266
|
+
if (cleanCommand.startsWith('create-')) {
|
|
267
|
+
return cleanCommand;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Convert to task ID format
|
|
271
|
+
return cleanCommand.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Detect circular dependencies
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
detectCircularDependencies(graph) {
|
|
279
|
+
const visited = new Set();
|
|
280
|
+
const recursionStack = new Set();
|
|
281
|
+
|
|
282
|
+
const hasCycle = (node, path = []) => {
|
|
283
|
+
if (recursionStack.has(node)) {
|
|
284
|
+
console.log(chalk.red(`\n⚠️ Circular dependency detected: ${[...path, node].join(' → ')}`));
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (visited.has(node)) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
visited.add(node);
|
|
293
|
+
recursionStack.add(node);
|
|
294
|
+
|
|
295
|
+
const neighbors = graph.get(node) || [];
|
|
296
|
+
for (const neighbor of neighbors) {
|
|
297
|
+
if (hasCycle(neighbor, [...path, node])) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
recursionStack.delete(node);
|
|
303
|
+
return false;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
for (const node of graph.keys()) {
|
|
307
|
+
if (hasCycle(node)) {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Validate all dependencies exist
|
|
317
|
+
* @param {Array} components - Components to validate
|
|
318
|
+
* @returns {Promise<Object>} Validation result
|
|
319
|
+
*/
|
|
320
|
+
async validateDependencies(components) {
|
|
321
|
+
const results = {
|
|
322
|
+
valid: true,
|
|
323
|
+
issues: [],
|
|
324
|
+
resolutions: []
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
for (const component of components) {
|
|
328
|
+
const deps = await this.analyzeDependencies(component.type, component.config);
|
|
329
|
+
|
|
330
|
+
if (deps.missing.length > 0) {
|
|
331
|
+
results.valid = false;
|
|
332
|
+
results.issues.push({
|
|
333
|
+
component: component.config.name || component.config.id,
|
|
334
|
+
missing: deps.missing
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Suggest resolutions
|
|
338
|
+
for (const missing of deps.missing) {
|
|
339
|
+
results.resolutions.push({
|
|
340
|
+
action: 'create',
|
|
341
|
+
type: missing.type,
|
|
342
|
+
id: missing.id,
|
|
343
|
+
reason: missing.reason
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (deps.circular) {
|
|
349
|
+
results.valid = false;
|
|
350
|
+
results.issues.push({
|
|
351
|
+
component: component.config.name || component.config.id,
|
|
352
|
+
issue: 'Circular dependency detected'
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return results;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get creation order for components based on dependencies
|
|
362
|
+
* @param {Array} components - Components to order
|
|
363
|
+
* @returns {Promise<Array>} Ordered components
|
|
364
|
+
*/
|
|
365
|
+
async getCreationOrder(components) {
|
|
366
|
+
const graph = new Map();
|
|
367
|
+
const inDegree = new Map();
|
|
368
|
+
|
|
369
|
+
// Initialize graph
|
|
370
|
+
for (const component of components) {
|
|
371
|
+
const id = this.getComponentId(component);
|
|
372
|
+
graph.set(id, []);
|
|
373
|
+
inDegree.set(id, 0);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Build dependency graph
|
|
377
|
+
for (const component of components) {
|
|
378
|
+
const id = this.getComponentId(component);
|
|
379
|
+
const deps = await this.analyzeDependencies(component.type, component.config);
|
|
380
|
+
|
|
381
|
+
for (const dep of deps.required) {
|
|
382
|
+
const depId = `${dep.type}:${dep.id}`;
|
|
383
|
+
|
|
384
|
+
// Only add edge if dependency is in our component list
|
|
385
|
+
if (graph.has(depId)) {
|
|
386
|
+
graph.get(depId).push(id);
|
|
387
|
+
inDegree.set(id, inDegree.get(id) + 1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Topological sort using Kahn's algorithm
|
|
393
|
+
const queue = [];
|
|
394
|
+
const ordered = [];
|
|
395
|
+
|
|
396
|
+
// Find nodes with no dependencies
|
|
397
|
+
for (const [id, degree] of inDegree.entries()) {
|
|
398
|
+
if (degree === 0) {
|
|
399
|
+
queue.push(id);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
while (queue.length > 0) {
|
|
404
|
+
const current = queue.shift();
|
|
405
|
+
ordered.push(current);
|
|
406
|
+
|
|
407
|
+
// Process neighbors
|
|
408
|
+
for (const neighbor of graph.get(current) || []) {
|
|
409
|
+
inDegree.set(neighbor, inDegree.get(neighbor) - 1);
|
|
410
|
+
|
|
411
|
+
if (inDegree.get(neighbor) === 0) {
|
|
412
|
+
queue.push(neighbor);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Check for cycles
|
|
418
|
+
if (ordered.length !== components.length) {
|
|
419
|
+
throw new Error('Circular dependency detected - cannot determine creation order');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Map back to components
|
|
423
|
+
const componentMap = new Map();
|
|
424
|
+
for (const component of components) {
|
|
425
|
+
const id = this.getComponentId(component);
|
|
426
|
+
componentMap.set(id, component);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return ordered.map(id => componentMap.get(id));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get component ID for graph
|
|
434
|
+
* @private
|
|
435
|
+
*/
|
|
436
|
+
getComponentId(component) {
|
|
437
|
+
const name = component.config.agentName ||
|
|
438
|
+
component.config.taskId ||
|
|
439
|
+
component.config.workflowId ||
|
|
440
|
+
component.config.name ||
|
|
441
|
+
component.config.id;
|
|
442
|
+
return `${component.type}:${name}`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Create missing dependencies interactively
|
|
447
|
+
* @param {Array} missing - Missing dependencies
|
|
448
|
+
* @returns {Promise<Array>} Components to create
|
|
449
|
+
*/
|
|
450
|
+
async promptForMissingDependencies(missing) {
|
|
451
|
+
const inquirer = require('inquirer');
|
|
452
|
+
const componentsToCreate = [];
|
|
453
|
+
|
|
454
|
+
console.log(chalk.yellow('\n⚠️ Missing dependencies detected:'));
|
|
455
|
+
|
|
456
|
+
for (const dep of missing) {
|
|
457
|
+
console.log(chalk.gray(` - ${dep.type}: ${dep.id} (${dep.reason})`));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const { action } = await inquirer.prompt([{
|
|
461
|
+
type: 'list',
|
|
462
|
+
name: 'action',
|
|
463
|
+
message: 'How would you like to handle missing dependencies?',
|
|
464
|
+
choices: [
|
|
465
|
+
{ name: 'Create all missing dependencies', value: 'create-all' },
|
|
466
|
+
{ name: 'Select which to create', value: 'select' },
|
|
467
|
+
{ name: 'Skip dependency creation', value: 'skip' }
|
|
468
|
+
]
|
|
469
|
+
}]);
|
|
470
|
+
|
|
471
|
+
if (action === 'skip') {
|
|
472
|
+
return [];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (action === 'create-all') {
|
|
476
|
+
for (const dep of missing) {
|
|
477
|
+
componentsToCreate.push({
|
|
478
|
+
type: dep.type,
|
|
479
|
+
config: await this.getMinimalConfig(dep.type, dep.id)
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
// Select which to create
|
|
484
|
+
const { selected } = await inquirer.prompt([{
|
|
485
|
+
type: 'checkbox',
|
|
486
|
+
name: 'selected',
|
|
487
|
+
message: 'Select dependencies to create:',
|
|
488
|
+
choices: missing.map(dep => ({
|
|
489
|
+
name: `${dep.type}: ${dep.id}`,
|
|
490
|
+
value: dep,
|
|
491
|
+
checked: true
|
|
492
|
+
}))
|
|
493
|
+
}]);
|
|
494
|
+
|
|
495
|
+
for (const dep of selected) {
|
|
496
|
+
componentsToCreate.push({
|
|
497
|
+
type: dep.type,
|
|
498
|
+
config: await this.getMinimalConfig(dep.type, dep.id)
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return componentsToCreate;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Validate workflow dependencies
|
|
508
|
+
* @param {Object} workflowData - Workflow configuration
|
|
509
|
+
* @returns {Promise<Object>} Validation result
|
|
510
|
+
*/
|
|
511
|
+
async validateWorkflowDependencies(workflowData) {
|
|
512
|
+
const result = {
|
|
513
|
+
valid: true,
|
|
514
|
+
issues: [],
|
|
515
|
+
taskDependencies: [],
|
|
516
|
+
missingTasks: []
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// Extract all task references
|
|
520
|
+
const taskRefs = new Set();
|
|
521
|
+
|
|
522
|
+
if (workflowData.steps && Array.isArray(workflowData.steps)) {
|
|
523
|
+
for (const step of workflowData.steps) {
|
|
524
|
+
if (step.type === 'task' && step.taskId) {
|
|
525
|
+
taskRefs.add(step.taskId);
|
|
526
|
+
} else if (step.action && typeof step.action === 'string') {
|
|
527
|
+
// Extract task references from action strings
|
|
528
|
+
const taskMatches = step.action.match(/task:([a-z0-9-]+)/g);
|
|
529
|
+
if (taskMatches) {
|
|
530
|
+
taskMatches.forEach(match => {
|
|
531
|
+
const taskId = match.replace('task:', '');
|
|
532
|
+
taskRefs.add(taskId);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Check step dependencies
|
|
538
|
+
if (step.dependencies && Array.isArray(step.dependencies)) {
|
|
539
|
+
for (const depId of step.dependencies) {
|
|
540
|
+
if (!workflowData.steps.find(s => s.id === depId)) {
|
|
541
|
+
result.valid = false;
|
|
542
|
+
result.issues.push({
|
|
543
|
+
step: step.id || step.name,
|
|
544
|
+
issue: `References non-existent step: ${depId}`
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Validate each task reference
|
|
553
|
+
for (const taskId of taskRefs) {
|
|
554
|
+
const taskPath = path.join(this.paths.tasks, `${taskId}.md`);
|
|
555
|
+
|
|
556
|
+
if (await fs.pathExists(taskPath)) {
|
|
557
|
+
result.taskDependencies.push({
|
|
558
|
+
taskId,
|
|
559
|
+
path: taskPath,
|
|
560
|
+
exists: true
|
|
561
|
+
});
|
|
562
|
+
} else {
|
|
563
|
+
result.valid = false;
|
|
564
|
+
result.missingTasks.push(taskId);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Check for circular step dependencies
|
|
569
|
+
if (workflowData.steps) {
|
|
570
|
+
const stepGraph = new Map();
|
|
571
|
+
|
|
572
|
+
for (const step of workflowData.steps) {
|
|
573
|
+
const stepId = step.id || step.name;
|
|
574
|
+
const deps = step.dependencies || [];
|
|
575
|
+
stepGraph.set(stepId, deps);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (this.detectCircularDependencies(stepGraph)) {
|
|
579
|
+
result.valid = false;
|
|
580
|
+
result.issues.push({
|
|
581
|
+
issue: 'Circular dependency detected in workflow steps'
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Get minimal config for dependency creation
|
|
591
|
+
* @private
|
|
592
|
+
*/
|
|
593
|
+
async getMinimalConfig(type, id) {
|
|
594
|
+
const inquirer = require('inquirer');
|
|
595
|
+
|
|
596
|
+
switch (type) {
|
|
597
|
+
case 'agent':
|
|
598
|
+
const { agentTitle } = await inquirer.prompt([{
|
|
599
|
+
type: 'input',
|
|
600
|
+
name: 'agentTitle',
|
|
601
|
+
message: `Title for agent '${id}':`,
|
|
602
|
+
default: id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
603
|
+
}]);
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
agentName: id,
|
|
607
|
+
agentTitle,
|
|
608
|
+
whenToUse: `Dependency for ${id}`,
|
|
609
|
+
commands: []
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
case 'task':
|
|
613
|
+
const { taskTitle } = await inquirer.prompt([{
|
|
614
|
+
type: 'input',
|
|
615
|
+
name: 'taskTitle',
|
|
616
|
+
message: `Title for task '${id}':`,
|
|
617
|
+
default: id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
|
|
618
|
+
}]);
|
|
619
|
+
|
|
620
|
+
return {
|
|
621
|
+
taskId: id,
|
|
622
|
+
taskTitle,
|
|
623
|
+
taskDescription: `Dependency task for ${id}`,
|
|
624
|
+
agentName: 'aios-developer'
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
case 'workflow':
|
|
628
|
+
return {
|
|
629
|
+
workflowId: id,
|
|
630
|
+
workflowName: id.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
631
|
+
workflowType: 'standard',
|
|
632
|
+
steps: []
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
638
|
module.exports = DependencyAnalyzer;
|