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,665 @@
1
+ /**
2
+ * Skill Executor - Execute skills with priority, retry, and guardrails
3
+ * Sprint 3.2: Skill System Architecture
4
+ *
5
+ * Features:
6
+ * - P-label priority execution (P0-P3)
7
+ * - Parallel and sequential execution
8
+ * - Input/output validation
9
+ * - Retry with exponential backoff
10
+ * - Execution hooks
11
+ * - Guardrail integration
12
+ */
13
+
14
+ const EventEmitter = require('events');
15
+
16
+ /**
17
+ * Execution status
18
+ */
19
+ const ExecutionStatus = {
20
+ PENDING: 'pending',
21
+ RUNNING: 'running',
22
+ COMPLETED: 'completed',
23
+ FAILED: 'failed',
24
+ CANCELLED: 'cancelled',
25
+ TIMEOUT: 'timeout',
26
+ SKIPPED: 'skipped'
27
+ };
28
+
29
+ /**
30
+ * Execution result
31
+ */
32
+ class ExecutionResult {
33
+ constructor(options = {}) {
34
+ this.skillId = options.skillId || '';
35
+ this.status = options.status || ExecutionStatus.PENDING;
36
+ this.output = options.output || null;
37
+ this.error = options.error || null;
38
+ this.startTime = options.startTime || null;
39
+ this.endTime = options.endTime || null;
40
+ this.duration = options.duration || 0;
41
+ this.attempts = options.attempts || 0;
42
+ this.metadata = options.metadata || {};
43
+ }
44
+
45
+ get success() {
46
+ return this.status === ExecutionStatus.COMPLETED;
47
+ }
48
+
49
+ toJSON() {
50
+ return {
51
+ skillId: this.skillId,
52
+ status: this.status,
53
+ success: this.success,
54
+ output: this.output,
55
+ error: this.error,
56
+ duration: this.duration,
57
+ attempts: this.attempts,
58
+ metadata: this.metadata
59
+ };
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Execution context
65
+ */
66
+ class ExecutionContext {
67
+ constructor(options = {}) {
68
+ this.executionId = options.executionId || this._generateId();
69
+ this.skillId = options.skillId || '';
70
+ this.input = options.input || {};
71
+ this.variables = new Map(Object.entries(options.variables || {}));
72
+ this.parentContext = options.parentContext || null;
73
+ this.startTime = null;
74
+ this.metadata = options.metadata || {};
75
+ }
76
+
77
+ _generateId() {
78
+ return `exec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
79
+ }
80
+
81
+ getVariable(name) {
82
+ if (this.variables.has(name)) {
83
+ return this.variables.get(name);
84
+ }
85
+ if (this.parentContext) {
86
+ return this.parentContext.getVariable(name);
87
+ }
88
+ return null;
89
+ }
90
+
91
+ setVariable(name, value) {
92
+ this.variables.set(name, value);
93
+ }
94
+
95
+ createChild(options = {}) {
96
+ return new ExecutionContext({
97
+ ...options,
98
+ parentContext: this,
99
+ variables: options.variables || {}
100
+ });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Input/Output validator
106
+ */
107
+ class IOValidator {
108
+ constructor() {
109
+ this.typeValidators = {
110
+ string: (v) => typeof v === 'string',
111
+ number: (v) => typeof v === 'number' && !isNaN(v),
112
+ boolean: (v) => typeof v === 'boolean',
113
+ array: (v) => Array.isArray(v),
114
+ object: (v) => typeof v === 'object' && v !== null && !Array.isArray(v),
115
+ any: () => true
116
+ };
117
+ }
118
+
119
+ validateInput(input, schema) {
120
+ const errors = [];
121
+
122
+ for (const field of schema) {
123
+ const value = input[field.name];
124
+
125
+ // Required check
126
+ if (field.required && (value === undefined || value === null)) {
127
+ errors.push(`Missing required input: ${field.name}`);
128
+ continue;
129
+ }
130
+
131
+ // Type check
132
+ if (value !== undefined && value !== null && field.type) {
133
+ const validator = this.typeValidators[field.type];
134
+ if (validator && !validator(value)) {
135
+ errors.push(`Invalid type for ${field.name}: expected ${field.type}`);
136
+ }
137
+ }
138
+
139
+ // Custom validation
140
+ if (field.validate && value !== undefined) {
141
+ try {
142
+ const result = field.validate(value);
143
+ if (result !== true && typeof result === 'string') {
144
+ errors.push(result);
145
+ }
146
+ } catch (e) {
147
+ errors.push(`Validation error for ${field.name}: ${e.message}`);
148
+ }
149
+ }
150
+ }
151
+
152
+ return {
153
+ valid: errors.length === 0,
154
+ errors
155
+ };
156
+ }
157
+
158
+ validateOutput(output, schema) {
159
+ const errors = [];
160
+
161
+ for (const field of schema) {
162
+ const value = output?.[field.name];
163
+
164
+ if (field.required && (value === undefined || value === null)) {
165
+ errors.push(`Missing required output: ${field.name}`);
166
+ }
167
+
168
+ if (value !== undefined && value !== null && field.type) {
169
+ const validator = this.typeValidators[field.type];
170
+ if (validator && !validator(value)) {
171
+ errors.push(`Invalid output type for ${field.name}: expected ${field.type}`);
172
+ }
173
+ }
174
+ }
175
+
176
+ return {
177
+ valid: errors.length === 0,
178
+ errors
179
+ };
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Main Skill Executor class
185
+ */
186
+ class SkillExecutor extends EventEmitter {
187
+ constructor(registry, options = {}) {
188
+ super();
189
+ this.registry = registry;
190
+ this.validator = new IOValidator();
191
+ this.guardrails = options.guardrails || [];
192
+ this.activeExecutions = new Map();
193
+ this.executionHistory = [];
194
+ this.maxHistorySize = options.maxHistorySize || 1000;
195
+
196
+ // Hooks
197
+ this.hooks = {
198
+ beforeExecute: options.beforeExecute || [],
199
+ afterExecute: options.afterExecute || [],
200
+ onError: options.onError || []
201
+ };
202
+
203
+ // Options
204
+ this.options = {
205
+ defaultTimeout: options.defaultTimeout || 30000,
206
+ maxConcurrent: options.maxConcurrent || 10,
207
+ enableMetrics: options.enableMetrics !== false
208
+ };
209
+
210
+ this._concurrentCount = 0;
211
+ }
212
+
213
+ /**
214
+ * Execute a single skill
215
+ */
216
+ async execute(skillId, input = {}, options = {}) {
217
+ const skillEntry = this.registry.getSkillEntry(skillId);
218
+ if (!skillEntry) {
219
+ throw new Error(`Skill '${skillId}' not found`);
220
+ }
221
+
222
+ const { metadata, handler } = skillEntry;
223
+
224
+ if (!handler) {
225
+ throw new Error(`Skill '${skillId}' has no handler`);
226
+ }
227
+
228
+ // Create execution context
229
+ const context = new ExecutionContext({
230
+ skillId,
231
+ input,
232
+ variables: options.variables,
233
+ metadata: options.metadata
234
+ });
235
+
236
+ // Create result
237
+ const result = new ExecutionResult({
238
+ skillId,
239
+ status: ExecutionStatus.PENDING
240
+ });
241
+
242
+ // Track execution
243
+ this.activeExecutions.set(context.executionId, { context, result, cancelled: false });
244
+
245
+ try {
246
+ // Check concurrency limit
247
+ if (this._concurrentCount >= this.options.maxConcurrent) {
248
+ await this._waitForSlot();
249
+ }
250
+ this._concurrentCount++;
251
+
252
+ // Validate input
253
+ const inputValidation = this.validator.validateInput(input, metadata.inputs);
254
+ if (!inputValidation.valid) {
255
+ throw new Error(`Input validation failed: ${inputValidation.errors.join(', ')}`);
256
+ }
257
+
258
+ // Run guardrails (pre-execution)
259
+ await this._runGuardrails('pre', { skillId, input, context });
260
+
261
+ // Run beforeExecute hooks
262
+ for (const hook of this.hooks.beforeExecute) {
263
+ await hook(context, metadata);
264
+ }
265
+
266
+ // Start execution
267
+ result.status = ExecutionStatus.RUNNING;
268
+ result.startTime = Date.now();
269
+ context.startTime = result.startTime;
270
+
271
+ this.emit('execution-started', { executionId: context.executionId, skillId });
272
+
273
+ // Execute with retry
274
+ const output = await this._executeWithRetry(
275
+ handler,
276
+ context,
277
+ metadata.retryPolicy,
278
+ options.timeout || metadata.timeout || this.options.defaultTimeout,
279
+ result
280
+ );
281
+
282
+ // Validate output
283
+ const outputValidation = this.validator.validateOutput(output, metadata.outputs);
284
+ if (!outputValidation.valid) {
285
+ throw new Error(`Output validation failed: ${outputValidation.errors.join(', ')}`);
286
+ }
287
+
288
+ // Run guardrails (post-execution)
289
+ await this._runGuardrails('post', { skillId, input, output, context });
290
+
291
+ // Complete
292
+ result.status = ExecutionStatus.COMPLETED;
293
+ result.output = output;
294
+ result.endTime = Date.now();
295
+ result.duration = result.endTime - result.startTime;
296
+
297
+ // Run afterExecute hooks
298
+ for (const hook of this.hooks.afterExecute) {
299
+ await hook(context, result);
300
+ }
301
+
302
+ // Record stats
303
+ if (this.options.enableMetrics) {
304
+ this.registry.recordExecution(skillId, true, result.duration);
305
+ }
306
+
307
+ this.emit('execution-completed', {
308
+ executionId: context.executionId,
309
+ skillId,
310
+ duration: result.duration
311
+ });
312
+
313
+ } catch (error) {
314
+ result.status = this.activeExecutions.get(context.executionId)?.cancelled
315
+ ? ExecutionStatus.CANCELLED
316
+ : ExecutionStatus.FAILED;
317
+ result.error = error.message;
318
+ result.endTime = Date.now();
319
+ result.duration = result.startTime ? result.endTime - result.startTime : 0;
320
+
321
+ // Run onError hooks
322
+ for (const hook of this.hooks.onError) {
323
+ await hook(error, context, result);
324
+ }
325
+
326
+ // Record stats
327
+ if (this.options.enableMetrics) {
328
+ this.registry.recordExecution(skillId, false, result.duration);
329
+ }
330
+
331
+ this.emit('execution-failed', {
332
+ executionId: context.executionId,
333
+ skillId,
334
+ error: error.message
335
+ });
336
+
337
+ } finally {
338
+ this._concurrentCount--;
339
+ this.activeExecutions.delete(context.executionId);
340
+ this._addToHistory(result);
341
+ }
342
+
343
+ return result;
344
+ }
345
+
346
+ /**
347
+ * Execute multiple skills in parallel
348
+ */
349
+ async executeParallel(tasks, options = {}) {
350
+ // Sort by priority
351
+ const sortedTasks = [...tasks].sort((a, b) => {
352
+ const priorityOrder = { P0: 0, P1: 1, P2: 2, P3: 3 };
353
+ return (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);
354
+ });
355
+
356
+ // Group by priority
357
+ const priorityGroups = new Map();
358
+ for (const task of sortedTasks) {
359
+ const priority = task.priority || 'P2';
360
+ if (!priorityGroups.has(priority)) {
361
+ priorityGroups.set(priority, []);
362
+ }
363
+ priorityGroups.get(priority).push(task);
364
+ }
365
+
366
+ const results = new Map();
367
+
368
+ // Execute P0 first (blocking)
369
+ if (priorityGroups.has('P0')) {
370
+ for (const task of priorityGroups.get('P0')) {
371
+ const result = await this.execute(task.skillId, task.input, task.options);
372
+ results.set(task.skillId, result);
373
+
374
+ // P0 failure stops everything
375
+ if (!result.success && options.failFast !== false) {
376
+ return { results: Object.fromEntries(results), partial: true };
377
+ }
378
+ }
379
+ }
380
+
381
+ // Execute P1-P3 in parallel within each priority level
382
+ for (const priority of ['P1', 'P2', 'P3']) {
383
+ if (priorityGroups.has(priority)) {
384
+ const groupTasks = priorityGroups.get(priority);
385
+ const groupResults = await Promise.allSettled(
386
+ groupTasks.map(task =>
387
+ this.execute(task.skillId, task.input, task.options)
388
+ )
389
+ );
390
+
391
+ groupTasks.forEach((task, index) => {
392
+ const settled = groupResults[index];
393
+ if (settled.status === 'fulfilled') {
394
+ results.set(task.skillId, settled.value);
395
+ } else {
396
+ results.set(task.skillId, new ExecutionResult({
397
+ skillId: task.skillId,
398
+ status: ExecutionStatus.FAILED,
399
+ error: settled.reason?.message || 'Unknown error'
400
+ }));
401
+ }
402
+ });
403
+ }
404
+ }
405
+
406
+ return {
407
+ results: Object.fromEntries(results),
408
+ partial: false,
409
+ summary: this._summarizeResults(results)
410
+ };
411
+ }
412
+
413
+ /**
414
+ * Execute skills sequentially
415
+ */
416
+ async executeSequential(tasks, options = {}) {
417
+ const results = [];
418
+
419
+ for (const task of tasks) {
420
+ const result = await this.execute(task.skillId, task.input, task.options);
421
+ results.push(result);
422
+
423
+ if (!result.success && options.stopOnError !== false) {
424
+ break;
425
+ }
426
+ }
427
+
428
+ return {
429
+ results,
430
+ completed: results.length === tasks.length,
431
+ summary: this._summarizeResults(new Map(results.map(r => [r.skillId, r])))
432
+ };
433
+ }
434
+
435
+ /**
436
+ * Execute with dependency resolution
437
+ */
438
+ async executeWithDependencies(skillId, input = {}, options = {}) {
439
+ const order = this.registry.resolveDependencies(skillId);
440
+ const results = new Map();
441
+
442
+ for (const depSkillId of order) {
443
+ // Use output from dependencies as input
444
+ const depInput = depSkillId === skillId
445
+ ? input
446
+ : this._mergeInputFromResults(results, depSkillId);
447
+
448
+ const result = await this.execute(depSkillId, depInput, options);
449
+ results.set(depSkillId, result);
450
+
451
+ if (!result.success) {
452
+ return {
453
+ results: Object.fromEntries(results),
454
+ failed: true,
455
+ failedAt: depSkillId
456
+ };
457
+ }
458
+ }
459
+
460
+ return {
461
+ results: Object.fromEntries(results),
462
+ failed: false,
463
+ finalResult: results.get(skillId)
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Cancel an execution
469
+ */
470
+ cancel(executionId) {
471
+ const execution = this.activeExecutions.get(executionId);
472
+ if (execution) {
473
+ execution.cancelled = true;
474
+ this.emit('execution-cancelled', { executionId });
475
+ return true;
476
+ }
477
+ return false;
478
+ }
479
+
480
+ /**
481
+ * Cancel all active executions
482
+ */
483
+ cancelAll() {
484
+ for (const [executionId] of this.activeExecutions) {
485
+ this.cancel(executionId);
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Get active execution count
491
+ */
492
+ getActiveCount() {
493
+ return this.activeExecutions.size;
494
+ }
495
+
496
+ /**
497
+ * Get execution history
498
+ */
499
+ getHistory(limit = 100) {
500
+ return this.executionHistory.slice(-limit);
501
+ }
502
+
503
+ /**
504
+ * Add guardrail
505
+ */
506
+ addGuardrail(guardrail) {
507
+ this.guardrails.push(guardrail);
508
+ }
509
+
510
+ /**
511
+ * Add hook
512
+ */
513
+ addHook(event, handler) {
514
+ if (this.hooks[event]) {
515
+ this.hooks[event].push(handler);
516
+ }
517
+ }
518
+
519
+ // Private methods
520
+
521
+ async _executeWithRetry(handler, context, retryPolicy, timeout, result) {
522
+ const maxRetries = retryPolicy?.maxRetries || 0;
523
+ const backoffMs = retryPolicy?.backoffMs || 1000;
524
+ const backoffMultiplier = retryPolicy?.backoffMultiplier || 2;
525
+
526
+ let lastError;
527
+ let currentBackoff = backoffMs;
528
+
529
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
530
+ result.attempts = attempt + 1;
531
+
532
+ // Check if cancelled
533
+ const execution = this.activeExecutions.get(context.executionId);
534
+ if (execution?.cancelled) {
535
+ throw new Error('Execution cancelled');
536
+ }
537
+
538
+ try {
539
+ // Execute with timeout
540
+ const output = await this._executeWithTimeout(handler, context, timeout);
541
+ return output;
542
+ } catch (error) {
543
+ lastError = error;
544
+
545
+ // Check if retryable
546
+ if (!this._isRetryable(error)) {
547
+ throw error;
548
+ }
549
+
550
+ if (attempt < maxRetries) {
551
+ this.emit('execution-retry', {
552
+ executionId: context.executionId,
553
+ skillId: context.skillId,
554
+ attempt: attempt + 1,
555
+ maxRetries,
556
+ nextRetryMs: currentBackoff
557
+ });
558
+
559
+ await this._sleep(currentBackoff);
560
+ currentBackoff *= backoffMultiplier;
561
+ }
562
+ }
563
+ }
564
+
565
+ throw lastError;
566
+ }
567
+
568
+ async _executeWithTimeout(handler, context, timeout) {
569
+ return new Promise((resolve, reject) => {
570
+ const timeoutId = setTimeout(() => {
571
+ reject(new Error(`Execution timeout after ${timeout}ms`));
572
+ }, timeout);
573
+
574
+ Promise.resolve(handler(context.input, context))
575
+ .then(result => {
576
+ clearTimeout(timeoutId);
577
+ resolve(result);
578
+ })
579
+ .catch(error => {
580
+ clearTimeout(timeoutId);
581
+ reject(error);
582
+ });
583
+ });
584
+ }
585
+
586
+ async _runGuardrails(phase, data) {
587
+ for (const guardrail of this.guardrails) {
588
+ if (guardrail.phase === phase || guardrail.phase === 'both') {
589
+ const result = await guardrail.check(data);
590
+ if (!result.passed) {
591
+ throw new Error(`Guardrail '${guardrail.name}' failed: ${result.reason}`);
592
+ }
593
+ }
594
+ }
595
+ }
596
+
597
+ _isRetryable(error) {
598
+ const nonRetryable = [
599
+ 'Execution cancelled',
600
+ 'Input validation failed',
601
+ 'Output validation failed',
602
+ 'Guardrail'
603
+ ];
604
+ return !nonRetryable.some(msg => error.message.includes(msg));
605
+ }
606
+
607
+ async _waitForSlot() {
608
+ while (this._concurrentCount >= this.options.maxConcurrent) {
609
+ await this._sleep(100);
610
+ }
611
+ }
612
+
613
+ _sleep(ms) {
614
+ return new Promise(resolve => setTimeout(resolve, ms));
615
+ }
616
+
617
+ _addToHistory(result) {
618
+ this.executionHistory.push(result);
619
+ if (this.executionHistory.length > this.maxHistorySize) {
620
+ this.executionHistory.shift();
621
+ }
622
+ }
623
+
624
+ _summarizeResults(resultsMap) {
625
+ let success = 0;
626
+ let failed = 0;
627
+ let totalDuration = 0;
628
+
629
+ for (const result of resultsMap.values()) {
630
+ if (result.success) {
631
+ success++;
632
+ } else {
633
+ failed++;
634
+ }
635
+ totalDuration += result.duration || 0;
636
+ }
637
+
638
+ return {
639
+ total: resultsMap.size,
640
+ success,
641
+ failed,
642
+ totalDuration,
643
+ averageDuration: resultsMap.size > 0 ? totalDuration / resultsMap.size : 0
644
+ };
645
+ }
646
+
647
+ _mergeInputFromResults(results, skillId) {
648
+ // Simple merge - could be extended with explicit mappings
649
+ const merged = {};
650
+ for (const result of results.values()) {
651
+ if (result.output && typeof result.output === 'object') {
652
+ Object.assign(merged, result.output);
653
+ }
654
+ }
655
+ return merged;
656
+ }
657
+ }
658
+
659
+ module.exports = {
660
+ SkillExecutor,
661
+ ExecutionResult,
662
+ ExecutionContext,
663
+ ExecutionStatus,
664
+ IOValidator
665
+ };