tlc-claude-code 1.4.8 → 1.4.9

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 (169) hide show
  1. package/package.json +1 -1
  2. package/server/index.js +229 -14
  3. package/server/lib/compliance/control-mapper.js +401 -0
  4. package/server/lib/compliance/control-mapper.test.js +117 -0
  5. package/server/lib/compliance/evidence-linker.js +296 -0
  6. package/server/lib/compliance/evidence-linker.test.js +121 -0
  7. package/server/lib/compliance/gdpr-checklist.js +416 -0
  8. package/server/lib/compliance/gdpr-checklist.test.js +131 -0
  9. package/server/lib/compliance/hipaa-checklist.js +277 -0
  10. package/server/lib/compliance/hipaa-checklist.test.js +101 -0
  11. package/server/lib/compliance/iso27001-checklist.js +287 -0
  12. package/server/lib/compliance/iso27001-checklist.test.js +99 -0
  13. package/server/lib/compliance/multi-framework-reporter.js +284 -0
  14. package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
  15. package/server/lib/compliance/pci-dss-checklist.js +214 -0
  16. package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
  17. package/server/lib/compliance/trust-centre.js +187 -0
  18. package/server/lib/compliance/trust-centre.test.js +93 -0
  19. package/server/lib/dashboard/api-server.js +155 -0
  20. package/server/lib/dashboard/api-server.test.js +155 -0
  21. package/server/lib/dashboard/health-api.js +199 -0
  22. package/server/lib/dashboard/health-api.test.js +122 -0
  23. package/server/lib/dashboard/notes-api.js +234 -0
  24. package/server/lib/dashboard/notes-api.test.js +134 -0
  25. package/server/lib/dashboard/router-api.js +176 -0
  26. package/server/lib/dashboard/router-api.test.js +132 -0
  27. package/server/lib/dashboard/tasks-api.js +289 -0
  28. package/server/lib/dashboard/tasks-api.test.js +161 -0
  29. package/server/lib/dashboard/tlc-introspection.js +197 -0
  30. package/server/lib/dashboard/tlc-introspection.test.js +138 -0
  31. package/server/lib/dashboard/version-api.js +222 -0
  32. package/server/lib/dashboard/version-api.test.js +112 -0
  33. package/server/lib/dashboard/websocket-server.js +104 -0
  34. package/server/lib/dashboard/websocket-server.test.js +118 -0
  35. package/server/lib/deploy/branch-classifier.js +163 -0
  36. package/server/lib/deploy/branch-classifier.test.js +164 -0
  37. package/server/lib/deploy/deployment-approval.js +299 -0
  38. package/server/lib/deploy/deployment-approval.test.js +296 -0
  39. package/server/lib/deploy/deployment-audit.js +374 -0
  40. package/server/lib/deploy/deployment-audit.test.js +307 -0
  41. package/server/lib/deploy/deployment-executor.js +335 -0
  42. package/server/lib/deploy/deployment-executor.test.js +329 -0
  43. package/server/lib/deploy/deployment-rules.js +163 -0
  44. package/server/lib/deploy/deployment-rules.test.js +188 -0
  45. package/server/lib/deploy/rollback-manager.js +379 -0
  46. package/server/lib/deploy/rollback-manager.test.js +321 -0
  47. package/server/lib/deploy/security-gates.js +236 -0
  48. package/server/lib/deploy/security-gates.test.js +222 -0
  49. package/server/lib/k8s/gitops-config.js +188 -0
  50. package/server/lib/k8s/gitops-config.test.js +59 -0
  51. package/server/lib/k8s/helm-generator.js +196 -0
  52. package/server/lib/k8s/helm-generator.test.js +59 -0
  53. package/server/lib/k8s/kustomize-generator.js +176 -0
  54. package/server/lib/k8s/kustomize-generator.test.js +58 -0
  55. package/server/lib/k8s/network-policy.js +114 -0
  56. package/server/lib/k8s/network-policy.test.js +53 -0
  57. package/server/lib/k8s/pod-security.js +114 -0
  58. package/server/lib/k8s/pod-security.test.js +55 -0
  59. package/server/lib/k8s/rbac-generator.js +132 -0
  60. package/server/lib/k8s/rbac-generator.test.js +57 -0
  61. package/server/lib/k8s/resource-manager.js +172 -0
  62. package/server/lib/k8s/resource-manager.test.js +60 -0
  63. package/server/lib/k8s/secrets-encryption.js +168 -0
  64. package/server/lib/k8s/secrets-encryption.test.js +49 -0
  65. package/server/lib/monitoring/alert-manager.js +238 -0
  66. package/server/lib/monitoring/alert-manager.test.js +106 -0
  67. package/server/lib/monitoring/health-check.js +226 -0
  68. package/server/lib/monitoring/health-check.test.js +176 -0
  69. package/server/lib/monitoring/incident-manager.js +230 -0
  70. package/server/lib/monitoring/incident-manager.test.js +98 -0
  71. package/server/lib/monitoring/log-aggregator.js +147 -0
  72. package/server/lib/monitoring/log-aggregator.test.js +89 -0
  73. package/server/lib/monitoring/metrics-collector.js +337 -0
  74. package/server/lib/monitoring/metrics-collector.test.js +172 -0
  75. package/server/lib/monitoring/status-page.js +214 -0
  76. package/server/lib/monitoring/status-page.test.js +105 -0
  77. package/server/lib/monitoring/uptime-monitor.js +194 -0
  78. package/server/lib/monitoring/uptime-monitor.test.js +109 -0
  79. package/server/lib/network/fail2ban-config.js +294 -0
  80. package/server/lib/network/fail2ban-config.test.js +275 -0
  81. package/server/lib/network/firewall-manager.js +252 -0
  82. package/server/lib/network/firewall-manager.test.js +254 -0
  83. package/server/lib/network/geoip-filter.js +282 -0
  84. package/server/lib/network/geoip-filter.test.js +264 -0
  85. package/server/lib/network/rate-limiter.js +229 -0
  86. package/server/lib/network/rate-limiter.test.js +293 -0
  87. package/server/lib/network/request-validator.js +351 -0
  88. package/server/lib/network/request-validator.test.js +345 -0
  89. package/server/lib/network/security-headers.js +251 -0
  90. package/server/lib/network/security-headers.test.js +283 -0
  91. package/server/lib/network/tls-config.js +210 -0
  92. package/server/lib/network/tls-config.test.js +248 -0
  93. package/server/lib/security/auth-security.js +369 -0
  94. package/server/lib/security/auth-security.test.js +448 -0
  95. package/server/lib/security/cis-benchmark.js +152 -0
  96. package/server/lib/security/cis-benchmark.test.js +137 -0
  97. package/server/lib/security/compose-templates.js +312 -0
  98. package/server/lib/security/compose-templates.test.js +229 -0
  99. package/server/lib/security/container-runtime.js +456 -0
  100. package/server/lib/security/container-runtime.test.js +503 -0
  101. package/server/lib/security/cors-validator.js +278 -0
  102. package/server/lib/security/cors-validator.test.js +310 -0
  103. package/server/lib/security/crypto-utils.js +253 -0
  104. package/server/lib/security/crypto-utils.test.js +409 -0
  105. package/server/lib/security/dockerfile-linter.js +459 -0
  106. package/server/lib/security/dockerfile-linter.test.js +483 -0
  107. package/server/lib/security/dockerfile-templates.js +278 -0
  108. package/server/lib/security/dockerfile-templates.test.js +164 -0
  109. package/server/lib/security/error-sanitizer.js +426 -0
  110. package/server/lib/security/error-sanitizer.test.js +331 -0
  111. package/server/lib/security/headers-generator.js +368 -0
  112. package/server/lib/security/headers-generator.test.js +398 -0
  113. package/server/lib/security/image-scanner.js +83 -0
  114. package/server/lib/security/image-scanner.test.js +106 -0
  115. package/server/lib/security/input-validator.js +352 -0
  116. package/server/lib/security/input-validator.test.js +330 -0
  117. package/server/lib/security/network-policy.js +174 -0
  118. package/server/lib/security/network-policy.test.js +164 -0
  119. package/server/lib/security/output-encoder.js +237 -0
  120. package/server/lib/security/output-encoder.test.js +276 -0
  121. package/server/lib/security/path-validator.js +359 -0
  122. package/server/lib/security/path-validator.test.js +293 -0
  123. package/server/lib/security/query-builder.js +421 -0
  124. package/server/lib/security/query-builder.test.js +318 -0
  125. package/server/lib/security/secret-detector.js +290 -0
  126. package/server/lib/security/secret-detector.test.js +354 -0
  127. package/server/lib/security/secrets-validator.js +137 -0
  128. package/server/lib/security/secrets-validator.test.js +120 -0
  129. package/server/lib/security-testing/dast-runner.js +154 -0
  130. package/server/lib/security-testing/dast-runner.test.js +62 -0
  131. package/server/lib/security-testing/dependency-scanner.js +172 -0
  132. package/server/lib/security-testing/dependency-scanner.test.js +64 -0
  133. package/server/lib/security-testing/pentest-runner.js +230 -0
  134. package/server/lib/security-testing/pentest-runner.test.js +60 -0
  135. package/server/lib/security-testing/sast-runner.js +136 -0
  136. package/server/lib/security-testing/sast-runner.test.js +62 -0
  137. package/server/lib/security-testing/secret-scanner.js +153 -0
  138. package/server/lib/security-testing/secret-scanner.test.js +66 -0
  139. package/server/lib/security-testing/security-gate.js +216 -0
  140. package/server/lib/security-testing/security-gate.test.js +115 -0
  141. package/server/lib/security-testing/security-reporter.js +303 -0
  142. package/server/lib/security-testing/security-reporter.test.js +114 -0
  143. package/server/lib/standards/audit-checker.js +546 -0
  144. package/server/lib/standards/audit-checker.test.js +415 -0
  145. package/server/lib/standards/cleanup-executor.js +452 -0
  146. package/server/lib/standards/cleanup-executor.test.js +293 -0
  147. package/server/lib/standards/refactor-stepper.js +425 -0
  148. package/server/lib/standards/refactor-stepper.test.js +298 -0
  149. package/server/lib/standards/standards-injector.js +167 -0
  150. package/server/lib/standards/standards-injector.test.js +232 -0
  151. package/server/lib/user-management.test.js +284 -0
  152. package/server/lib/vps/backup-manager.js +157 -0
  153. package/server/lib/vps/backup-manager.test.js +59 -0
  154. package/server/lib/vps/caddy-config.js +159 -0
  155. package/server/lib/vps/caddy-config.test.js +48 -0
  156. package/server/lib/vps/compose-orchestrator.js +219 -0
  157. package/server/lib/vps/compose-orchestrator.test.js +50 -0
  158. package/server/lib/vps/database-config.js +208 -0
  159. package/server/lib/vps/database-config.test.js +47 -0
  160. package/server/lib/vps/deploy-script.js +211 -0
  161. package/server/lib/vps/deploy-script.test.js +53 -0
  162. package/server/lib/vps/secrets-manager.js +148 -0
  163. package/server/lib/vps/secrets-manager.test.js +58 -0
  164. package/server/lib/vps/server-hardening.js +174 -0
  165. package/server/lib/vps/server-hardening.test.js +70 -0
  166. package/server/package-lock.json +19 -0
  167. package/server/package.json +1 -0
  168. package/server/templates/CLAUDE.md +37 -0
  169. package/server/templates/CODING-STANDARDS.md +408 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Incident Manager Tests
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import {
6
+ createIncident,
7
+ generateTimeline,
8
+ linkAlerts,
9
+ updateStatus,
10
+ generatePostMortem,
11
+ calculateMttr,
12
+ INCIDENT_STATUS,
13
+ createIncidentManager,
14
+ } from './incident-manager.js';
15
+
16
+ describe('incident-manager', () => {
17
+ describe('INCIDENT_STATUS', () => {
18
+ it('defines status constants', () => {
19
+ expect(INCIDENT_STATUS.OPEN).toBe('open');
20
+ expect(INCIDENT_STATUS.INVESTIGATING).toBe('investigating');
21
+ expect(INCIDENT_STATUS.RESOLVED).toBe('resolved');
22
+ });
23
+ });
24
+
25
+ describe('createIncident', () => {
26
+ it('creates incident from alert', () => {
27
+ const incident = createIncident({ alert: { id: 'a1', title: 'DB Down' } });
28
+ expect(incident.id).toBeDefined();
29
+ expect(incident.title).toBe('DB Down');
30
+ expect(incident.status).toBe('open');
31
+ });
32
+ });
33
+
34
+ describe('generateTimeline', () => {
35
+ it('generates timeline from events', () => {
36
+ const timeline = generateTimeline({
37
+ events: [
38
+ { type: 'alert', timestamp: new Date('2024-01-01T10:00:00Z') },
39
+ { type: 'acknowledge', timestamp: new Date('2024-01-01T10:05:00Z') },
40
+ ],
41
+ });
42
+ expect(timeline.length).toBe(2);
43
+ });
44
+ });
45
+
46
+ describe('linkAlerts', () => {
47
+ it('links related alerts to incident', () => {
48
+ const incident = createIncident({ title: 'Outage' });
49
+ const linked = linkAlerts(incident, [{ id: 'a1' }, { id: 'a2' }]);
50
+ expect(linked.alerts).toHaveLength(2);
51
+ });
52
+ });
53
+
54
+ describe('updateStatus', () => {
55
+ it('updates incident status', () => {
56
+ const incident = createIncident({ title: 'Test' });
57
+ const updated = updateStatus(incident, 'investigating');
58
+ expect(updated.status).toBe('investigating');
59
+ });
60
+
61
+ it('records status history', () => {
62
+ const incident = createIncident({ title: 'Test' });
63
+ const updated = updateStatus(incident, 'resolved');
64
+ expect(updated.statusHistory.length).toBeGreaterThan(0);
65
+ });
66
+ });
67
+
68
+ describe('generatePostMortem', () => {
69
+ it('generates post-mortem template', () => {
70
+ const postMortem = generatePostMortem({
71
+ incident: { id: 'i1', title: 'Outage' },
72
+ });
73
+ expect(postMortem).toContain('# Post-Mortem');
74
+ expect(postMortem).toContain('Outage');
75
+ });
76
+ });
77
+
78
+ describe('calculateMttr', () => {
79
+ it('calculates mean time to resolve', () => {
80
+ const incidents = [
81
+ { createdAt: new Date('2024-01-01T10:00:00Z'), resolvedAt: new Date('2024-01-01T11:00:00Z') },
82
+ { createdAt: new Date('2024-01-02T10:00:00Z'), resolvedAt: new Date('2024-01-02T10:30:00Z') },
83
+ ];
84
+ const mttr = calculateMttr(incidents);
85
+ expect(mttr).toBe(45 * 60 * 1000); // 45 minutes
86
+ });
87
+ });
88
+
89
+ describe('createIncidentManager', () => {
90
+ it('creates manager with methods', () => {
91
+ const manager = createIncidentManager();
92
+ expect(manager.create).toBeDefined();
93
+ expect(manager.update).toBeDefined();
94
+ expect(manager.resolve).toBeDefined();
95
+ expect(manager.getMetrics).toBeDefined();
96
+ });
97
+ });
98
+ });
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Log Aggregator
3
+ * Collects and aggregates logs from multiple sources
4
+ */
5
+
6
+ /**
7
+ * Log level constants
8
+ */
9
+ export const LOG_LEVELS = {
10
+ ERROR: 'error',
11
+ WARN: 'warn',
12
+ INFO: 'info',
13
+ DEBUG: 'debug',
14
+ };
15
+
16
+ /**
17
+ * Structures a log entry as JSON with timestamp
18
+ * @param {Object} entry - Log entry with message and level
19
+ * @returns {Object} Structured log entry
20
+ */
21
+ export function structureLog(entry) {
22
+ return {
23
+ ...entry,
24
+ timestamp: entry.timestamp || new Date().toISOString(),
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Filters sensitive data from log entries
30
+ * @param {Object} log - Log entry to filter
31
+ * @returns {Object} Filtered log entry
32
+ */
33
+ export function filterSensitiveData(log) {
34
+ const sensitivePatterns = [
35
+ /password\s*[=:]\s*\S+/gi,
36
+ /api_key\s*[=:]\s*\S+/gi,
37
+ /secret\s*[=:]\s*\S+/gi,
38
+ /token\s*[=:]\s*\S+/gi,
39
+ /authorization\s*[=:]\s*\S+/gi,
40
+ ];
41
+
42
+ let filteredMessage = log.message;
43
+ for (const pattern of sensitivePatterns) {
44
+ filteredMessage = filteredMessage.replace(pattern, (match) => {
45
+ const [key] = match.split(/[=:]/);
46
+ return `${key}=[REDACTED]`;
47
+ });
48
+ }
49
+
50
+ return {
51
+ ...log,
52
+ message: filteredMessage,
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Aggregates logs from multiple sources
58
+ * @param {Array} sources - Array of log sources with logs
59
+ * @returns {Array} Aggregated logs
60
+ */
61
+ export function aggregateLogs(sources) {
62
+ const aggregated = [];
63
+ for (const source of sources) {
64
+ for (const log of source.logs) {
65
+ aggregated.push({
66
+ ...log,
67
+ source: source.source,
68
+ });
69
+ }
70
+ }
71
+ return aggregated;
72
+ }
73
+
74
+ /**
75
+ * Determines if logs should be rotated
76
+ * @param {Object} options - Rotation options
77
+ * @param {number} options.maxSize - Maximum size in bytes
78
+ * @param {number} options.currentSize - Current size in bytes
79
+ * @param {number} options.maxAge - Maximum age in milliseconds
80
+ * @param {number} options.age - Current age in milliseconds
81
+ * @returns {Object} Rotation result
82
+ */
83
+ export function rotateLogs(options) {
84
+ const { maxSize, currentSize, maxAge, age } = options;
85
+
86
+ const shouldRotateBySize = maxSize && currentSize && currentSize > maxSize;
87
+ const shouldRotateByAge = maxAge && age && age > maxAge;
88
+
89
+ return {
90
+ rotated: shouldRotateBySize || shouldRotateByAge,
91
+ reason: shouldRotateBySize ? 'size' : shouldRotateByAge ? 'age' : null,
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Exports logs to specified format
97
+ * @param {Array} logs - Logs to export
98
+ * @param {string} format - Output format (json or ndjson)
99
+ * @returns {string} Exported logs
100
+ */
101
+ export function exportLogs(logs, format) {
102
+ if (format === 'ndjson') {
103
+ return logs.map(log => JSON.stringify(log)).join('\n');
104
+ }
105
+ return JSON.stringify(logs);
106
+ }
107
+
108
+ /**
109
+ * Creates a log aggregator instance
110
+ * @returns {Object} Log aggregator with methods
111
+ */
112
+ export function createLogAggregator() {
113
+ const sources = [];
114
+ const logs = [];
115
+
116
+ return {
117
+ /**
118
+ * Adds a log source
119
+ * @param {Object} source - Source configuration
120
+ */
121
+ addSource(source) {
122
+ sources.push(source);
123
+ },
124
+
125
+ /**
126
+ * Collects logs from all sources
127
+ * @returns {Array} Collected logs
128
+ */
129
+ collect() {
130
+ const collected = aggregateLogs(sources.map(s => ({
131
+ source: s.name,
132
+ logs: s.getLogs ? s.getLogs() : [],
133
+ })));
134
+ logs.push(...collected);
135
+ return collected;
136
+ },
137
+
138
+ /**
139
+ * Exports all collected logs
140
+ * @param {string} format - Export format
141
+ * @returns {string} Exported logs
142
+ */
143
+ export(format = 'json') {
144
+ return exportLogs(logs, format);
145
+ },
146
+ };
147
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Log Aggregator Tests
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import {
6
+ aggregateLogs,
7
+ structureLog,
8
+ filterSensitiveData,
9
+ rotateLogs,
10
+ exportLogs,
11
+ LOG_LEVELS,
12
+ createLogAggregator,
13
+ } from './log-aggregator.js';
14
+
15
+ describe('log-aggregator', () => {
16
+ describe('LOG_LEVELS', () => {
17
+ it('defines level constants', () => {
18
+ expect(LOG_LEVELS.ERROR).toBe('error');
19
+ expect(LOG_LEVELS.WARN).toBe('warn');
20
+ expect(LOG_LEVELS.INFO).toBe('info');
21
+ expect(LOG_LEVELS.DEBUG).toBe('debug');
22
+ });
23
+ });
24
+
25
+ describe('structureLog', () => {
26
+ it('structures log as JSON', () => {
27
+ const log = structureLog({ message: 'Test', level: 'info' });
28
+ expect(log.message).toBe('Test');
29
+ expect(log.level).toBe('info');
30
+ expect(log.timestamp).toBeDefined();
31
+ });
32
+ });
33
+
34
+ describe('filterSensitiveData', () => {
35
+ it('redacts passwords', () => {
36
+ const log = filterSensitiveData({ message: 'password=secret123' });
37
+ expect(log.message).not.toContain('secret123');
38
+ expect(log.message).toContain('[REDACTED]');
39
+ });
40
+
41
+ it('redacts API keys', () => {
42
+ const log = filterSensitiveData({ message: 'api_key: sk-abc123' });
43
+ expect(log.message).not.toContain('sk-abc123');
44
+ });
45
+ });
46
+
47
+ describe('aggregateLogs', () => {
48
+ it('aggregates from multiple sources', () => {
49
+ const logs = aggregateLogs([
50
+ { source: 'app', logs: [{ message: 'a' }] },
51
+ { source: 'db', logs: [{ message: 'b' }] },
52
+ ]);
53
+ expect(logs.length).toBe(2);
54
+ });
55
+ });
56
+
57
+ describe('rotateLogs', () => {
58
+ it('rotates by size', () => {
59
+ const result = rotateLogs({ maxSize: 1000, currentSize: 1500 });
60
+ expect(result.rotated).toBe(true);
61
+ });
62
+
63
+ it('rotates by time', () => {
64
+ const result = rotateLogs({ maxAge: 86400000, age: 90000000 });
65
+ expect(result.rotated).toBe(true);
66
+ });
67
+ });
68
+
69
+ describe('exportLogs', () => {
70
+ it('exports to JSON', () => {
71
+ const output = exportLogs([{ message: 'test' }], 'json');
72
+ expect(JSON.parse(output)).toHaveLength(1);
73
+ });
74
+
75
+ it('exports to NDJSON', () => {
76
+ const output = exportLogs([{ message: 'a' }, { message: 'b' }], 'ndjson');
77
+ expect(output.split('\n').filter(Boolean)).toHaveLength(2);
78
+ });
79
+ });
80
+
81
+ describe('createLogAggregator', () => {
82
+ it('creates aggregator with methods', () => {
83
+ const aggregator = createLogAggregator();
84
+ expect(aggregator.addSource).toBeDefined();
85
+ expect(aggregator.collect).toBeDefined();
86
+ expect(aggregator.export).toBeDefined();
87
+ });
88
+ });
89
+ });
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Metrics Collector
3
+ * Prometheus format metrics collection and formatting
4
+ */
5
+
6
+ export const METRIC_TYPES = {
7
+ COUNTER: 'counter',
8
+ HISTOGRAM: 'histogram',
9
+ GAUGE: 'gauge',
10
+ };
11
+
12
+ /**
13
+ * Create a counter metric
14
+ * @param {string} name - Metric name
15
+ * @param {Object} options - Configuration options
16
+ * @param {Array} options.labels - Label names
17
+ * @returns {Object} Counter with inc and get methods
18
+ */
19
+ export function createCounter(name, options = {}) {
20
+ const { labels = [] } = options;
21
+ let value = 0;
22
+ const labeledValues = new Map();
23
+
24
+ function getLabelKey(labelValues) {
25
+ return JSON.stringify(labelValues);
26
+ }
27
+
28
+ return {
29
+ inc(amountOrLabels = 1) {
30
+ if (typeof amountOrLabels === 'number') {
31
+ value += amountOrLabels;
32
+ } else if (typeof amountOrLabels === 'object' && labels.length > 0) {
33
+ const key = getLabelKey(amountOrLabels);
34
+ labeledValues.set(key, (labeledValues.get(key) || 0) + 1);
35
+ } else {
36
+ value += 1;
37
+ }
38
+ },
39
+
40
+ get(labelValues) {
41
+ if (labelValues && labels.length > 0) {
42
+ const key = getLabelKey(labelValues);
43
+ return labeledValues.get(key) || 0;
44
+ }
45
+ return value;
46
+ },
47
+
48
+ getName() {
49
+ return name;
50
+ },
51
+
52
+ getType() {
53
+ return METRIC_TYPES.COUNTER;
54
+ },
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Create a histogram metric
60
+ * @param {string} name - Metric name
61
+ * @param {Object} options - Configuration options
62
+ * @param {Array} options.buckets - Bucket boundaries
63
+ * @returns {Object} Histogram with observe and get methods
64
+ */
65
+ export function createHistogram(name, options = {}) {
66
+ const { buckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] } = options;
67
+ const observations = [];
68
+ let sum = 0;
69
+
70
+ return {
71
+ observe(value) {
72
+ observations.push(value);
73
+ sum += value;
74
+ },
75
+
76
+ getCount() {
77
+ return observations.length;
78
+ },
79
+
80
+ getSum() {
81
+ return sum;
82
+ },
83
+
84
+ getPercentile(percentile) {
85
+ if (observations.length === 0) return 0;
86
+
87
+ const sorted = [...observations].sort((a, b) => a - b);
88
+ const index = Math.floor((percentile / 100) * sorted.length);
89
+ return sorted[Math.min(index, sorted.length - 1)];
90
+ },
91
+
92
+ getBuckets() {
93
+ const result = {};
94
+ for (const bucket of buckets) {
95
+ result[bucket] = observations.filter((v) => v <= bucket).length;
96
+ }
97
+ return result;
98
+ },
99
+
100
+ getName() {
101
+ return name;
102
+ },
103
+
104
+ getType() {
105
+ return METRIC_TYPES.HISTOGRAM;
106
+ },
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Create a gauge metric
112
+ * @param {string} name - Metric name
113
+ * @param {Object} options - Configuration options
114
+ * @returns {Object} Gauge with set, inc, dec, and get methods
115
+ */
116
+ export function createGauge(name, options = {}) {
117
+ let value = 0;
118
+
119
+ return {
120
+ set(newValue) {
121
+ value = newValue;
122
+ },
123
+
124
+ inc(amount = 1) {
125
+ value += amount;
126
+ },
127
+
128
+ dec(amount = 1) {
129
+ value -= amount;
130
+ },
131
+
132
+ get() {
133
+ return value;
134
+ },
135
+
136
+ getName() {
137
+ return name;
138
+ },
139
+
140
+ getType() {
141
+ return METRIC_TYPES.GAUGE;
142
+ },
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Collect system metrics
148
+ * @param {Object} options - Configuration options
149
+ * @param {boolean} options.cpu - Include CPU metrics
150
+ * @param {boolean} options.memory - Include memory metrics
151
+ * @param {boolean} options.eventLoop - Include event loop metrics
152
+ * @returns {Object} Collected metrics
153
+ */
154
+ export async function collectMetrics(options = {}) {
155
+ const { cpu = false, memory = false, eventLoop = false } = options;
156
+ const metrics = {};
157
+
158
+ if (cpu) {
159
+ const cpuUsage = process.cpuUsage();
160
+ metrics.cpu = {
161
+ user: cpuUsage.user,
162
+ system: cpuUsage.system,
163
+ };
164
+ }
165
+
166
+ if (memory) {
167
+ const memUsage = process.memoryUsage();
168
+ metrics.memory = {
169
+ heapUsed: memUsage.heapUsed,
170
+ heapTotal: memUsage.heapTotal,
171
+ external: memUsage.external,
172
+ rss: memUsage.rss,
173
+ };
174
+ }
175
+
176
+ if (eventLoop) {
177
+ // Measure event loop lag
178
+ const start = Date.now();
179
+ await new Promise((resolve) => setImmediate(resolve));
180
+ const lag = Date.now() - start;
181
+ metrics.eventLoop = {
182
+ lag,
183
+ };
184
+ }
185
+
186
+ return metrics;
187
+ }
188
+
189
+ /**
190
+ * Format metric in Prometheus format
191
+ * @param {Object} metric - Metric to format
192
+ * @param {string} metric.name - Metric name
193
+ * @param {string} metric.type - Metric type
194
+ * @param {number} metric.value - Metric value (for counter/gauge)
195
+ * @param {Object} metric.buckets - Bucket values (for histogram)
196
+ * @param {number} metric.sum - Sum (for histogram)
197
+ * @param {number} metric.count - Count (for histogram)
198
+ * @param {Object} metric.labels - Labels
199
+ * @returns {string} Prometheus formatted string
200
+ */
201
+ export function formatPrometheus(metric) {
202
+ const { name, type, value, buckets, sum, count, labels } = metric;
203
+ const lines = [];
204
+
205
+ // Type declaration
206
+ lines.push(`# TYPE ${name} ${type}`);
207
+
208
+ // Format labels
209
+ let labelStr = '';
210
+ if (labels && Object.keys(labels).length > 0) {
211
+ const labelParts = Object.entries(labels).map(([k, v]) => `${k}="${v}"`);
212
+ labelStr = `{${labelParts.join(',')}}`;
213
+ }
214
+
215
+ if (type === 'histogram') {
216
+ // Histogram buckets
217
+ for (const [le, bucketCount] of Object.entries(buckets)) {
218
+ const bucketLabel = labelStr ? labelStr.replace('}', `,le="${le}"}`) : `{le="${le}"}`;
219
+ lines.push(`${name}_bucket${bucketLabel} ${bucketCount}`);
220
+ }
221
+ // Add +Inf bucket
222
+ const infLabel = labelStr ? labelStr.replace('}', ',le="+Inf"}') : '{le="+Inf"}';
223
+ lines.push(`${name}_bucket${infLabel} ${count}`);
224
+ lines.push(`${name}_sum${labelStr} ${sum}`);
225
+ lines.push(`${name}_count${labelStr} ${count}`);
226
+ } else {
227
+ // Counter or Gauge
228
+ lines.push(`${name}${labelStr} ${value}`);
229
+ }
230
+
231
+ return lines.join('\n');
232
+ }
233
+
234
+ /**
235
+ * Create a metrics collector
236
+ * @param {Object} options - Configuration options
237
+ * @param {number} options.retentionMs - Retention period in milliseconds
238
+ * @returns {Object} Metrics collector
239
+ */
240
+ export function createMetricsCollector(options = {}) {
241
+ const { retentionMs = 3600000 } = options;
242
+
243
+ const counters = new Map();
244
+ const histograms = new Map();
245
+ const gauges = new Map();
246
+ const requestMetrics = [];
247
+
248
+ // Create default request metrics
249
+ const requestCounter = createCounter('http_requests_total', { labels: ['method', 'path', 'status'] });
250
+ const requestDuration = createHistogram('http_request_duration_seconds');
251
+
252
+ return {
253
+ counter(name, opts) {
254
+ if (!counters.has(name)) {
255
+ counters.set(name, createCounter(name, opts));
256
+ }
257
+ return counters.get(name);
258
+ },
259
+
260
+ histogram(name, opts) {
261
+ if (!histograms.has(name)) {
262
+ histograms.set(name, createHistogram(name, opts));
263
+ }
264
+ return histograms.get(name);
265
+ },
266
+
267
+ gauge(name, opts) {
268
+ if (!gauges.has(name)) {
269
+ gauges.set(name, createGauge(name, opts));
270
+ }
271
+ return gauges.get(name);
272
+ },
273
+
274
+ trackRequest({ method, path, status, duration }) {
275
+ requestCounter.inc({ method, path, status: String(status) });
276
+ requestDuration.observe(duration);
277
+ requestMetrics.push({
278
+ method,
279
+ path,
280
+ status,
281
+ duration,
282
+ timestamp: Date.now(),
283
+ });
284
+
285
+ // Clean up old metrics based on retention
286
+ const cutoff = Date.now() - retentionMs;
287
+ while (requestMetrics.length > 0 && requestMetrics[0].timestamp < cutoff) {
288
+ requestMetrics.shift();
289
+ }
290
+ },
291
+
292
+ collect() {
293
+ return {
294
+ counters: Object.fromEntries(counters),
295
+ histograms: Object.fromEntries(histograms),
296
+ gauges: Object.fromEntries(gauges),
297
+ requests: requestMetrics,
298
+ };
299
+ },
300
+
301
+ format() {
302
+ const output = [];
303
+
304
+ for (const [name, counter] of counters) {
305
+ output.push(formatPrometheus({
306
+ name,
307
+ type: 'counter',
308
+ value: counter.get(),
309
+ }));
310
+ }
311
+
312
+ for (const [name, histogram] of histograms) {
313
+ output.push(formatPrometheus({
314
+ name,
315
+ type: 'histogram',
316
+ buckets: histogram.getBuckets(),
317
+ sum: histogram.getSum(),
318
+ count: histogram.getCount(),
319
+ }));
320
+ }
321
+
322
+ for (const [name, gauge] of gauges) {
323
+ output.push(formatPrometheus({
324
+ name,
325
+ type: 'gauge',
326
+ value: gauge.get(),
327
+ }));
328
+ }
329
+
330
+ return output.join('\n\n');
331
+ },
332
+
333
+ getRetention() {
334
+ return retentionMs;
335
+ },
336
+ };
337
+ }