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,650 @@
1
+ /**
2
+ * Skill Registry - Centralized skill registration and discovery
3
+ * Sprint 3.1: Skill System Architecture
4
+ *
5
+ * Inspired by OpenAI Agents SDK skill management patterns
6
+ */
7
+
8
+ const EventEmitter = require('events');
9
+
10
+ /**
11
+ * Skill metadata schema
12
+ */
13
+ class SkillMetadata {
14
+ constructor(options = {}) {
15
+ this.id = options.id || '';
16
+ this.name = options.name || '';
17
+ this.description = options.description || '';
18
+ this.version = options.version || '1.0.0';
19
+ this.category = options.category || 'general';
20
+ this.tags = options.tags || [];
21
+ this.author = options.author || '';
22
+ this.inputs = options.inputs || [];
23
+ this.outputs = options.outputs || [];
24
+ this.dependencies = options.dependencies || [];
25
+ this.timeout = options.timeout || 30000;
26
+ this.retryPolicy = options.retryPolicy || { maxRetries: 3, backoffMs: 1000 };
27
+ this.priority = options.priority || 'P2';
28
+ this.permissions = options.permissions || [];
29
+ this.createdAt = options.createdAt || new Date().toISOString();
30
+ this.updatedAt = options.updatedAt || new Date().toISOString();
31
+ }
32
+
33
+ validate() {
34
+ const errors = [];
35
+
36
+ if (!this.id) {
37
+ errors.push('Skill ID is required');
38
+ }
39
+
40
+ if (!this.name) {
41
+ errors.push('Skill name is required');
42
+ }
43
+
44
+ if (!/^[a-z0-9-]+$/.test(this.id)) {
45
+ errors.push('Skill ID must be lowercase alphanumeric with hyphens');
46
+ }
47
+
48
+ if (!['P0', 'P1', 'P2', 'P3'].includes(this.priority)) {
49
+ errors.push('Priority must be P0, P1, P2, or P3');
50
+ }
51
+
52
+ // Validate inputs
53
+ for (const input of this.inputs) {
54
+ if (!input.name) {
55
+ errors.push('Input name is required');
56
+ }
57
+ if (!input.type) {
58
+ errors.push(`Input ${input.name || 'unknown'} must have a type`);
59
+ }
60
+ }
61
+
62
+ // Validate outputs
63
+ for (const output of this.outputs) {
64
+ if (!output.name) {
65
+ errors.push('Output name is required');
66
+ }
67
+ }
68
+
69
+ return {
70
+ valid: errors.length === 0,
71
+ errors
72
+ };
73
+ }
74
+
75
+ toJSON() {
76
+ return {
77
+ id: this.id,
78
+ name: this.name,
79
+ description: this.description,
80
+ version: this.version,
81
+ category: this.category,
82
+ tags: this.tags,
83
+ author: this.author,
84
+ inputs: this.inputs,
85
+ outputs: this.outputs,
86
+ dependencies: this.dependencies,
87
+ timeout: this.timeout,
88
+ retryPolicy: this.retryPolicy,
89
+ priority: this.priority,
90
+ permissions: this.permissions,
91
+ createdAt: this.createdAt,
92
+ updatedAt: this.updatedAt
93
+ };
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Skill categories
99
+ */
100
+ const SkillCategory = {
101
+ ANALYSIS: 'analysis',
102
+ DESIGN: 'design',
103
+ IMPLEMENTATION: 'implementation',
104
+ TESTING: 'testing',
105
+ DOCUMENTATION: 'documentation',
106
+ ORCHESTRATION: 'orchestration',
107
+ VALIDATION: 'validation',
108
+ INTEGRATION: 'integration',
109
+ MONITORING: 'monitoring',
110
+ GENERAL: 'general'
111
+ };
112
+
113
+ /**
114
+ * Priority levels for skill execution
115
+ */
116
+ const SkillPriority = {
117
+ P0: 'P0', // Critical - blocks all other work
118
+ P1: 'P1', // High - should run soon
119
+ P2: 'P2', // Medium - normal priority
120
+ P3: 'P3' // Low - background/optional
121
+ };
122
+
123
+ /**
124
+ * Skill health status
125
+ */
126
+ const SkillHealth = {
127
+ HEALTHY: 'healthy',
128
+ DEGRADED: 'degraded',
129
+ UNHEALTHY: 'unhealthy',
130
+ UNKNOWN: 'unknown'
131
+ };
132
+
133
+ /**
134
+ * Main Skill Registry class
135
+ */
136
+ class SkillRegistry extends EventEmitter {
137
+ constructor(options = {}) {
138
+ super();
139
+ this.skills = new Map();
140
+ this.categoryIndex = new Map();
141
+ this.tagIndex = new Map();
142
+ this.healthStatus = new Map();
143
+ this.executionStats = new Map();
144
+ this.validators = [];
145
+ this.hooks = {
146
+ beforeRegister: [],
147
+ afterRegister: [],
148
+ beforeUnregister: [],
149
+ afterUnregister: []
150
+ };
151
+
152
+ // Options
153
+ this.options = {
154
+ enableHealthMonitoring: options.enableHealthMonitoring !== false,
155
+ healthCheckInterval: options.healthCheckInterval || 60000,
156
+ maxSkills: options.maxSkills || 1000,
157
+ enableStats: options.enableStats !== false
158
+ };
159
+
160
+ // Start health monitoring if enabled
161
+ if (this.options.enableHealthMonitoring) {
162
+ this._startHealthMonitoring();
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Register a new skill
168
+ */
169
+ registerSkill(skillDef, handler = null) {
170
+ const metadata = skillDef instanceof SkillMetadata
171
+ ? skillDef
172
+ : new SkillMetadata(skillDef);
173
+
174
+ // Validate metadata
175
+ const validation = metadata.validate();
176
+ if (!validation.valid) {
177
+ throw new Error(`Invalid skill metadata: ${validation.errors.join(', ')}`);
178
+ }
179
+
180
+ // Check max skills limit
181
+ if (this.skills.size >= this.options.maxSkills) {
182
+ throw new Error(`Maximum skill limit (${this.options.maxSkills}) reached`);
183
+ }
184
+
185
+ // Run custom validators
186
+ for (const validator of this.validators) {
187
+ const result = validator(metadata);
188
+ if (!result.valid) {
189
+ throw new Error(`Skill validation failed: ${result.error}`);
190
+ }
191
+ }
192
+
193
+ // Run beforeRegister hooks
194
+ for (const hook of this.hooks.beforeRegister) {
195
+ hook(metadata);
196
+ }
197
+
198
+ // Check for duplicate
199
+ if (this.skills.has(metadata.id)) {
200
+ throw new Error(`Skill with ID '${metadata.id}' already exists`);
201
+ }
202
+
203
+ // Validate dependencies exist
204
+ for (const dep of metadata.dependencies) {
205
+ if (!this.skills.has(dep)) {
206
+ throw new Error(`Dependency '${dep}' not found for skill '${metadata.id}'`);
207
+ }
208
+ }
209
+
210
+ // Store skill
211
+ const skillEntry = {
212
+ metadata,
213
+ handler,
214
+ registeredAt: new Date().toISOString()
215
+ };
216
+ this.skills.set(metadata.id, skillEntry);
217
+
218
+ // Update indexes
219
+ this._addToIndex(this.categoryIndex, metadata.category, metadata.id);
220
+ for (const tag of metadata.tags) {
221
+ this._addToIndex(this.tagIndex, tag, metadata.id);
222
+ }
223
+
224
+ // Initialize health and stats
225
+ this.healthStatus.set(metadata.id, SkillHealth.HEALTHY);
226
+ if (this.options.enableStats) {
227
+ this.executionStats.set(metadata.id, {
228
+ totalExecutions: 0,
229
+ successCount: 0,
230
+ failureCount: 0,
231
+ averageExecutionTime: 0,
232
+ lastExecutedAt: null
233
+ });
234
+ }
235
+
236
+ // Run afterRegister hooks
237
+ for (const hook of this.hooks.afterRegister) {
238
+ hook(metadata);
239
+ }
240
+
241
+ this.emit('skill-registered', { skillId: metadata.id, metadata });
242
+
243
+ return metadata;
244
+ }
245
+
246
+ /**
247
+ * Unregister a skill
248
+ */
249
+ unregisterSkill(skillId) {
250
+ const skillEntry = this.skills.get(skillId);
251
+ if (!skillEntry) {
252
+ return false;
253
+ }
254
+
255
+ // Check if other skills depend on this one
256
+ const dependents = this._findDependents(skillId);
257
+ if (dependents.length > 0) {
258
+ throw new Error(
259
+ `Cannot unregister skill '${skillId}': required by ${dependents.join(', ')}`
260
+ );
261
+ }
262
+
263
+ // Run beforeUnregister hooks
264
+ for (const hook of this.hooks.beforeUnregister) {
265
+ hook(skillEntry.metadata);
266
+ }
267
+
268
+ // Remove from indexes
269
+ const metadata = skillEntry.metadata;
270
+ this._removeFromIndex(this.categoryIndex, metadata.category, skillId);
271
+ for (const tag of metadata.tags) {
272
+ this._removeFromIndex(this.tagIndex, tag, skillId);
273
+ }
274
+
275
+ // Remove skill
276
+ this.skills.delete(skillId);
277
+ this.healthStatus.delete(skillId);
278
+ this.executionStats.delete(skillId);
279
+
280
+ // Run afterUnregister hooks
281
+ for (const hook of this.hooks.afterUnregister) {
282
+ hook(metadata);
283
+ }
284
+
285
+ this.emit('skill-unregistered', { skillId, metadata });
286
+
287
+ return true;
288
+ }
289
+
290
+ /**
291
+ * Get a skill by ID
292
+ */
293
+ getSkill(skillId) {
294
+ const entry = this.skills.get(skillId);
295
+ return entry ? entry.metadata : null;
296
+ }
297
+
298
+ /**
299
+ * Get skill with handler
300
+ */
301
+ getSkillEntry(skillId) {
302
+ return this.skills.get(skillId) || null;
303
+ }
304
+
305
+ /**
306
+ * Check if skill exists
307
+ */
308
+ hasSkill(skillId) {
309
+ return this.skills.has(skillId);
310
+ }
311
+
312
+ /**
313
+ * Get all skills
314
+ */
315
+ getAllSkills() {
316
+ return Array.from(this.skills.values()).map(entry => entry.metadata);
317
+ }
318
+
319
+ /**
320
+ * Find skills by category
321
+ */
322
+ findByCategory(category) {
323
+ const skillIds = this.categoryIndex.get(category) || new Set();
324
+ return Array.from(skillIds).map(id => this.getSkill(id)).filter(Boolean);
325
+ }
326
+
327
+ /**
328
+ * Find skills by tags (OR matching)
329
+ */
330
+ findByTags(tags, matchAll = false) {
331
+ if (matchAll) {
332
+ // AND matching - skill must have all tags
333
+ return this.getAllSkills().filter(skill =>
334
+ tags.every(tag => skill.tags.includes(tag))
335
+ );
336
+ } else {
337
+ // OR matching - skill must have at least one tag
338
+ const matchedIds = new Set();
339
+ for (const tag of tags) {
340
+ const skillIds = this.tagIndex.get(tag) || new Set();
341
+ for (const id of skillIds) {
342
+ matchedIds.add(id);
343
+ }
344
+ }
345
+ return Array.from(matchedIds).map(id => this.getSkill(id)).filter(Boolean);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Search skills by query
351
+ */
352
+ search(query) {
353
+ const lowerQuery = query.toLowerCase();
354
+ return this.getAllSkills().filter(skill =>
355
+ skill.id.toLowerCase().includes(lowerQuery) ||
356
+ skill.name.toLowerCase().includes(lowerQuery) ||
357
+ skill.description.toLowerCase().includes(lowerQuery) ||
358
+ skill.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
359
+ );
360
+ }
361
+
362
+ /**
363
+ * Get skills by priority
364
+ */
365
+ findByPriority(priority) {
366
+ return this.getAllSkills().filter(skill => skill.priority === priority);
367
+ }
368
+
369
+ /**
370
+ * Get skill dependencies (resolved order)
371
+ */
372
+ resolveDependencies(skillId, visited = new Set()) {
373
+ const skill = this.getSkill(skillId);
374
+ if (!skill) {
375
+ throw new Error(`Skill '${skillId}' not found`);
376
+ }
377
+
378
+ // Circular dependency check
379
+ if (visited.has(skillId)) {
380
+ throw new Error(`Circular dependency detected: ${skillId}`);
381
+ }
382
+ visited.add(skillId);
383
+
384
+ const resolved = [];
385
+ for (const depId of skill.dependencies) {
386
+ const depResolved = this.resolveDependencies(depId, new Set(visited));
387
+ for (const r of depResolved) {
388
+ if (!resolved.includes(r)) {
389
+ resolved.push(r);
390
+ }
391
+ }
392
+ }
393
+ resolved.push(skillId);
394
+
395
+ return resolved;
396
+ }
397
+
398
+ /**
399
+ * Update skill health status
400
+ */
401
+ updateHealth(skillId, status, reason = null) {
402
+ if (!this.skills.has(skillId)) {
403
+ return false;
404
+ }
405
+
406
+ const previousStatus = this.healthStatus.get(skillId);
407
+ this.healthStatus.set(skillId, status);
408
+
409
+ if (previousStatus !== status) {
410
+ this.emit('health-changed', {
411
+ skillId,
412
+ previousStatus,
413
+ newStatus: status,
414
+ reason
415
+ });
416
+ }
417
+
418
+ return true;
419
+ }
420
+
421
+ /**
422
+ * Get skill health
423
+ */
424
+ getHealth(skillId) {
425
+ return this.healthStatus.get(skillId) || SkillHealth.UNKNOWN;
426
+ }
427
+
428
+ /**
429
+ * Get all healthy skills
430
+ */
431
+ getHealthySkills() {
432
+ return this.getAllSkills().filter(
433
+ skill => this.getHealth(skill.id) === SkillHealth.HEALTHY
434
+ );
435
+ }
436
+
437
+ /**
438
+ * Record execution stats
439
+ */
440
+ recordExecution(skillId, success, executionTime) {
441
+ if (!this.options.enableStats) return;
442
+
443
+ const stats = this.executionStats.get(skillId);
444
+ if (!stats) return;
445
+
446
+ stats.totalExecutions++;
447
+ if (success) {
448
+ stats.successCount++;
449
+ } else {
450
+ stats.failureCount++;
451
+ }
452
+
453
+ // Update average execution time
454
+ stats.averageExecutionTime = (
455
+ (stats.averageExecutionTime * (stats.totalExecutions - 1) + executionTime) /
456
+ stats.totalExecutions
457
+ );
458
+
459
+ stats.lastExecutedAt = new Date().toISOString();
460
+
461
+ this.emit('execution-recorded', { skillId, success, executionTime, stats });
462
+ }
463
+
464
+ /**
465
+ * Get execution stats
466
+ */
467
+ getStats(skillId) {
468
+ return this.executionStats.get(skillId) || null;
469
+ }
470
+
471
+ /**
472
+ * Get all stats
473
+ */
474
+ getAllStats() {
475
+ return Object.fromEntries(this.executionStats);
476
+ }
477
+
478
+ /**
479
+ * Add custom validator
480
+ */
481
+ addValidator(validator) {
482
+ this.validators.push(validator);
483
+ }
484
+
485
+ /**
486
+ * Add hook
487
+ */
488
+ addHook(event, handler) {
489
+ if (this.hooks[event]) {
490
+ this.hooks[event].push(handler);
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Get registry summary
496
+ */
497
+ getSummary() {
498
+ const categories = {};
499
+ for (const [category, ids] of this.categoryIndex) {
500
+ categories[category] = ids.size;
501
+ }
502
+
503
+ const healthCounts = {
504
+ [SkillHealth.HEALTHY]: 0,
505
+ [SkillHealth.DEGRADED]: 0,
506
+ [SkillHealth.UNHEALTHY]: 0,
507
+ [SkillHealth.UNKNOWN]: 0
508
+ };
509
+ for (const status of this.healthStatus.values()) {
510
+ healthCounts[status]++;
511
+ }
512
+
513
+ return {
514
+ totalSkills: this.skills.size,
515
+ categories,
516
+ healthStatus: healthCounts,
517
+ tags: Array.from(this.tagIndex.keys())
518
+ };
519
+ }
520
+
521
+ /**
522
+ * Export registry to JSON
523
+ */
524
+ exportToJSON() {
525
+ const skills = this.getAllSkills().map(skill => skill.toJSON());
526
+ return {
527
+ version: '1.0.0',
528
+ exportedAt: new Date().toISOString(),
529
+ skills,
530
+ summary: this.getSummary()
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Import skills from JSON
536
+ */
537
+ importFromJSON(data) {
538
+ if (!data.skills || !Array.isArray(data.skills)) {
539
+ throw new Error('Invalid import data: skills array required');
540
+ }
541
+
542
+ const imported = [];
543
+ const errors = [];
544
+
545
+ for (const skillDef of data.skills) {
546
+ try {
547
+ const metadata = this.registerSkill(skillDef);
548
+ imported.push(metadata.id);
549
+ } catch (error) {
550
+ errors.push({ skillId: skillDef.id, error: error.message });
551
+ }
552
+ }
553
+
554
+ return { imported, errors };
555
+ }
556
+
557
+ /**
558
+ * Clear all skills
559
+ */
560
+ clear() {
561
+ this.skills.clear();
562
+ this.categoryIndex.clear();
563
+ this.tagIndex.clear();
564
+ this.healthStatus.clear();
565
+ this.executionStats.clear();
566
+ this.emit('registry-cleared');
567
+ }
568
+
569
+ // Private methods
570
+
571
+ _addToIndex(index, key, value) {
572
+ if (!index.has(key)) {
573
+ index.set(key, new Set());
574
+ }
575
+ index.get(key).add(value);
576
+ }
577
+
578
+ _removeFromIndex(index, key, value) {
579
+ const set = index.get(key);
580
+ if (set) {
581
+ set.delete(value);
582
+ if (set.size === 0) {
583
+ index.delete(key);
584
+ }
585
+ }
586
+ }
587
+
588
+ _findDependents(skillId) {
589
+ const dependents = [];
590
+ for (const [id, entry] of this.skills) {
591
+ if (entry.metadata.dependencies.includes(skillId)) {
592
+ dependents.push(id);
593
+ }
594
+ }
595
+ return dependents;
596
+ }
597
+
598
+ _startHealthMonitoring() {
599
+ this._healthCheckInterval = setInterval(() => {
600
+ this._performHealthCheck();
601
+ }, this.options.healthCheckInterval);
602
+ }
603
+
604
+ _performHealthCheck() {
605
+ for (const [skillId, entry] of this.skills) {
606
+ const stats = this.executionStats.get(skillId);
607
+ if (!stats || stats.totalExecutions === 0) {
608
+ continue;
609
+ }
610
+
611
+ const failureRate = stats.failureCount / stats.totalExecutions;
612
+
613
+ let newStatus = SkillHealth.HEALTHY;
614
+ if (failureRate > 0.5) {
615
+ newStatus = SkillHealth.UNHEALTHY;
616
+ } else if (failureRate > 0.2) {
617
+ newStatus = SkillHealth.DEGRADED;
618
+ }
619
+
620
+ this.updateHealth(skillId, newStatus, `Failure rate: ${(failureRate * 100).toFixed(1)}%`);
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Stop health monitoring
626
+ */
627
+ stopHealthMonitoring() {
628
+ if (this._healthCheckInterval) {
629
+ clearInterval(this._healthCheckInterval);
630
+ this._healthCheckInterval = null;
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Destroy registry
636
+ */
637
+ destroy() {
638
+ this.stopHealthMonitoring();
639
+ this.clear();
640
+ this.removeAllListeners();
641
+ }
642
+ }
643
+
644
+ module.exports = {
645
+ SkillRegistry,
646
+ SkillMetadata,
647
+ SkillCategory,
648
+ SkillPriority,
649
+ SkillHealth
650
+ };