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,572 @@
1
+ /**
2
+ * @file steering-auto-update.js
3
+ * @description Automatic steering file update engine
4
+ * @version 1.0.0
5
+ *
6
+ * Part of MUSUBI v5.0.0 - Phase 5 Advanced Features
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { EventEmitter } = require('events');
14
+
15
+ /**
16
+ * Update trigger types
17
+ * @enum {string}
18
+ */
19
+ const TRIGGER = {
20
+ AGENT_WORK: 'agent-work',
21
+ CODE_CHANGE: 'code-change',
22
+ DEPENDENCY_UPDATE: 'dependency-update',
23
+ CONFIG_CHANGE: 'config-change',
24
+ MANUAL: 'manual',
25
+ SCHEDULED: 'scheduled'
26
+ };
27
+
28
+ /**
29
+ * Steering file types
30
+ * @enum {string}
31
+ */
32
+ const STEERING_TYPE = {
33
+ STRUCTURE: 'structure',
34
+ TECH: 'tech',
35
+ PRODUCT: 'product',
36
+ RULES: 'rules',
37
+ CUSTOM: 'custom'
38
+ };
39
+
40
+ /**
41
+ * @typedef {Object} UpdateRule
42
+ * @property {string} id - Rule identifier
43
+ * @property {string} name - Rule name
44
+ * @property {string} trigger - What triggers this update
45
+ * @property {string} target - Target steering file
46
+ * @property {Function} condition - Condition to check
47
+ * @property {Function} update - Update function
48
+ * @property {number} [priority=0] - Rule priority
49
+ */
50
+
51
+ /**
52
+ * @typedef {Object} UpdateResult
53
+ * @property {string} id - Update identifier
54
+ * @property {boolean} success - Whether update succeeded
55
+ * @property {string} file - Updated file path
56
+ * @property {string} trigger - What triggered the update
57
+ * @property {string[]} changes - List of changes made
58
+ * @property {number} timestamp - Update timestamp
59
+ */
60
+
61
+ /**
62
+ * @typedef {Object} SteeringAutoUpdateOptions
63
+ * @property {string} [steeringPath='steering'] - Path to steering directory
64
+ * @property {boolean} [autoSave=true] - Auto-save changes
65
+ * @property {boolean} [backup=true] - Create backups before updates
66
+ * @property {Object} [rules={}] - Custom update rules
67
+ */
68
+
69
+ /**
70
+ * Default update rules
71
+ */
72
+ const DEFAULT_RULES = [
73
+ {
74
+ id: 'tech-deps-update',
75
+ name: 'Update tech.md on dependency changes',
76
+ trigger: TRIGGER.DEPENDENCY_UPDATE,
77
+ target: STEERING_TYPE.TECH,
78
+ priority: 10,
79
+ condition: (context) => context.packageJsonChanged,
80
+ update: async (steering, context) => {
81
+ const changes = [];
82
+
83
+ if (context.newDependencies?.length > 0) {
84
+ changes.push(`Added dependencies: ${context.newDependencies.join(', ')}`);
85
+ }
86
+ if (context.removedDependencies?.length > 0) {
87
+ changes.push(`Removed dependencies: ${context.removedDependencies.join(', ')}`);
88
+ }
89
+ if (context.updatedDependencies?.length > 0) {
90
+ changes.push(`Updated dependencies: ${context.updatedDependencies.join(', ')}`);
91
+ }
92
+
93
+ return { section: 'dependencies', changes };
94
+ }
95
+ },
96
+ {
97
+ id: 'structure-dirs-update',
98
+ name: 'Update structure.md on new directories',
99
+ trigger: TRIGGER.CODE_CHANGE,
100
+ target: STEERING_TYPE.STRUCTURE,
101
+ priority: 5,
102
+ condition: (context) => context.newDirectories?.length > 0,
103
+ update: async (steering, context) => {
104
+ const changes = context.newDirectories.map(dir => `Added directory: ${dir}`);
105
+ return { section: 'directories', changes };
106
+ }
107
+ },
108
+ {
109
+ id: 'structure-files-update',
110
+ name: 'Update structure.md on significant file changes',
111
+ trigger: TRIGGER.CODE_CHANGE,
112
+ target: STEERING_TYPE.STRUCTURE,
113
+ priority: 4,
114
+ condition: (context) => context.significantFileChanges,
115
+ update: async (steering, context) => {
116
+ const changes = [];
117
+ if (context.newEntryPoints?.length > 0) {
118
+ changes.push(`New entry points: ${context.newEntryPoints.join(', ')}`);
119
+ }
120
+ if (context.newModules?.length > 0) {
121
+ changes.push(`New modules: ${context.newModules.join(', ')}`);
122
+ }
123
+ return { section: 'files', changes };
124
+ }
125
+ },
126
+ {
127
+ id: 'product-features-update',
128
+ name: 'Update product.md on feature completion',
129
+ trigger: TRIGGER.AGENT_WORK,
130
+ target: STEERING_TYPE.PRODUCT,
131
+ priority: 8,
132
+ condition: (context) => context.featureCompleted,
133
+ update: async (steering, context) => {
134
+ const changes = [`Completed feature: ${context.featureName}`];
135
+ if (context.featureDescription) {
136
+ changes.push(`Description: ${context.featureDescription}`);
137
+ }
138
+ return { section: 'features', changes };
139
+ }
140
+ },
141
+ {
142
+ id: 'rules-patterns-update',
143
+ name: 'Update rules when new patterns detected',
144
+ trigger: TRIGGER.AGENT_WORK,
145
+ target: STEERING_TYPE.RULES,
146
+ priority: 3,
147
+ condition: (context) => context.newPatterns?.length > 0,
148
+ update: async (steering, context) => {
149
+ const changes = context.newPatterns.map(p => `New pattern: ${p}`);
150
+ return { section: 'patterns', changes };
151
+ }
152
+ }
153
+ ];
154
+
155
+ /**
156
+ * Steering Auto-Update Engine
157
+ * @extends EventEmitter
158
+ */
159
+ class SteeringAutoUpdate extends EventEmitter {
160
+ /**
161
+ * Create steering auto-update engine
162
+ * @param {SteeringAutoUpdateOptions} [options={}] - Options
163
+ */
164
+ constructor(options = {}) {
165
+ super();
166
+
167
+ this.steeringPath = options.steeringPath || 'steering';
168
+ this.autoSave = options.autoSave !== false;
169
+ this.backup = options.backup !== false;
170
+ this.rules = [...DEFAULT_RULES, ...(options.rules || [])];
171
+
172
+ // Sort rules by priority
173
+ this.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
174
+
175
+ // State
176
+ this.updates = new Map();
177
+ this.updateCounter = 0;
178
+ this.steering = new Map();
179
+ this.pendingChanges = new Map();
180
+ }
181
+
182
+ /**
183
+ * Load steering files
184
+ * @param {string} [basePath='.'] - Base path
185
+ * @returns {Promise<Map>} Loaded steering files
186
+ */
187
+ async loadSteering(basePath = '.') {
188
+ const steeringDir = path.join(basePath, this.steeringPath);
189
+
190
+ const files = {
191
+ [STEERING_TYPE.STRUCTURE]: 'structure.md',
192
+ [STEERING_TYPE.TECH]: 'tech.md',
193
+ [STEERING_TYPE.PRODUCT]: 'product.md',
194
+ [STEERING_TYPE.RULES]: 'rules/constitution.md'
195
+ };
196
+
197
+ for (const [type, file] of Object.entries(files)) {
198
+ const filePath = path.join(steeringDir, file);
199
+ try {
200
+ if (fs.existsSync(filePath)) {
201
+ const content = fs.readFileSync(filePath, 'utf8');
202
+ this.steering.set(type, {
203
+ path: filePath,
204
+ content,
205
+ parsed: this.parseMarkdown(content),
206
+ lastModified: fs.statSync(filePath).mtime
207
+ });
208
+ }
209
+ } catch (error) {
210
+ this.emit('error', { type, file, error });
211
+ }
212
+ }
213
+
214
+ // Load custom steering files
215
+ const customDir = path.join(steeringDir, 'custom');
216
+ if (fs.existsSync(customDir)) {
217
+ const customFiles = fs.readdirSync(customDir).filter(f => f.endsWith('.md'));
218
+ for (const file of customFiles) {
219
+ const filePath = path.join(customDir, file);
220
+ const content = fs.readFileSync(filePath, 'utf8');
221
+ this.steering.set(`custom:${file}`, {
222
+ path: filePath,
223
+ content,
224
+ parsed: this.parseMarkdown(content),
225
+ lastModified: fs.statSync(filePath).mtime
226
+ });
227
+ }
228
+ }
229
+
230
+ this.emit('steering:loaded', { count: this.steering.size });
231
+ return this.steering;
232
+ }
233
+
234
+ /**
235
+ * Parse markdown into sections
236
+ * @private
237
+ */
238
+ parseMarkdown(content) {
239
+ const sections = new Map();
240
+ const lines = content.split('\n');
241
+ let currentSection = 'header';
242
+ let currentContent = [];
243
+
244
+ for (const line of lines) {
245
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
246
+ if (headerMatch) {
247
+ // Save previous section
248
+ if (currentContent.length > 0) {
249
+ sections.set(currentSection, currentContent.join('\n'));
250
+ }
251
+ currentSection = headerMatch[2].toLowerCase().replace(/\s+/g, '-');
252
+ currentContent = [line];
253
+ } else {
254
+ currentContent.push(line);
255
+ }
256
+ }
257
+
258
+ // Save last section
259
+ if (currentContent.length > 0) {
260
+ sections.set(currentSection, currentContent.join('\n'));
261
+ }
262
+
263
+ return sections;
264
+ }
265
+
266
+ /**
267
+ * Process an update trigger
268
+ * @param {string} trigger - Trigger type
269
+ * @param {Object} context - Trigger context
270
+ * @returns {Promise<UpdateResult[]>} Update results
271
+ */
272
+ async processTrigger(trigger, context = {}) {
273
+ const id = this.generateId();
274
+ this.emit('trigger:received', { id, trigger, context });
275
+
276
+ const results = [];
277
+
278
+ // Find applicable rules
279
+ const applicableRules = this.rules.filter(rule => {
280
+ if (rule.trigger !== trigger) return false;
281
+ try {
282
+ return rule.condition(context);
283
+ } catch (error) {
284
+ this.emit('rule:error', { rule: rule.id, error });
285
+ return false;
286
+ }
287
+ });
288
+
289
+ // Apply each rule
290
+ for (const rule of applicableRules) {
291
+ try {
292
+ this.emit('rule:applying', { rule: rule.id, target: rule.target });
293
+
294
+ const steering = this.steering.get(rule.target);
295
+ if (!steering) {
296
+ this.emit('rule:skipped', { rule: rule.id, reason: 'target not found' });
297
+ continue;
298
+ }
299
+
300
+ const updateResult = await rule.update(steering, context);
301
+
302
+ if (updateResult && updateResult.changes?.length > 0) {
303
+ // Queue changes
304
+ if (!this.pendingChanges.has(rule.target)) {
305
+ this.pendingChanges.set(rule.target, []);
306
+ }
307
+ this.pendingChanges.get(rule.target).push({
308
+ rule: rule.id,
309
+ section: updateResult.section,
310
+ changes: updateResult.changes,
311
+ timestamp: Date.now()
312
+ });
313
+
314
+ const result = {
315
+ id: `update-${++this.updateCounter}`,
316
+ success: true,
317
+ file: steering.path,
318
+ trigger,
319
+ rule: rule.id,
320
+ changes: updateResult.changes,
321
+ timestamp: Date.now()
322
+ };
323
+
324
+ results.push(result);
325
+ this.updates.set(result.id, result);
326
+
327
+ this.emit('rule:applied', { rule: rule.id, changes: updateResult.changes });
328
+ }
329
+ } catch (error) {
330
+ const result = {
331
+ id: `update-${++this.updateCounter}`,
332
+ success: false,
333
+ file: rule.target,
334
+ trigger,
335
+ rule: rule.id,
336
+ error: error.message,
337
+ timestamp: Date.now()
338
+ };
339
+
340
+ results.push(result);
341
+ this.emit('rule:failed', { rule: rule.id, error });
342
+ }
343
+ }
344
+
345
+ // Auto-save if enabled
346
+ if (this.autoSave && results.some(r => r.success)) {
347
+ await this.applyPendingChanges();
348
+ }
349
+
350
+ this.emit('trigger:processed', { id, results });
351
+ return results;
352
+ }
353
+
354
+ /**
355
+ * Apply pending changes to files
356
+ * @returns {Promise<Object>} Apply result
357
+ */
358
+ async applyPendingChanges() {
359
+ const applied = [];
360
+
361
+ for (const [target, changes] of this.pendingChanges.entries()) {
362
+ const steering = this.steering.get(target);
363
+ if (!steering) continue;
364
+
365
+ try {
366
+ // Create backup
367
+ if (this.backup) {
368
+ const backupPath = `${steering.path}.backup`;
369
+ fs.writeFileSync(backupPath, steering.content);
370
+ }
371
+
372
+ // Generate changelog section
373
+ const changelog = this.generateChangelog(changes);
374
+
375
+ // Update content
376
+ let newContent = steering.content;
377
+
378
+ // Add or update changelog section
379
+ if (newContent.includes('## Changelog')) {
380
+ newContent = newContent.replace(
381
+ /## Changelog\n/,
382
+ `## Changelog\n\n${changelog}\n`
383
+ );
384
+ } else {
385
+ newContent += `\n\n## Changelog\n\n${changelog}`;
386
+ }
387
+
388
+ // Write file
389
+ fs.writeFileSync(steering.path, newContent);
390
+
391
+ // Update in-memory state
392
+ steering.content = newContent;
393
+ steering.parsed = this.parseMarkdown(newContent);
394
+ steering.lastModified = new Date();
395
+
396
+ applied.push({ target, changesCount: changes.length });
397
+
398
+ this.emit('changes:applied', { target, changes });
399
+ } catch (error) {
400
+ this.emit('changes:failed', { target, error });
401
+ }
402
+ }
403
+
404
+ this.pendingChanges.clear();
405
+ return { applied };
406
+ }
407
+
408
+ /**
409
+ * Generate changelog entry
410
+ * @private
411
+ */
412
+ generateChangelog(changes) {
413
+ const date = new Date().toISOString().split('T')[0];
414
+ const entries = [];
415
+
416
+ for (const change of changes) {
417
+ for (const item of change.changes) {
418
+ entries.push(`- ${item}`);
419
+ }
420
+ }
421
+
422
+ return `### ${date}\n${entries.join('\n')}`;
423
+ }
424
+
425
+ /**
426
+ * Add custom rule
427
+ * @param {UpdateRule} rule - Rule to add
428
+ */
429
+ addRule(rule) {
430
+ if (!rule.id || !rule.trigger || !rule.target) {
431
+ throw new Error('Rule must have id, trigger, and target');
432
+ }
433
+
434
+ this.rules.push(rule);
435
+ this.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
436
+
437
+ this.emit('rule:added', { ruleId: rule.id });
438
+ }
439
+
440
+ /**
441
+ * Remove rule
442
+ * @param {string} ruleId - Rule ID to remove
443
+ */
444
+ removeRule(ruleId) {
445
+ const index = this.rules.findIndex(r => r.id === ruleId);
446
+ if (index !== -1) {
447
+ this.rules.splice(index, 1);
448
+ this.emit('rule:removed', { ruleId });
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Get update history
454
+ * @param {Object} [filter={}] - Filter options
455
+ * @returns {UpdateResult[]}
456
+ */
457
+ getHistory(filter = {}) {
458
+ let results = Array.from(this.updates.values());
459
+
460
+ if (filter.trigger) {
461
+ results = results.filter(r => r.trigger === filter.trigger);
462
+ }
463
+ if (filter.file) {
464
+ results = results.filter(r => r.file === filter.file);
465
+ }
466
+ if (filter.success !== undefined) {
467
+ results = results.filter(r => r.success === filter.success);
468
+ }
469
+
470
+ return results.sort((a, b) => b.timestamp - a.timestamp);
471
+ }
472
+
473
+ /**
474
+ * Get statistics
475
+ * @returns {Object}
476
+ */
477
+ getStats() {
478
+ const updates = Array.from(this.updates.values());
479
+
480
+ return {
481
+ totalUpdates: updates.length,
482
+ successful: updates.filter(u => u.success).length,
483
+ failed: updates.filter(u => !u.success).length,
484
+ byTrigger: updates.reduce((acc, u) => {
485
+ acc[u.trigger] = (acc[u.trigger] || 0) + 1;
486
+ return acc;
487
+ }, {}),
488
+ rulesCount: this.rules.length,
489
+ steeringFilesLoaded: this.steering.size,
490
+ pendingChanges: this.pendingChanges.size
491
+ };
492
+ }
493
+
494
+ /**
495
+ * Generate unique ID
496
+ * @private
497
+ */
498
+ generateId() {
499
+ return `trigger-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 6)}`;
500
+ }
501
+
502
+ /**
503
+ * Clear history
504
+ */
505
+ clearHistory() {
506
+ this.updates.clear();
507
+ }
508
+
509
+ /**
510
+ * Validate steering consistency
511
+ * @returns {Object} Validation result
512
+ */
513
+ validateConsistency() {
514
+ const issues = [];
515
+
516
+ // Check cross-file references
517
+ const structure = this.steering.get(STEERING_TYPE.STRUCTURE);
518
+ const tech = this.steering.get(STEERING_TYPE.TECH);
519
+
520
+ if (structure && tech) {
521
+ // Check if tech references match structure
522
+ const structureDirs = this.extractDirectories(structure.content);
523
+ const techDirs = this.extractDirectories(tech.content);
524
+
525
+ for (const dir of techDirs) {
526
+ if (!structureDirs.includes(dir)) {
527
+ issues.push({
528
+ type: 'mismatch',
529
+ file: STEERING_TYPE.TECH,
530
+ message: `Directory "${dir}" referenced in tech.md but not in structure.md`
531
+ });
532
+ }
533
+ }
534
+ }
535
+
536
+ return {
537
+ valid: issues.length === 0,
538
+ issues
539
+ };
540
+ }
541
+
542
+ /**
543
+ * Extract directories from content
544
+ * @private
545
+ */
546
+ extractDirectories(content) {
547
+ const dirs = [];
548
+ const regex = /`([^`]+\/)`/g;
549
+ let match;
550
+ while ((match = regex.exec(content)) !== null) {
551
+ dirs.push(match[1]);
552
+ }
553
+ return dirs;
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Create steering auto-update instance
559
+ * @param {SteeringAutoUpdateOptions} [options={}] - Options
560
+ * @returns {SteeringAutoUpdate}
561
+ */
562
+ function createSteeringAutoUpdate(options = {}) {
563
+ return new SteeringAutoUpdate(options);
564
+ }
565
+
566
+ module.exports = {
567
+ SteeringAutoUpdate,
568
+ createSteeringAutoUpdate,
569
+ TRIGGER,
570
+ STEERING_TYPE,
571
+ DEFAULT_RULES
572
+ };