musubi-sdd 3.10.0 → 5.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.
Files changed (44) hide show
  1. package/README.md +24 -19
  2. package/package.json +1 -1
  3. package/src/agents/agent-loop.js +532 -0
  4. package/src/agents/agentic/code-generator.js +767 -0
  5. package/src/agents/agentic/code-reviewer.js +698 -0
  6. package/src/agents/agentic/index.js +43 -0
  7. package/src/agents/function-tool.js +432 -0
  8. package/src/agents/index.js +45 -0
  9. package/src/agents/schema-generator.js +514 -0
  10. package/src/analyzers/ast-extractor.js +870 -0
  11. package/src/analyzers/context-optimizer.js +681 -0
  12. package/src/analyzers/repository-map.js +692 -0
  13. package/src/integrations/index.js +7 -1
  14. package/src/integrations/mcp/index.js +175 -0
  15. package/src/integrations/mcp/mcp-context-provider.js +472 -0
  16. package/src/integrations/mcp/mcp-discovery.js +436 -0
  17. package/src/integrations/mcp/mcp-tool-registry.js +467 -0
  18. package/src/integrations/mcp-connector.js +818 -0
  19. package/src/integrations/tool-discovery.js +589 -0
  20. package/src/managers/index.js +7 -0
  21. package/src/managers/skill-tools.js +565 -0
  22. package/src/monitoring/cost-tracker.js +7 -0
  23. package/src/monitoring/incident-manager.js +10 -0
  24. package/src/monitoring/observability.js +10 -0
  25. package/src/monitoring/quality-dashboard.js +491 -0
  26. package/src/monitoring/release-manager.js +10 -0
  27. package/src/orchestration/agent-skill-binding.js +655 -0
  28. package/src/orchestration/error-handler.js +827 -0
  29. package/src/orchestration/index.js +235 -1
  30. package/src/orchestration/mcp-tool-adapters.js +896 -0
  31. package/src/orchestration/reasoning/index.js +58 -0
  32. package/src/orchestration/reasoning/planning-engine.js +831 -0
  33. package/src/orchestration/reasoning/reasoning-engine.js +710 -0
  34. package/src/orchestration/reasoning/self-correction.js +751 -0
  35. package/src/orchestration/skill-executor.js +665 -0
  36. package/src/orchestration/skill-registry.js +650 -0
  37. package/src/orchestration/workflow-examples.js +1072 -0
  38. package/src/orchestration/workflow-executor.js +779 -0
  39. package/src/phase4-integration.js +248 -0
  40. package/src/phase5-integration.js +402 -0
  41. package/src/steering/steering-auto-update.js +572 -0
  42. package/src/steering/steering-validator.js +547 -0
  43. package/src/templates/template-constraints.js +646 -0
  44. package/src/validators/advanced-validation.js +580 -0
@@ -0,0 +1,655 @@
1
+ /**
2
+ * Agent-Skill Binding - Dynamic capability-based skill assignment
3
+ * Sprint 3.3: Skill System Architecture
4
+ *
5
+ * Features:
6
+ * - Agent capability scoring
7
+ * - Dynamic skill-agent matching
8
+ * - Permission-based access control
9
+ * - Skill affinity management
10
+ * - Load balancing
11
+ */
12
+
13
+ const EventEmitter = require('events');
14
+
15
+ /**
16
+ * Agent status
17
+ */
18
+ const AgentStatus = {
19
+ AVAILABLE: 'available',
20
+ BUSY: 'busy',
21
+ OFFLINE: 'offline',
22
+ MAINTENANCE: 'maintenance'
23
+ };
24
+
25
+ /**
26
+ * Agent definition
27
+ */
28
+ class AgentDefinition {
29
+ constructor(options = {}) {
30
+ this.id = options.id || '';
31
+ this.name = options.name || '';
32
+ this.description = options.description || '';
33
+ this.capabilities = options.capabilities || [];
34
+ this.permissions = options.permissions || [];
35
+ this.maxConcurrentTasks = options.maxConcurrentTasks || 5;
36
+ this.priority = options.priority || 'normal';
37
+ this.tags = options.tags || [];
38
+ this.metadata = options.metadata || {};
39
+ }
40
+
41
+ validate() {
42
+ const errors = [];
43
+
44
+ if (!this.id) {
45
+ errors.push('Agent ID is required');
46
+ }
47
+
48
+ if (!this.name) {
49
+ errors.push('Agent name is required');
50
+ }
51
+
52
+ if (!Array.isArray(this.capabilities)) {
53
+ errors.push('Capabilities must be an array');
54
+ }
55
+
56
+ return {
57
+ valid: errors.length === 0,
58
+ errors
59
+ };
60
+ }
61
+
62
+ toJSON() {
63
+ return {
64
+ id: this.id,
65
+ name: this.name,
66
+ description: this.description,
67
+ capabilities: this.capabilities,
68
+ permissions: this.permissions,
69
+ maxConcurrentTasks: this.maxConcurrentTasks,
70
+ priority: this.priority,
71
+ tags: this.tags,
72
+ metadata: this.metadata
73
+ };
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Binding record
79
+ */
80
+ class BindingRecord {
81
+ constructor(agentId, skillId, options = {}) {
82
+ this.agentId = agentId;
83
+ this.skillId = skillId;
84
+ this.score = options.score || 0;
85
+ this.affinity = options.affinity || 0;
86
+ this.executionCount = options.executionCount || 0;
87
+ this.successRate = options.successRate || 1.0;
88
+ this.averageExecutionTime = options.averageExecutionTime || 0;
89
+ this.lastExecutedAt = options.lastExecutedAt || null;
90
+ this.createdAt = new Date().toISOString();
91
+ }
92
+
93
+ updateStats(success, executionTime) {
94
+ this.executionCount++;
95
+ const successWeight = success ? 1 : 0;
96
+ this.successRate = (
97
+ (this.successRate * (this.executionCount - 1) + successWeight) /
98
+ this.executionCount
99
+ );
100
+ this.averageExecutionTime = (
101
+ (this.averageExecutionTime * (this.executionCount - 1) + executionTime) /
102
+ this.executionCount
103
+ );
104
+ this.lastExecutedAt = new Date().toISOString();
105
+
106
+ // Update affinity based on performance
107
+ this.affinity = this._calculateAffinity();
108
+ }
109
+
110
+ _calculateAffinity() {
111
+ // Affinity increases with success rate and execution count
112
+ const successFactor = this.successRate * 50;
113
+ const experienceFactor = Math.min(this.executionCount / 10, 30);
114
+ const recencyFactor = this.lastExecutedAt
115
+ ? Math.max(0, 20 - (Date.now() - new Date(this.lastExecutedAt).getTime()) / 86400000)
116
+ : 0;
117
+
118
+ return Math.round(successFactor + experienceFactor + recencyFactor);
119
+ }
120
+
121
+ toJSON() {
122
+ return {
123
+ agentId: this.agentId,
124
+ skillId: this.skillId,
125
+ score: this.score,
126
+ affinity: this.affinity,
127
+ executionCount: this.executionCount,
128
+ successRate: this.successRate,
129
+ averageExecutionTime: this.averageExecutionTime,
130
+ lastExecutedAt: this.lastExecutedAt
131
+ };
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Capability matcher
137
+ */
138
+ class CapabilityMatcher {
139
+ constructor() {
140
+ this.capabilityWeights = new Map();
141
+ this.synonyms = new Map();
142
+ }
143
+
144
+ /**
145
+ * Set weight for a capability (higher = more important)
146
+ */
147
+ setWeight(capability, weight) {
148
+ this.capabilityWeights.set(capability, weight);
149
+ }
150
+
151
+ /**
152
+ * Add synonym mapping
153
+ */
154
+ addSynonym(capability, synonym) {
155
+ if (!this.synonyms.has(capability)) {
156
+ this.synonyms.set(capability, new Set());
157
+ }
158
+ this.synonyms.get(capability).add(synonym);
159
+ }
160
+
161
+ /**
162
+ * Calculate match score between agent capabilities and skill requirements
163
+ */
164
+ calculateScore(agentCapabilities, skillRequirements) {
165
+ if (!skillRequirements || skillRequirements.length === 0) {
166
+ return 100; // No requirements = full match
167
+ }
168
+
169
+ let totalWeight = 0;
170
+ let matchedWeight = 0;
171
+
172
+ for (const req of skillRequirements) {
173
+ const weight = this.capabilityWeights.get(req) || 1;
174
+ totalWeight += weight;
175
+
176
+ if (this._hasCapability(agentCapabilities, req)) {
177
+ matchedWeight += weight;
178
+ }
179
+ }
180
+
181
+ return totalWeight > 0 ? Math.round((matchedWeight / totalWeight) * 100) : 0;
182
+ }
183
+
184
+ /**
185
+ * Check if agent has capability (including synonyms)
186
+ */
187
+ _hasCapability(agentCapabilities, required) {
188
+ if (agentCapabilities.includes(required)) {
189
+ return true;
190
+ }
191
+
192
+ // Check synonyms
193
+ for (const [cap, syns] of this.synonyms) {
194
+ if (cap === required && agentCapabilities.some(ac => syns.has(ac))) {
195
+ return true;
196
+ }
197
+ if (syns.has(required) && agentCapabilities.includes(cap)) {
198
+ return true;
199
+ }
200
+ }
201
+
202
+ return false;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Main Agent-Skill Binding class
208
+ */
209
+ class AgentSkillBinding extends EventEmitter {
210
+ constructor(skillRegistry, options = {}) {
211
+ super();
212
+ this.skillRegistry = skillRegistry;
213
+ this.agents = new Map();
214
+ this.agentStatus = new Map();
215
+ this.agentLoad = new Map();
216
+ this.bindings = new Map(); // Map<agentId, Map<skillId, BindingRecord>>
217
+ this.matcher = new CapabilityMatcher();
218
+
219
+ // Options
220
+ this.options = {
221
+ autoBinding: options.autoBinding !== false,
222
+ minMatchScore: options.minMatchScore || 50,
223
+ enableLoadBalancing: options.enableLoadBalancing !== false,
224
+ affinityWeight: options.affinityWeight || 0.3
225
+ };
226
+
227
+ // Listen to skill registry events
228
+ if (this.skillRegistry) {
229
+ this.skillRegistry.on('skill-registered', ({ skillId }) => {
230
+ if (this.options.autoBinding) {
231
+ this._autoBindSkill(skillId);
232
+ }
233
+ });
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Register an agent
239
+ */
240
+ registerAgent(agentDef) {
241
+ const agent = agentDef instanceof AgentDefinition
242
+ ? agentDef
243
+ : new AgentDefinition(agentDef);
244
+
245
+ const validation = agent.validate();
246
+ if (!validation.valid) {
247
+ throw new Error(`Invalid agent: ${validation.errors.join(', ')}`);
248
+ }
249
+
250
+ if (this.agents.has(agent.id)) {
251
+ throw new Error(`Agent '${agent.id}' already exists`);
252
+ }
253
+
254
+ this.agents.set(agent.id, agent);
255
+ this.agentStatus.set(agent.id, AgentStatus.AVAILABLE);
256
+ this.agentLoad.set(agent.id, 0);
257
+ this.bindings.set(agent.id, new Map());
258
+
259
+ // Auto-bind existing skills
260
+ if (this.options.autoBinding && this.skillRegistry) {
261
+ this._autoBindAgent(agent.id);
262
+ }
263
+
264
+ this.emit('agent-registered', { agentId: agent.id, agent });
265
+
266
+ return agent;
267
+ }
268
+
269
+ /**
270
+ * Unregister an agent
271
+ */
272
+ unregisterAgent(agentId) {
273
+ if (!this.agents.has(agentId)) {
274
+ return false;
275
+ }
276
+
277
+ this.agents.delete(agentId);
278
+ this.agentStatus.delete(agentId);
279
+ this.agentLoad.delete(agentId);
280
+ this.bindings.delete(agentId);
281
+
282
+ this.emit('agent-unregistered', { agentId });
283
+
284
+ return true;
285
+ }
286
+
287
+ /**
288
+ * Get agent by ID
289
+ */
290
+ getAgent(agentId) {
291
+ return this.agents.get(agentId) || null;
292
+ }
293
+
294
+ /**
295
+ * Get all agents
296
+ */
297
+ getAllAgents() {
298
+ return Array.from(this.agents.values());
299
+ }
300
+
301
+ /**
302
+ * Update agent status
303
+ */
304
+ setAgentStatus(agentId, status) {
305
+ if (!this.agents.has(agentId)) {
306
+ return false;
307
+ }
308
+
309
+ const previousStatus = this.agentStatus.get(agentId);
310
+ this.agentStatus.set(agentId, status);
311
+
312
+ if (previousStatus !== status) {
313
+ this.emit('agent-status-changed', { agentId, previousStatus, newStatus: status });
314
+ }
315
+
316
+ return true;
317
+ }
318
+
319
+ /**
320
+ * Get agent status
321
+ */
322
+ getAgentStatus(agentId) {
323
+ return this.agentStatus.get(agentId) || null;
324
+ }
325
+
326
+ /**
327
+ * Get available agents
328
+ */
329
+ getAvailableAgents() {
330
+ return this.getAllAgents().filter(
331
+ agent => this.agentStatus.get(agent.id) === AgentStatus.AVAILABLE
332
+ );
333
+ }
334
+
335
+ /**
336
+ * Create manual binding
337
+ */
338
+ bind(agentId, skillId, options = {}) {
339
+ const agent = this.agents.get(agentId);
340
+ if (!agent) {
341
+ throw new Error(`Agent '${agentId}' not found`);
342
+ }
343
+
344
+ const skill = this.skillRegistry?.getSkill(skillId);
345
+ if (!skill) {
346
+ throw new Error(`Skill '${skillId}' not found`);
347
+ }
348
+
349
+ // Check permissions
350
+ if (skill.permissions && skill.permissions.length > 0) {
351
+ const hasPermission = skill.permissions.every(
352
+ p => agent.permissions.includes(p)
353
+ );
354
+ if (!hasPermission) {
355
+ throw new Error(`Agent '${agentId}' lacks required permissions for skill '${skillId}'`);
356
+ }
357
+ }
358
+
359
+ // Calculate score
360
+ const score = options.score || this.matcher.calculateScore(
361
+ agent.capabilities,
362
+ skill.tags // Use tags as capability requirements
363
+ );
364
+
365
+ const record = new BindingRecord(agentId, skillId, { score });
366
+ this.bindings.get(agentId).set(skillId, record);
367
+
368
+ this.emit('skill-bound', { agentId, skillId, score });
369
+
370
+ return record;
371
+ }
372
+
373
+ /**
374
+ * Remove binding
375
+ */
376
+ unbind(agentId, skillId) {
377
+ const agentBindings = this.bindings.get(agentId);
378
+ if (!agentBindings) {
379
+ return false;
380
+ }
381
+
382
+ const removed = agentBindings.delete(skillId);
383
+ if (removed) {
384
+ this.emit('skill-unbound', { agentId, skillId });
385
+ }
386
+
387
+ return removed;
388
+ }
389
+
390
+ /**
391
+ * Get binding
392
+ */
393
+ getBinding(agentId, skillId) {
394
+ return this.bindings.get(agentId)?.get(skillId) || null;
395
+ }
396
+
397
+ /**
398
+ * Get all bindings for an agent
399
+ */
400
+ getAgentBindings(agentId) {
401
+ const bindings = this.bindings.get(agentId);
402
+ return bindings ? Array.from(bindings.values()) : [];
403
+ }
404
+
405
+ /**
406
+ * Find best agent for a skill
407
+ */
408
+ findBestAgentForSkill(skillId, options = {}) {
409
+ const skill = this.skillRegistry?.getSkill(skillId);
410
+ if (!skill) {
411
+ return null;
412
+ }
413
+
414
+ const candidates = [];
415
+
416
+ for (const [agentId, agent] of this.agents) {
417
+ // Check availability
418
+ if (this.agentStatus.get(agentId) !== AgentStatus.AVAILABLE) {
419
+ continue;
420
+ }
421
+
422
+ // Check load
423
+ const load = this.agentLoad.get(agentId) || 0;
424
+ if (load >= agent.maxConcurrentTasks) {
425
+ continue;
426
+ }
427
+
428
+ // Check permissions
429
+ if (skill.permissions && skill.permissions.length > 0) {
430
+ const hasPermission = skill.permissions.every(
431
+ p => agent.permissions.includes(p)
432
+ );
433
+ if (!hasPermission) {
434
+ continue;
435
+ }
436
+ }
437
+
438
+ // Get or calculate binding score
439
+ let binding = this.bindings.get(agentId)?.get(skillId);
440
+ if (!binding) {
441
+ const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
442
+ if (score >= this.options.minMatchScore) {
443
+ binding = new BindingRecord(agentId, skillId, { score });
444
+ }
445
+ }
446
+
447
+ if (binding && binding.score >= this.options.minMatchScore) {
448
+ // Calculate final score with affinity and load
449
+ const loadPenalty = (load / agent.maxConcurrentTasks) * 20;
450
+ const affinityBonus = binding.affinity * this.options.affinityWeight;
451
+ const finalScore = binding.score + affinityBonus - loadPenalty;
452
+
453
+ candidates.push({
454
+ agent,
455
+ binding,
456
+ finalScore,
457
+ load
458
+ });
459
+ }
460
+ }
461
+
462
+ if (candidates.length === 0) {
463
+ return null;
464
+ }
465
+
466
+ // Sort by final score (descending)
467
+ candidates.sort((a, b) => b.finalScore - a.finalScore);
468
+
469
+ return candidates[0];
470
+ }
471
+
472
+ /**
473
+ * Find all capable agents for a skill
474
+ */
475
+ findCapableAgents(skillId) {
476
+ const skill = this.skillRegistry?.getSkill(skillId);
477
+ if (!skill) {
478
+ return [];
479
+ }
480
+
481
+ const capable = [];
482
+
483
+ for (const [agentId, agent] of this.agents) {
484
+ const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
485
+ if (score >= this.options.minMatchScore) {
486
+ capable.push({
487
+ agent,
488
+ score,
489
+ status: this.agentStatus.get(agentId),
490
+ load: this.agentLoad.get(agentId) || 0
491
+ });
492
+ }
493
+ }
494
+
495
+ return capable.sort((a, b) => b.score - a.score);
496
+ }
497
+
498
+ /**
499
+ * Acquire agent for task
500
+ */
501
+ acquireAgent(agentId) {
502
+ const agent = this.agents.get(agentId);
503
+ if (!agent) {
504
+ return false;
505
+ }
506
+
507
+ const load = this.agentLoad.get(agentId) || 0;
508
+ if (load >= agent.maxConcurrentTasks) {
509
+ return false;
510
+ }
511
+
512
+ this.agentLoad.set(agentId, load + 1);
513
+
514
+ if (load + 1 >= agent.maxConcurrentTasks) {
515
+ this.setAgentStatus(agentId, AgentStatus.BUSY);
516
+ }
517
+
518
+ this.emit('agent-acquired', { agentId, currentLoad: load + 1 });
519
+
520
+ return true;
521
+ }
522
+
523
+ /**
524
+ * Release agent from task
525
+ */
526
+ releaseAgent(agentId) {
527
+ const agent = this.agents.get(agentId);
528
+ if (!agent) {
529
+ return false;
530
+ }
531
+
532
+ const load = this.agentLoad.get(agentId) || 0;
533
+ if (load <= 0) {
534
+ return false;
535
+ }
536
+
537
+ this.agentLoad.set(agentId, load - 1);
538
+
539
+ if (this.agentStatus.get(agentId) === AgentStatus.BUSY && load - 1 < agent.maxConcurrentTasks) {
540
+ this.setAgentStatus(agentId, AgentStatus.AVAILABLE);
541
+ }
542
+
543
+ this.emit('agent-released', { agentId, currentLoad: load - 1 });
544
+
545
+ return true;
546
+ }
547
+
548
+ /**
549
+ * Record execution result
550
+ */
551
+ recordExecution(agentId, skillId, success, executionTime) {
552
+ const binding = this.getBinding(agentId, skillId);
553
+ if (binding) {
554
+ binding.updateStats(success, executionTime);
555
+ this.emit('execution-recorded', { agentId, skillId, success, executionTime });
556
+ }
557
+ }
558
+
559
+ /**
560
+ * Get agent load
561
+ */
562
+ getAgentLoad(agentId) {
563
+ return this.agentLoad.get(agentId) || 0;
564
+ }
565
+
566
+ /**
567
+ * Get capability matcher
568
+ */
569
+ getMatcher() {
570
+ return this.matcher;
571
+ }
572
+
573
+ /**
574
+ * Get summary
575
+ */
576
+ getSummary() {
577
+ const statusCounts = {
578
+ [AgentStatus.AVAILABLE]: 0,
579
+ [AgentStatus.BUSY]: 0,
580
+ [AgentStatus.OFFLINE]: 0,
581
+ [AgentStatus.MAINTENANCE]: 0
582
+ };
583
+
584
+ let totalBindings = 0;
585
+ let totalLoad = 0;
586
+
587
+ for (const [agentId, status] of this.agentStatus) {
588
+ statusCounts[status]++;
589
+ totalBindings += (this.bindings.get(agentId)?.size || 0);
590
+ totalLoad += (this.agentLoad.get(agentId) || 0);
591
+ }
592
+
593
+ return {
594
+ totalAgents: this.agents.size,
595
+ statusCounts,
596
+ totalBindings,
597
+ totalLoad,
598
+ averageLoad: this.agents.size > 0 ? totalLoad / this.agents.size : 0
599
+ };
600
+ }
601
+
602
+ /**
603
+ * Clear all
604
+ */
605
+ clear() {
606
+ this.agents.clear();
607
+ this.agentStatus.clear();
608
+ this.agentLoad.clear();
609
+ this.bindings.clear();
610
+ this.emit('cleared');
611
+ }
612
+
613
+ // Private methods
614
+
615
+ _autoBindAgent(agentId) {
616
+ const agent = this.agents.get(agentId);
617
+ if (!agent || !this.skillRegistry) return;
618
+
619
+ const skills = this.skillRegistry.getAllSkills();
620
+ for (const skill of skills) {
621
+ try {
622
+ const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
623
+ if (score >= this.options.minMatchScore) {
624
+ this.bind(agentId, skill.id, { score });
625
+ }
626
+ } catch (e) {
627
+ // Ignore binding errors during auto-bind
628
+ }
629
+ }
630
+ }
631
+
632
+ _autoBindSkill(skillId) {
633
+ const skill = this.skillRegistry?.getSkill(skillId);
634
+ if (!skill) return;
635
+
636
+ for (const [agentId, agent] of this.agents) {
637
+ try {
638
+ const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
639
+ if (score >= this.options.minMatchScore) {
640
+ this.bind(agentId, skillId, { score });
641
+ }
642
+ } catch (e) {
643
+ // Ignore binding errors during auto-bind
644
+ }
645
+ }
646
+ }
647
+ }
648
+
649
+ module.exports = {
650
+ AgentSkillBinding,
651
+ AgentDefinition,
652
+ BindingRecord,
653
+ CapabilityMatcher,
654
+ AgentStatus
655
+ };