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,547 @@
1
+ /**
2
+ * @file steering-validator.js
3
+ * @description Steering file validation and consistency checks
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
+ * Validation severity levels
17
+ * @enum {string}
18
+ */
19
+ const SEVERITY = {
20
+ INFO: 'info',
21
+ WARNING: 'warning',
22
+ ERROR: 'error',
23
+ CRITICAL: 'critical'
24
+ };
25
+
26
+ /**
27
+ * Validation rule types
28
+ * @enum {string}
29
+ */
30
+ const RULE_TYPE = {
31
+ REQUIRED: 'required',
32
+ FORMAT: 'format',
33
+ CONSISTENCY: 'consistency',
34
+ REFERENCE: 'reference',
35
+ COMPLETENESS: 'completeness'
36
+ };
37
+
38
+ /**
39
+ * @typedef {Object} ValidationIssue
40
+ * @property {string} id - Issue identifier
41
+ * @property {string} file - File with issue
42
+ * @property {string} type - Issue type
43
+ * @property {string} severity - Issue severity
44
+ * @property {string} message - Issue message
45
+ * @property {number} [line] - Line number
46
+ * @property {string} [suggestion] - Fix suggestion
47
+ */
48
+
49
+ /**
50
+ * @typedef {Object} ValidationResult
51
+ * @property {boolean} valid - Overall validity
52
+ * @property {number} score - Validation score (0-100)
53
+ * @property {ValidationIssue[]} issues - Found issues
54
+ * @property {Object} summary - Validation summary
55
+ * @property {number} timestamp - Validation timestamp
56
+ */
57
+
58
+ /**
59
+ * Default validation rules
60
+ */
61
+ const DEFAULT_VALIDATION_RULES = [
62
+ // Structure.md rules
63
+ {
64
+ id: 'structure-has-overview',
65
+ file: 'structure.md',
66
+ type: RULE_TYPE.REQUIRED,
67
+ severity: SEVERITY.ERROR,
68
+ check: (content) => content.includes('## Overview') || content.includes('# '),
69
+ message: 'structure.md must have an Overview section'
70
+ },
71
+ {
72
+ id: 'structure-has-directories',
73
+ file: 'structure.md',
74
+ type: RULE_TYPE.REQUIRED,
75
+ severity: SEVERITY.WARNING,
76
+ check: (content) => content.includes('src/') || content.includes('lib/'),
77
+ message: 'structure.md should document directory structure'
78
+ },
79
+
80
+ // Tech.md rules
81
+ {
82
+ id: 'tech-has-stack',
83
+ file: 'tech.md',
84
+ type: RULE_TYPE.REQUIRED,
85
+ severity: SEVERITY.ERROR,
86
+ check: (content) => content.includes('stack') || content.includes('technologies'),
87
+ message: 'tech.md must document the technology stack',
88
+ suggestion: 'Add a "Technology Stack" section'
89
+ },
90
+ {
91
+ id: 'tech-has-dependencies',
92
+ file: 'tech.md',
93
+ type: RULE_TYPE.REQUIRED,
94
+ severity: SEVERITY.WARNING,
95
+ check: (content) => content.includes('dependencies') || content.includes('package'),
96
+ message: 'tech.md should document dependencies'
97
+ },
98
+
99
+ // Product.md rules
100
+ {
101
+ id: 'product-has-vision',
102
+ file: 'product.md',
103
+ type: RULE_TYPE.REQUIRED,
104
+ severity: SEVERITY.ERROR,
105
+ check: (content) => content.includes('vision') || content.includes('purpose') || content.includes('goal'),
106
+ message: 'product.md must have a vision/purpose section'
107
+ },
108
+ {
109
+ id: 'product-has-features',
110
+ file: 'product.md',
111
+ type: RULE_TYPE.REQUIRED,
112
+ severity: SEVERITY.WARNING,
113
+ check: (content) => content.includes('feature') || content.includes('capability'),
114
+ message: 'product.md should list features or capabilities'
115
+ },
116
+
117
+ // Constitution rules
118
+ {
119
+ id: 'constitution-has-articles',
120
+ file: 'rules/constitution.md',
121
+ type: RULE_TYPE.REQUIRED,
122
+ severity: SEVERITY.ERROR,
123
+ check: (content) => content.includes('Article') || content.includes('Rule'),
124
+ message: 'constitution.md must define governance articles'
125
+ },
126
+
127
+ // Format rules
128
+ {
129
+ id: 'format-valid-markdown',
130
+ file: '*',
131
+ type: RULE_TYPE.FORMAT,
132
+ severity: SEVERITY.WARNING,
133
+ check: (content) => {
134
+ // Check for broken links format
135
+ const brokenLinks = content.match(/\[([^\]]*)\]\(\s*\)/g);
136
+ return !brokenLinks || brokenLinks.length === 0;
137
+ },
138
+ message: 'File contains empty markdown links'
139
+ },
140
+ {
141
+ id: 'format-no-todo-in-production',
142
+ file: '*',
143
+ type: RULE_TYPE.COMPLETENESS,
144
+ severity: SEVERITY.INFO,
145
+ check: (content) => !content.includes('[TODO]') && !content.includes('[TBD]'),
146
+ message: 'File contains TODO or TBD markers'
147
+ }
148
+ ];
149
+
150
+ /**
151
+ * Steering Validator class
152
+ * @extends EventEmitter
153
+ */
154
+ class SteeringValidator extends EventEmitter {
155
+ /**
156
+ * Create steering validator
157
+ * @param {Object} [options={}] - Options
158
+ */
159
+ constructor(options = {}) {
160
+ super();
161
+
162
+ this.steeringPath = options.steeringPath || 'steering';
163
+ this.rules = [...DEFAULT_VALIDATION_RULES, ...(options.rules || [])];
164
+ this.strictMode = options.strictMode ?? false;
165
+
166
+ // State
167
+ this.validations = new Map();
168
+ this.validationCounter = 0;
169
+ }
170
+
171
+ /**
172
+ * Validate all steering files
173
+ * @param {string} [basePath='.'] - Base path
174
+ * @returns {Promise<ValidationResult>}
175
+ */
176
+ async validate(basePath = '.') {
177
+ const id = `validation-${++this.validationCounter}`;
178
+ this.emit('validation:start', { id, basePath });
179
+
180
+ const steeringDir = path.join(basePath, this.steeringPath);
181
+ const issues = [];
182
+
183
+ // Define files to check
184
+ const files = [
185
+ 'structure.md',
186
+ 'tech.md',
187
+ 'product.md',
188
+ 'rules/constitution.md'
189
+ ];
190
+
191
+ // Check each file
192
+ for (const file of files) {
193
+ const filePath = path.join(steeringDir, file);
194
+
195
+ if (!fs.existsSync(filePath)) {
196
+ issues.push({
197
+ id: `issue-${issues.length + 1}`,
198
+ file,
199
+ type: RULE_TYPE.REQUIRED,
200
+ severity: SEVERITY.ERROR,
201
+ message: `Required steering file "${file}" not found`,
202
+ suggestion: `Create ${file} with appropriate content`
203
+ });
204
+ continue;
205
+ }
206
+
207
+ try {
208
+ const content = fs.readFileSync(filePath, 'utf8');
209
+ const fileIssues = await this.validateFile(file, content);
210
+ issues.push(...fileIssues);
211
+ } catch (error) {
212
+ issues.push({
213
+ id: `issue-${issues.length + 1}`,
214
+ file,
215
+ type: RULE_TYPE.FORMAT,
216
+ severity: SEVERITY.ERROR,
217
+ message: `Error reading file: ${error.message}`
218
+ });
219
+ }
220
+ }
221
+
222
+ // Check custom steering files
223
+ const customDir = path.join(steeringDir, 'custom');
224
+ if (fs.existsSync(customDir)) {
225
+ const customFiles = fs.readdirSync(customDir).filter(f => f.endsWith('.md'));
226
+ for (const file of customFiles) {
227
+ const filePath = path.join(customDir, file);
228
+ const content = fs.readFileSync(filePath, 'utf8');
229
+ const fileIssues = await this.validateFile(`custom/${file}`, content);
230
+ issues.push(...fileIssues);
231
+ }
232
+ }
233
+
234
+ // Cross-file consistency checks
235
+ const consistencyIssues = await this.checkConsistency(basePath);
236
+ issues.push(...consistencyIssues);
237
+
238
+ // Calculate score
239
+ const score = this.calculateScore(issues);
240
+
241
+ // Create result
242
+ const result = {
243
+ id,
244
+ valid: issues.filter(i => i.severity === SEVERITY.ERROR || i.severity === SEVERITY.CRITICAL).length === 0,
245
+ score,
246
+ issues,
247
+ summary: this.createSummary(issues),
248
+ timestamp: Date.now()
249
+ };
250
+
251
+ this.validations.set(id, result);
252
+ this.emit('validation:complete', { result });
253
+
254
+ return result;
255
+ }
256
+
257
+ /**
258
+ * Validate a single file
259
+ * @param {string} file - File name
260
+ * @param {string} content - File content
261
+ * @returns {ValidationIssue[]}
262
+ */
263
+ async validateFile(file, content) {
264
+ const issues = [];
265
+
266
+ // Get applicable rules
267
+ const applicableRules = this.rules.filter(rule =>
268
+ rule.file === file || rule.file === '*'
269
+ );
270
+
271
+ for (const rule of applicableRules) {
272
+ try {
273
+ const passes = rule.check(content);
274
+
275
+ if (!passes) {
276
+ issues.push({
277
+ id: `issue-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`,
278
+ file,
279
+ rule: rule.id,
280
+ type: rule.type,
281
+ severity: rule.severity,
282
+ message: rule.message,
283
+ suggestion: rule.suggestion
284
+ });
285
+ }
286
+ } catch (error) {
287
+ this.emit('rule:error', { rule: rule.id, error });
288
+ }
289
+ }
290
+
291
+ return issues;
292
+ }
293
+
294
+ /**
295
+ * Check cross-file consistency
296
+ * @param {string} basePath - Base path
297
+ * @returns {Promise<ValidationIssue[]>}
298
+ */
299
+ async checkConsistency(basePath) {
300
+ const issues = [];
301
+ const steeringDir = path.join(basePath, this.steeringPath);
302
+
303
+ try {
304
+ // Load files
305
+ const structurePath = path.join(steeringDir, 'structure.md');
306
+ const techPath = path.join(steeringDir, 'tech.md');
307
+ const productPath = path.join(steeringDir, 'product.md');
308
+
309
+ const files = {};
310
+ if (fs.existsSync(structurePath)) {
311
+ files.structure = fs.readFileSync(structurePath, 'utf8');
312
+ }
313
+ if (fs.existsSync(techPath)) {
314
+ files.tech = fs.readFileSync(techPath, 'utf8');
315
+ }
316
+ if (fs.existsSync(productPath)) {
317
+ files.product = fs.readFileSync(productPath, 'utf8');
318
+ }
319
+
320
+ // Check project name consistency
321
+ const projectNames = this.extractProjectNames(files);
322
+ if (projectNames.size > 1) {
323
+ issues.push({
324
+ id: `consistency-project-name`,
325
+ file: 'multiple',
326
+ type: RULE_TYPE.CONSISTENCY,
327
+ severity: SEVERITY.WARNING,
328
+ message: `Inconsistent project names found: ${Array.from(projectNames).join(', ')}`,
329
+ suggestion: 'Use consistent project name across all steering files'
330
+ });
331
+ }
332
+
333
+ // Check language consistency
334
+ if (files.structure && files.tech) {
335
+ const structureLangs = this.extractLanguages(files.structure);
336
+ const techLangs = this.extractLanguages(files.tech);
337
+
338
+ const missingInTech = structureLangs.filter(l => !techLangs.includes(l));
339
+ if (missingInTech.length > 0) {
340
+ issues.push({
341
+ id: `consistency-languages`,
342
+ file: 'tech.md',
343
+ type: RULE_TYPE.CONSISTENCY,
344
+ severity: SEVERITY.INFO,
345
+ message: `Languages in structure.md not documented in tech.md: ${missingInTech.join(', ')}`
346
+ });
347
+ }
348
+ }
349
+
350
+ } catch (error) {
351
+ this.emit('consistency:error', { error });
352
+ }
353
+
354
+ return issues;
355
+ }
356
+
357
+ /**
358
+ * Extract project names from files
359
+ * @private
360
+ */
361
+ extractProjectNames(files) {
362
+ const names = new Set();
363
+
364
+ for (const [, content] of Object.entries(files)) {
365
+ // Look for project name patterns
366
+ const matches = content.match(/^#\s+([^\n]+)/m);
367
+ if (matches) {
368
+ names.add(matches[1].trim());
369
+ }
370
+ }
371
+
372
+ return names;
373
+ }
374
+
375
+ /**
376
+ * Extract languages from content
377
+ * @private
378
+ */
379
+ extractLanguages(content) {
380
+ const langs = [];
381
+ const patterns = [
382
+ /javascript/gi,
383
+ /typescript/gi,
384
+ /python/gi,
385
+ /java(?!script)/gi,
386
+ /go(?:lang)?/gi,
387
+ /rust/gi,
388
+ /ruby/gi
389
+ ];
390
+
391
+ for (const pattern of patterns) {
392
+ if (pattern.test(content)) {
393
+ langs.push(pattern.source.replace(/[^\w]/g, '').toLowerCase());
394
+ }
395
+ }
396
+
397
+ return [...new Set(langs)];
398
+ }
399
+
400
+ /**
401
+ * Calculate validation score
402
+ * @private
403
+ */
404
+ calculateScore(issues) {
405
+ let score = 100;
406
+
407
+ const penalties = {
408
+ [SEVERITY.INFO]: 1,
409
+ [SEVERITY.WARNING]: 5,
410
+ [SEVERITY.ERROR]: 15,
411
+ [SEVERITY.CRITICAL]: 30
412
+ };
413
+
414
+ for (const issue of issues) {
415
+ score -= penalties[issue.severity] || 0;
416
+ }
417
+
418
+ return Math.max(0, score);
419
+ }
420
+
421
+ /**
422
+ * Create validation summary
423
+ * @private
424
+ */
425
+ createSummary(issues) {
426
+ const bySeverity = {};
427
+ const byType = {};
428
+ const byFile = {};
429
+
430
+ for (const issue of issues) {
431
+ bySeverity[issue.severity] = (bySeverity[issue.severity] || 0) + 1;
432
+ byType[issue.type] = (byType[issue.type] || 0) + 1;
433
+ byFile[issue.file] = (byFile[issue.file] || 0) + 1;
434
+ }
435
+
436
+ return {
437
+ totalIssues: issues.length,
438
+ bySeverity,
439
+ byType,
440
+ byFile
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Add custom rule
446
+ * @param {Object} rule - Validation rule
447
+ */
448
+ addRule(rule) {
449
+ if (!rule.id || !rule.file || !rule.check) {
450
+ throw new Error('Rule must have id, file, and check function');
451
+ }
452
+
453
+ this.rules.push({
454
+ type: RULE_TYPE.COMPLETENESS,
455
+ severity: SEVERITY.WARNING,
456
+ ...rule
457
+ });
458
+ }
459
+
460
+ /**
461
+ * Get validation history
462
+ * @returns {ValidationResult[]}
463
+ */
464
+ getHistory() {
465
+ return Array.from(this.validations.values())
466
+ .sort((a, b) => b.timestamp - a.timestamp);
467
+ }
468
+
469
+ /**
470
+ * Get statistics
471
+ * @returns {Object}
472
+ */
473
+ getStats() {
474
+ const validations = Array.from(this.validations.values());
475
+ const scores = validations.map(v => v.score);
476
+
477
+ return {
478
+ totalValidations: validations.length,
479
+ averageScore: scores.length > 0
480
+ ? scores.reduce((a, b) => a + b, 0) / scores.length
481
+ : 0,
482
+ passed: validations.filter(v => v.valid).length,
483
+ failed: validations.filter(v => !v.valid).length,
484
+ rulesCount: this.rules.length
485
+ };
486
+ }
487
+
488
+ /**
489
+ * Export validation report
490
+ * @param {string} validationId - Validation ID
491
+ * @returns {string} Markdown report
492
+ */
493
+ exportReport(validationId) {
494
+ const validation = this.validations.get(validationId);
495
+ if (!validation) return '';
496
+
497
+ let md = `# Steering Validation Report\n\n`;
498
+ md += `**Status:** ${validation.valid ? '✅ Valid' : '❌ Invalid'}\n`;
499
+ md += `**Score:** ${validation.score}/100\n`;
500
+ md += `**Date:** ${new Date(validation.timestamp).toISOString()}\n\n`;
501
+
502
+ md += `## Summary\n\n`;
503
+ md += `- Total Issues: ${validation.issues.length}\n`;
504
+ for (const [severity, count] of Object.entries(validation.summary.bySeverity)) {
505
+ md += `- ${severity}: ${count}\n`;
506
+ }
507
+
508
+ if (validation.issues.length > 0) {
509
+ md += `\n## Issues\n\n`;
510
+
511
+ for (const issue of validation.issues) {
512
+ const icon = {
513
+ [SEVERITY.INFO]: 'ℹ️',
514
+ [SEVERITY.WARNING]: '⚠️',
515
+ [SEVERITY.ERROR]: '❌',
516
+ [SEVERITY.CRITICAL]: '🚨'
517
+ }[issue.severity];
518
+
519
+ md += `### ${icon} ${issue.message}\n\n`;
520
+ md += `- **File:** ${issue.file}\n`;
521
+ md += `- **Type:** ${issue.type}\n`;
522
+ md += `- **Severity:** ${issue.severity}\n`;
523
+ if (issue.suggestion) md += `- **Suggestion:** ${issue.suggestion}\n`;
524
+ md += `\n`;
525
+ }
526
+ }
527
+
528
+ return md;
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Create steering validator
534
+ * @param {Object} [options={}] - Options
535
+ * @returns {SteeringValidator}
536
+ */
537
+ function createSteeringValidator(options = {}) {
538
+ return new SteeringValidator(options);
539
+ }
540
+
541
+ module.exports = {
542
+ SteeringValidator,
543
+ createSteeringValidator,
544
+ SEVERITY,
545
+ RULE_TYPE,
546
+ DEFAULT_VALIDATION_RULES
547
+ };