musubi-sdd 3.0.1 → 3.6.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 (62) hide show
  1. package/README.md +17 -3
  2. package/bin/musubi-change.js +623 -10
  3. package/bin/musubi-orchestrate.js +456 -0
  4. package/bin/musubi-trace.js +393 -0
  5. package/package.json +3 -2
  6. package/src/analyzers/impact-analyzer.js +682 -0
  7. package/src/integrations/cicd.js +782 -0
  8. package/src/integrations/documentation.js +740 -0
  9. package/src/integrations/examples.js +789 -0
  10. package/src/integrations/index.js +23 -0
  11. package/src/integrations/platforms.js +929 -0
  12. package/src/llm-providers/anthropic-provider.js +175 -0
  13. package/src/llm-providers/base-provider.js +221 -0
  14. package/src/llm-providers/copilot-provider.js +262 -0
  15. package/src/llm-providers/index.js +214 -0
  16. package/src/llm-providers/openai-provider.js +205 -0
  17. package/src/managers/delta-spec.js +484 -0
  18. package/src/monitoring/incident-manager.js +890 -0
  19. package/src/monitoring/index.js +633 -0
  20. package/src/monitoring/observability.js +938 -0
  21. package/src/monitoring/release-manager.js +622 -0
  22. package/src/orchestration/index.js +193 -0
  23. package/src/orchestration/orchestration-engine.js +409 -0
  24. package/src/orchestration/pattern-registry.js +319 -0
  25. package/src/orchestration/patterns/auto.js +386 -0
  26. package/src/orchestration/patterns/group-chat.js +395 -0
  27. package/src/orchestration/patterns/human-in-loop.js +506 -0
  28. package/src/orchestration/patterns/nested.js +322 -0
  29. package/src/orchestration/patterns/sequential.js +278 -0
  30. package/src/orchestration/patterns/swarm.js +502 -0
  31. package/src/orchestration/replanning/alternative-generator.js +508 -0
  32. package/src/orchestration/replanning/config.js +378 -0
  33. package/src/orchestration/replanning/index.js +40 -0
  34. package/src/orchestration/replanning/plan-evaluator.js +455 -0
  35. package/src/orchestration/replanning/plan-monitor.js +379 -0
  36. package/src/orchestration/replanning/replan-history.js +402 -0
  37. package/src/orchestration/replanning/replanning-engine.js +706 -0
  38. package/src/orchestration/workflow-orchestrator.js +738 -0
  39. package/src/reporters/coverage-report.js +452 -0
  40. package/src/reporters/traceability-matrix-report.js +684 -0
  41. package/src/steering/advanced-validation.js +812 -0
  42. package/src/steering/auto-updater.js +670 -0
  43. package/src/steering/index.js +119 -0
  44. package/src/steering/quality-metrics.js +650 -0
  45. package/src/steering/template-constraints.js +789 -0
  46. package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +22 -0
  47. package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +21 -0
  48. package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +90 -28
  49. package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +32 -0
  50. package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +27 -0
  51. package/src/templates/agents/claude-code/skills/steering/SKILL.md +30 -0
  52. package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +21 -0
  53. package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +27 -0
  54. package/src/templates/agents/codex/AGENTS.md +36 -1
  55. package/src/templates/agents/cursor/AGENTS.md +36 -1
  56. package/src/templates/agents/gemini-cli/GEMINI.md +36 -1
  57. package/src/templates/agents/github-copilot/AGENTS.md +65 -1
  58. package/src/templates/agents/qwen-code/QWEN.md +36 -1
  59. package/src/templates/agents/windsurf/AGENTS.md +36 -1
  60. package/src/templates/shared/delta-spec-template.md +246 -0
  61. package/src/validators/delta-format.js +474 -0
  62. package/src/validators/traceability-validator.js +561 -0
@@ -0,0 +1,938 @@
1
+ /**
2
+ * Observability Module - Logs, Metrics, and Traces
3
+ *
4
+ * Provides unified observability capabilities:
5
+ * - Structured logging
6
+ * - Metrics collection
7
+ * - Distributed tracing
8
+ * - Correlation IDs
9
+ */
10
+
11
+ const { EventEmitter } = require('events');
12
+
13
+ /**
14
+ * Log Levels
15
+ */
16
+ const LogLevel = {
17
+ TRACE: 'trace',
18
+ DEBUG: 'debug',
19
+ INFO: 'info',
20
+ WARN: 'warn',
21
+ ERROR: 'error',
22
+ FATAL: 'fatal'
23
+ };
24
+
25
+ /**
26
+ * Log level priorities
27
+ */
28
+ const LOG_PRIORITY = {
29
+ [LogLevel.TRACE]: 0,
30
+ [LogLevel.DEBUG]: 1,
31
+ [LogLevel.INFO]: 2,
32
+ [LogLevel.WARN]: 3,
33
+ [LogLevel.ERROR]: 4,
34
+ [LogLevel.FATAL]: 5
35
+ };
36
+
37
+ /**
38
+ * Trace Status
39
+ */
40
+ const TraceStatus = {
41
+ OK: 'ok',
42
+ ERROR: 'error',
43
+ UNSET: 'unset'
44
+ };
45
+
46
+ /**
47
+ * Span Kind
48
+ */
49
+ const SpanKind = {
50
+ INTERNAL: 'internal',
51
+ SERVER: 'server',
52
+ CLIENT: 'client',
53
+ PRODUCER: 'producer',
54
+ CONSUMER: 'consumer'
55
+ };
56
+
57
+ /**
58
+ * Generate unique ID
59
+ */
60
+ function generateId(length = 16) {
61
+ let result = '';
62
+ const chars = '0123456789abcdef';
63
+ for (let i = 0; i < length; i++) {
64
+ result += chars[Math.floor(Math.random() * chars.length)];
65
+ }
66
+ return result;
67
+ }
68
+
69
+ /**
70
+ * Structured Logger
71
+ */
72
+ class Logger extends EventEmitter {
73
+ constructor(options = {}) {
74
+ super();
75
+ this.name = options.name || 'default';
76
+ this.level = options.level || LogLevel.INFO;
77
+ this.context = options.context || {};
78
+ this.outputs = options.outputs || [new ConsoleOutput()];
79
+ this.parent = options.parent || null;
80
+ }
81
+
82
+ /**
83
+ * Create a child logger with additional context
84
+ */
85
+ child(context) {
86
+ return new Logger({
87
+ name: this.name,
88
+ level: this.level,
89
+ context: { ...this.context, ...context },
90
+ outputs: this.outputs,
91
+ parent: this
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Check if a level is enabled
97
+ */
98
+ isLevelEnabled(level) {
99
+ return LOG_PRIORITY[level] >= LOG_PRIORITY[this.level];
100
+ }
101
+
102
+ /**
103
+ * Log at a specific level
104
+ */
105
+ log(level, message, meta = {}) {
106
+ if (!this.isLevelEnabled(level)) return;
107
+
108
+ const entry = {
109
+ timestamp: new Date().toISOString(),
110
+ level,
111
+ logger: this.name,
112
+ message,
113
+ ...this.context,
114
+ ...meta
115
+ };
116
+
117
+ for (const output of this.outputs) {
118
+ output.write(entry);
119
+ }
120
+
121
+ this.emit('log', entry);
122
+ }
123
+
124
+ trace(message, meta) { this.log(LogLevel.TRACE, message, meta); }
125
+ debug(message, meta) { this.log(LogLevel.DEBUG, message, meta); }
126
+ info(message, meta) { this.log(LogLevel.INFO, message, meta); }
127
+ warn(message, meta) { this.log(LogLevel.WARN, message, meta); }
128
+ error(message, meta) { this.log(LogLevel.ERROR, message, meta); }
129
+ fatal(message, meta) { this.log(LogLevel.FATAL, message, meta); }
130
+
131
+ /**
132
+ * Add an output
133
+ */
134
+ addOutput(output) {
135
+ this.outputs.push(output);
136
+ return this;
137
+ }
138
+
139
+ /**
140
+ * Set log level
141
+ */
142
+ setLevel(level) {
143
+ this.level = level;
144
+ return this;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Console output for logger
150
+ */
151
+ class ConsoleOutput {
152
+ constructor(options = {}) {
153
+ this.format = options.format || 'json';
154
+ this.pretty = options.pretty || false;
155
+ }
156
+
157
+ write(entry) {
158
+ if (this.format === 'json') {
159
+ const output = this.pretty
160
+ ? JSON.stringify(entry, null, 2)
161
+ : JSON.stringify(entry);
162
+
163
+ if (entry.level === LogLevel.ERROR || entry.level === LogLevel.FATAL) {
164
+ console.error(output);
165
+ } else if (entry.level === LogLevel.WARN) {
166
+ console.warn(output);
167
+ } else {
168
+ console.log(output);
169
+ }
170
+ } else {
171
+ const timestamp = entry.timestamp;
172
+ const level = entry.level.toUpperCase().padEnd(5);
173
+ console.log(`${timestamp} [${level}] ${entry.message}`);
174
+ }
175
+ }
176
+ }
177
+
178
+ /**
179
+ * File output for logger
180
+ */
181
+ class FileOutput {
182
+ constructor(options = {}) {
183
+ this.path = options.path || 'app.log';
184
+ this.buffer = [];
185
+ this.bufferSize = options.bufferSize || 100;
186
+ }
187
+
188
+ write(entry) {
189
+ this.buffer.push(JSON.stringify(entry) + '\n');
190
+
191
+ if (this.buffer.length >= this.bufferSize) {
192
+ this.flush();
193
+ }
194
+ }
195
+
196
+ flush() {
197
+ // In real implementation, would write to file
198
+ this.buffer = [];
199
+ }
200
+
201
+ getBuffer() {
202
+ return [...this.buffer];
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Metric Collector
208
+ */
209
+ class MetricsCollector extends EventEmitter {
210
+ constructor(options = {}) {
211
+ super();
212
+ this.name = options.name || 'default';
213
+ this.prefix = options.prefix || '';
214
+ this.metrics = new Map();
215
+ this.labels = options.labels || {};
216
+ }
217
+
218
+ /**
219
+ * Format metric name
220
+ */
221
+ _formatName(name) {
222
+ return this.prefix ? `${this.prefix}_${name}` : name;
223
+ }
224
+
225
+ /**
226
+ * Create or get a counter
227
+ */
228
+ counter(name, help = '') {
229
+ const fullName = this._formatName(name);
230
+ if (!this.metrics.has(fullName)) {
231
+ this.metrics.set(fullName, {
232
+ type: 'counter',
233
+ name: fullName,
234
+ help,
235
+ values: new Map()
236
+ });
237
+ }
238
+ return new CounterMetric(this.metrics.get(fullName), this.labels);
239
+ }
240
+
241
+ /**
242
+ * Create or get a gauge
243
+ */
244
+ gauge(name, help = '') {
245
+ const fullName = this._formatName(name);
246
+ if (!this.metrics.has(fullName)) {
247
+ this.metrics.set(fullName, {
248
+ type: 'gauge',
249
+ name: fullName,
250
+ help,
251
+ values: new Map()
252
+ });
253
+ }
254
+ return new GaugeMetric(this.metrics.get(fullName), this.labels);
255
+ }
256
+
257
+ /**
258
+ * Create or get a histogram
259
+ */
260
+ histogram(name, help = '', buckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]) {
261
+ const fullName = this._formatName(name);
262
+ if (!this.metrics.has(fullName)) {
263
+ this.metrics.set(fullName, {
264
+ type: 'histogram',
265
+ name: fullName,
266
+ help,
267
+ buckets,
268
+ values: new Map()
269
+ });
270
+ }
271
+ return new HistogramMetric(this.metrics.get(fullName), this.labels);
272
+ }
273
+
274
+ /**
275
+ * Export metrics in Prometheus format
276
+ */
277
+ toPrometheus() {
278
+ let output = '';
279
+
280
+ for (const metric of this.metrics.values()) {
281
+ output += `# HELP ${metric.name} ${metric.help}\n`;
282
+ output += `# TYPE ${metric.name} ${metric.type}\n`;
283
+
284
+ if (metric.type === 'histogram') {
285
+ for (const [labelsKey, data] of metric.values) {
286
+ const labels = labelsKey ? `{${labelsKey}}` : '';
287
+ for (let i = 0; i < metric.buckets.length; i++) {
288
+ output += `${metric.name}_bucket{le="${metric.buckets[i]}"${labelsKey ? ',' + labelsKey : ''}} ${data.buckets[i]}\n`;
289
+ }
290
+ output += `${metric.name}_bucket{le="+Inf"${labelsKey ? ',' + labelsKey : ''}} ${data.count}\n`;
291
+ output += `${metric.name}_sum${labels} ${data.sum}\n`;
292
+ output += `${metric.name}_count${labels} ${data.count}\n`;
293
+ }
294
+ } else {
295
+ for (const [labelsKey, value] of metric.values) {
296
+ const labels = labelsKey ? `{${labelsKey}}` : '';
297
+ output += `${metric.name}${labels} ${value}\n`;
298
+ }
299
+ }
300
+ }
301
+
302
+ return output;
303
+ }
304
+
305
+ /**
306
+ * Export metrics as JSON
307
+ */
308
+ toJSON() {
309
+ const result = [];
310
+
311
+ for (const metric of this.metrics.values()) {
312
+ const metricData = {
313
+ name: metric.name,
314
+ type: metric.type,
315
+ help: metric.help,
316
+ values: []
317
+ };
318
+
319
+ for (const [labelsKey, value] of metric.values) {
320
+ metricData.values.push({
321
+ labels: labelsKey,
322
+ value: metric.type === 'histogram' ? { ...value } : value
323
+ });
324
+ }
325
+
326
+ result.push(metricData);
327
+ }
328
+
329
+ return result;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Counter metric
335
+ */
336
+ class CounterMetric {
337
+ constructor(metric, defaultLabels = {}) {
338
+ this.metric = metric;
339
+ this.defaultLabels = defaultLabels;
340
+ }
341
+
342
+ _labelKey(labels = {}) {
343
+ const all = { ...this.defaultLabels, ...labels };
344
+ return Object.entries(all)
345
+ .map(([k, v]) => `${k}="${v}"`)
346
+ .sort()
347
+ .join(',');
348
+ }
349
+
350
+ inc(labels = {}, value = 1) {
351
+ const key = this._labelKey(labels);
352
+ const current = this.metric.values.get(key) || 0;
353
+ this.metric.values.set(key, current + value);
354
+ return this;
355
+ }
356
+
357
+ get(labels = {}) {
358
+ const key = this._labelKey(labels);
359
+ return this.metric.values.get(key) || 0;
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Gauge metric
365
+ */
366
+ class GaugeMetric {
367
+ constructor(metric, defaultLabels = {}) {
368
+ this.metric = metric;
369
+ this.defaultLabels = defaultLabels;
370
+ }
371
+
372
+ _labelKey(labels = {}) {
373
+ const all = { ...this.defaultLabels, ...labels };
374
+ return Object.entries(all)
375
+ .map(([k, v]) => `${k}="${v}"`)
376
+ .sort()
377
+ .join(',');
378
+ }
379
+
380
+ set(value, labels = {}) {
381
+ const key = this._labelKey(labels);
382
+ this.metric.values.set(key, value);
383
+ return this;
384
+ }
385
+
386
+ inc(labels = {}, value = 1) {
387
+ const key = this._labelKey(labels);
388
+ const current = this.metric.values.get(key) || 0;
389
+ this.metric.values.set(key, current + value);
390
+ return this;
391
+ }
392
+
393
+ dec(labels = {}, value = 1) {
394
+ return this.inc(labels, -value);
395
+ }
396
+
397
+ get(labels = {}) {
398
+ const key = this._labelKey(labels);
399
+ return this.metric.values.get(key) || 0;
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Histogram metric
405
+ */
406
+ class HistogramMetric {
407
+ constructor(metric, defaultLabels = {}) {
408
+ this.metric = metric;
409
+ this.defaultLabels = defaultLabels;
410
+ }
411
+
412
+ _labelKey(labels = {}) {
413
+ const all = { ...this.defaultLabels, ...labels };
414
+ return Object.entries(all)
415
+ .map(([k, v]) => `${k}="${v}"`)
416
+ .sort()
417
+ .join(',');
418
+ }
419
+
420
+ observe(value, labels = {}) {
421
+ const key = this._labelKey(labels);
422
+ let data = this.metric.values.get(key);
423
+
424
+ if (!data) {
425
+ data = {
426
+ buckets: this.metric.buckets.map(() => 0),
427
+ sum: 0,
428
+ count: 0
429
+ };
430
+ this.metric.values.set(key, data);
431
+ }
432
+
433
+ data.sum += value;
434
+ data.count += 1;
435
+
436
+ for (let i = 0; i < this.metric.buckets.length; i++) {
437
+ if (value <= this.metric.buckets[i]) {
438
+ data.buckets[i] += 1;
439
+ }
440
+ }
441
+
442
+ return this;
443
+ }
444
+
445
+ get(labels = {}) {
446
+ const key = this._labelKey(labels);
447
+ return this.metric.values.get(key) || { buckets: [], sum: 0, count: 0 };
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Span for distributed tracing
453
+ */
454
+ class Span {
455
+ constructor(options) {
456
+ this.traceId = options.traceId || generateId(32);
457
+ this.spanId = options.spanId || generateId(16);
458
+ this.parentSpanId = options.parentSpanId || null;
459
+ this.name = options.name;
460
+ this.kind = options.kind || SpanKind.INTERNAL;
461
+ this.startTime = options.startTime || Date.now();
462
+ this.endTime = null;
463
+ this.status = TraceStatus.UNSET;
464
+ this.statusMessage = '';
465
+ this.attributes = options.attributes || {};
466
+ this.events = [];
467
+ this.links = options.links || [];
468
+ }
469
+
470
+ /**
471
+ * Set an attribute
472
+ */
473
+ setAttribute(key, value) {
474
+ this.attributes[key] = value;
475
+ return this;
476
+ }
477
+
478
+ /**
479
+ * Set multiple attributes
480
+ */
481
+ setAttributes(attributes) {
482
+ Object.assign(this.attributes, attributes);
483
+ return this;
484
+ }
485
+
486
+ /**
487
+ * Add an event
488
+ */
489
+ addEvent(name, attributes = {}) {
490
+ this.events.push({
491
+ name,
492
+ timestamp: Date.now(),
493
+ attributes
494
+ });
495
+ return this;
496
+ }
497
+
498
+ /**
499
+ * Set status
500
+ */
501
+ setStatus(status, message = '') {
502
+ this.status = status;
503
+ this.statusMessage = message;
504
+ return this;
505
+ }
506
+
507
+ /**
508
+ * End the span
509
+ */
510
+ end() {
511
+ this.endTime = Date.now();
512
+ return this;
513
+ }
514
+
515
+ /**
516
+ * Get duration in milliseconds
517
+ */
518
+ getDuration() {
519
+ if (!this.endTime) return null;
520
+ return this.endTime - this.startTime;
521
+ }
522
+
523
+ /**
524
+ * Get span context for propagation
525
+ */
526
+ getContext() {
527
+ return {
528
+ traceId: this.traceId,
529
+ spanId: this.spanId
530
+ };
531
+ }
532
+
533
+ toJSON() {
534
+ return {
535
+ traceId: this.traceId,
536
+ spanId: this.spanId,
537
+ parentSpanId: this.parentSpanId,
538
+ name: this.name,
539
+ kind: this.kind,
540
+ startTime: this.startTime,
541
+ endTime: this.endTime,
542
+ duration: this.getDuration(),
543
+ status: this.status,
544
+ statusMessage: this.statusMessage,
545
+ attributes: this.attributes,
546
+ events: this.events,
547
+ links: this.links
548
+ };
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Tracer for distributed tracing
554
+ */
555
+ class Tracer extends EventEmitter {
556
+ constructor(options = {}) {
557
+ super();
558
+ this.serviceName = options.serviceName || 'unknown';
559
+ this.version = options.version || '1.0.0';
560
+ this.spans = [];
561
+ this.exporters = options.exporters || [];
562
+ }
563
+
564
+ /**
565
+ * Start a new span
566
+ */
567
+ startSpan(name, options = {}) {
568
+ const span = new Span({
569
+ traceId: options.traceId,
570
+ parentSpanId: options.parentSpanId,
571
+ name,
572
+ kind: options.kind,
573
+ attributes: {
574
+ 'service.name': this.serviceName,
575
+ 'service.version': this.version,
576
+ ...options.attributes
577
+ },
578
+ links: options.links
579
+ });
580
+
581
+ this.spans.push(span);
582
+ this.emit('spanStarted', span);
583
+ return span;
584
+ }
585
+
586
+ /**
587
+ * Create a child span
588
+ */
589
+ startChildSpan(parent, name, options = {}) {
590
+ return this.startSpan(name, {
591
+ traceId: parent.traceId,
592
+ parentSpanId: parent.spanId,
593
+ ...options
594
+ });
595
+ }
596
+
597
+ /**
598
+ * End and export a span
599
+ */
600
+ endSpan(span) {
601
+ span.end();
602
+ this.emit('spanEnded', span);
603
+
604
+ for (const exporter of this.exporters) {
605
+ exporter.export(span);
606
+ }
607
+
608
+ return span;
609
+ }
610
+
611
+ /**
612
+ * Get all spans for a trace
613
+ */
614
+ getTrace(traceId) {
615
+ return this.spans.filter(s => s.traceId === traceId);
616
+ }
617
+
618
+ /**
619
+ * Add an exporter
620
+ */
621
+ addExporter(exporter) {
622
+ this.exporters.push(exporter);
623
+ return this;
624
+ }
625
+
626
+ /**
627
+ * Get all spans
628
+ */
629
+ getAllSpans() {
630
+ return this.spans.map(s => s.toJSON());
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Console exporter for traces
636
+ */
637
+ class ConsoleExporter {
638
+ constructor(options = {}) {
639
+ this.format = options.format || 'json';
640
+ }
641
+
642
+ export(span) {
643
+ if (this.format === 'json') {
644
+ console.log(JSON.stringify(span.toJSON()));
645
+ } else {
646
+ const duration = span.getDuration() || 0;
647
+ console.log(`[${span.traceId.slice(0, 8)}] ${span.name} (${duration}ms) - ${span.status}`);
648
+ }
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Memory exporter for testing
654
+ */
655
+ class MemoryExporter {
656
+ constructor() {
657
+ this.spans = [];
658
+ }
659
+
660
+ export(span) {
661
+ this.spans.push(span.toJSON());
662
+ }
663
+
664
+ getSpans() {
665
+ return this.spans;
666
+ }
667
+
668
+ clear() {
669
+ this.spans = [];
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Context propagation for correlation
675
+ */
676
+ class CorrelationContext {
677
+ constructor() {
678
+ this.values = new Map();
679
+ }
680
+
681
+ /**
682
+ * Set a value
683
+ */
684
+ set(key, value) {
685
+ this.values.set(key, value);
686
+ return this;
687
+ }
688
+
689
+ /**
690
+ * Get a value
691
+ */
692
+ get(key) {
693
+ return this.values.get(key);
694
+ }
695
+
696
+ /**
697
+ * Check if key exists
698
+ */
699
+ has(key) {
700
+ return this.values.has(key);
701
+ }
702
+
703
+ /**
704
+ * Get all values
705
+ */
706
+ getAll() {
707
+ return Object.fromEntries(this.values);
708
+ }
709
+
710
+ /**
711
+ * Create headers for HTTP propagation
712
+ */
713
+ toHeaders() {
714
+ const headers = {};
715
+
716
+ if (this.has('traceId')) {
717
+ headers['x-trace-id'] = this.get('traceId');
718
+ }
719
+ if (this.has('spanId')) {
720
+ headers['x-span-id'] = this.get('spanId');
721
+ }
722
+ if (this.has('correlationId')) {
723
+ headers['x-correlation-id'] = this.get('correlationId');
724
+ }
725
+
726
+ return headers;
727
+ }
728
+
729
+ /**
730
+ * Create from headers
731
+ */
732
+ static fromHeaders(headers) {
733
+ const ctx = new CorrelationContext();
734
+
735
+ if (headers['x-trace-id']) {
736
+ ctx.set('traceId', headers['x-trace-id']);
737
+ }
738
+ if (headers['x-span-id']) {
739
+ ctx.set('spanId', headers['x-span-id']);
740
+ }
741
+ if (headers['x-correlation-id']) {
742
+ ctx.set('correlationId', headers['x-correlation-id']);
743
+ }
744
+
745
+ return ctx;
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Unified Observability Provider
751
+ */
752
+ class ObservabilityProvider extends EventEmitter {
753
+ constructor(options = {}) {
754
+ super();
755
+ this.serviceName = options.serviceName || 'musubi-service';
756
+ this.version = options.version || '1.0.0';
757
+
758
+ this.logger = new Logger({
759
+ name: this.serviceName,
760
+ level: options.logLevel || LogLevel.INFO
761
+ });
762
+
763
+ this.metrics = new MetricsCollector({
764
+ name: this.serviceName,
765
+ prefix: options.metricsPrefix || ''
766
+ });
767
+
768
+ this.tracer = new Tracer({
769
+ serviceName: this.serviceName,
770
+ version: this.version
771
+ });
772
+
773
+ // Standard metrics
774
+ this._setupStandardMetrics();
775
+ }
776
+
777
+ /**
778
+ * Setup standard metrics
779
+ */
780
+ _setupStandardMetrics() {
781
+ // Request metrics
782
+ this.requestCounter = this.metrics.counter('http_requests_total', 'Total HTTP requests');
783
+ this.requestDuration = this.metrics.histogram('http_request_duration_seconds', 'HTTP request duration');
784
+ this.requestErrors = this.metrics.counter('http_request_errors_total', 'Total HTTP errors');
785
+
786
+ // Resource metrics
787
+ this.activeConnections = this.metrics.gauge('active_connections', 'Active connections');
788
+ }
789
+
790
+ /**
791
+ * Get the logger
792
+ */
793
+ getLogger(name) {
794
+ return this.logger.child({ component: name });
795
+ }
796
+
797
+ /**
798
+ * Get the metrics collector
799
+ */
800
+ getMetrics() {
801
+ return this.metrics;
802
+ }
803
+
804
+ /**
805
+ * Get the tracer
806
+ */
807
+ getTracer() {
808
+ return this.tracer;
809
+ }
810
+
811
+ /**
812
+ * Start a traced operation
813
+ */
814
+ trace(name, fn, options = {}) {
815
+ const span = this.tracer.startSpan(name, options);
816
+
817
+ try {
818
+ const result = fn(span);
819
+
820
+ if (result && typeof result.then === 'function') {
821
+ return result
822
+ .then(r => {
823
+ span.setStatus(TraceStatus.OK);
824
+ this.tracer.endSpan(span);
825
+ return r;
826
+ })
827
+ .catch(err => {
828
+ span.setStatus(TraceStatus.ERROR, err.message);
829
+ span.addEvent('exception', {
830
+ 'exception.type': err.name,
831
+ 'exception.message': err.message
832
+ });
833
+ this.tracer.endSpan(span);
834
+ throw err;
835
+ });
836
+ }
837
+
838
+ span.setStatus(TraceStatus.OK);
839
+ this.tracer.endSpan(span);
840
+ return result;
841
+ } catch (err) {
842
+ span.setStatus(TraceStatus.ERROR, err.message);
843
+ span.addEvent('exception', {
844
+ 'exception.type': err.name,
845
+ 'exception.message': err.message
846
+ });
847
+ this.tracer.endSpan(span);
848
+ throw err;
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Record an HTTP request
854
+ */
855
+ recordRequest(method, path, statusCode, duration) {
856
+ const labels = { method, path, status_code: statusCode.toString() };
857
+
858
+ this.requestCounter.inc(labels);
859
+ this.requestDuration.observe(duration / 1000, { method, path }); // Convert to seconds
860
+
861
+ if (statusCode >= 400) {
862
+ this.requestErrors.inc(labels);
863
+ }
864
+ }
865
+
866
+ /**
867
+ * Export all telemetry data
868
+ */
869
+ exportTelemetry() {
870
+ return {
871
+ service: {
872
+ name: this.serviceName,
873
+ version: this.version
874
+ },
875
+ metrics: this.metrics.toJSON(),
876
+ traces: this.tracer.getAllSpans()
877
+ };
878
+ }
879
+ }
880
+
881
+ /**
882
+ * Create a logger
883
+ */
884
+ function createLogger(options = {}) {
885
+ return new Logger(options);
886
+ }
887
+
888
+ /**
889
+ * Create a metrics collector
890
+ */
891
+ function createMetricsCollector(options = {}) {
892
+ return new MetricsCollector(options);
893
+ }
894
+
895
+ /**
896
+ * Create a tracer
897
+ */
898
+ function createTracer(options = {}) {
899
+ return new Tracer(options);
900
+ }
901
+
902
+ /**
903
+ * Create an observability provider
904
+ */
905
+ function createObservability(options = {}) {
906
+ return new ObservabilityProvider(options);
907
+ }
908
+
909
+ module.exports = {
910
+ // Classes
911
+ Logger,
912
+ ConsoleOutput,
913
+ FileOutput,
914
+ MetricsCollector,
915
+ CounterMetric,
916
+ GaugeMetric,
917
+ HistogramMetric,
918
+ Span,
919
+ Tracer,
920
+ ConsoleExporter,
921
+ MemoryExporter,
922
+ CorrelationContext,
923
+ ObservabilityProvider,
924
+
925
+ // Constants
926
+ LogLevel,
927
+ TraceStatus,
928
+ SpanKind,
929
+
930
+ // Factories
931
+ createLogger,
932
+ createMetricsCollector,
933
+ createTracer,
934
+ createObservability,
935
+
936
+ // Utilities
937
+ generateId
938
+ };