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,226 @@
1
+ /**
2
+ * Health Check Manager
3
+ * Liveness/readiness probes for container orchestration
4
+ */
5
+
6
+ export const HEALTH_STATUS = {
7
+ HEALTHY: 'healthy',
8
+ UNHEALTHY: 'unhealthy',
9
+ DEGRADED: 'degraded',
10
+ };
11
+
12
+ /**
13
+ * Create a liveness probe that checks if the process is running
14
+ * @param {Object} options - Configuration options
15
+ * @param {boolean} options.includeMemory - Include memory usage in response
16
+ * @returns {Object} Liveness probe with check method
17
+ */
18
+ export function createLivenessProbe(options = {}) {
19
+ const { includeMemory = false } = options;
20
+
21
+ return {
22
+ async check() {
23
+ const result = {
24
+ status: HEALTH_STATUS.HEALTHY,
25
+ pid: process.pid,
26
+ uptime: process.uptime(),
27
+ };
28
+
29
+ if (includeMemory) {
30
+ result.memory = process.memoryUsage();
31
+ }
32
+
33
+ return result;
34
+ },
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Create a readiness probe that checks dependencies
40
+ * @param {Object} options - Configuration options
41
+ * @param {Array} options.checks - Array of { name, check } objects
42
+ * @param {number} options.timeout - Timeout for each check in ms
43
+ * @returns {Object} Readiness probe with check method
44
+ */
45
+ export function createReadinessProbe(options = {}) {
46
+ const { checks = [], timeout = 5000 } = options;
47
+
48
+ return {
49
+ async check() {
50
+ const failed = [];
51
+ const results = {};
52
+
53
+ for (const { name, check } of checks) {
54
+ try {
55
+ const checkPromise = check();
56
+ const timeoutPromise = new Promise((_, reject) =>
57
+ setTimeout(() => reject(new Error('timeout')), timeout)
58
+ );
59
+
60
+ const result = await Promise.race([checkPromise, timeoutPromise]);
61
+ results[name] = result;
62
+
63
+ if (!result) {
64
+ failed.push(name);
65
+ }
66
+ } catch (error) {
67
+ results[name] = false;
68
+ failed.push(name);
69
+ }
70
+ }
71
+
72
+ return {
73
+ status: failed.length === 0 ? HEALTH_STATUS.HEALTHY : HEALTH_STATUS.UNHEALTHY,
74
+ checks: results,
75
+ failed,
76
+ };
77
+ },
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Create a deep health check for detailed verification
83
+ * @param {Object} options - Configuration options
84
+ * @param {Object} options.db - Database client
85
+ * @param {Object} options.cache - Cache client
86
+ * @param {Function} options.fetch - Fetch function for external services
87
+ * @returns {Object} Deep health check with various check methods
88
+ */
89
+ export function createDeepHealthCheck(options = {}) {
90
+ const { db, cache, fetch: fetchFn } = options;
91
+
92
+ return {
93
+ async checkDatabase() {
94
+ if (!db) {
95
+ return { healthy: false, error: 'No database configured' };
96
+ }
97
+
98
+ try {
99
+ await db.query('SELECT 1');
100
+ return { healthy: true };
101
+ } catch (error) {
102
+ return { healthy: false, error: error.message };
103
+ }
104
+ },
105
+
106
+ async checkCache() {
107
+ if (!cache) {
108
+ return { healthy: false, error: 'No cache configured' };
109
+ }
110
+
111
+ try {
112
+ const result = await cache.ping();
113
+ return { healthy: result === 'PONG' };
114
+ } catch (error) {
115
+ return { healthy: false, error: error.message };
116
+ }
117
+ },
118
+
119
+ async checkExternal(url) {
120
+ if (!fetchFn) {
121
+ return { healthy: false, error: 'No fetch configured' };
122
+ }
123
+
124
+ try {
125
+ const response = await fetchFn(url);
126
+ return { healthy: response.ok };
127
+ } catch (error) {
128
+ return { healthy: false, error: error.message };
129
+ }
130
+ },
131
+
132
+ async getStatus() {
133
+ // Return status without sensitive info
134
+ return {
135
+ database: db ? 'configured' : 'not configured',
136
+ cache: cache ? 'configured' : 'not configured',
137
+ };
138
+ },
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Run all configured health checks
144
+ * @param {Object} options - Configuration options
145
+ * @param {boolean} options.liveness - Include liveness check
146
+ * @param {boolean} options.readiness - Include readiness check
147
+ * @param {Array} options.checks - Additional checks to run
148
+ * @returns {Object} Health check results
149
+ */
150
+ export async function runHealthCheck(options = {}) {
151
+ const { liveness = false, readiness = false, checks = [] } = options;
152
+ const result = {};
153
+ let allHealthy = true;
154
+
155
+ if (liveness) {
156
+ const livenessProbe = createLivenessProbe();
157
+ result.liveness = await livenessProbe.check();
158
+ if (result.liveness.status !== HEALTH_STATUS.HEALTHY) {
159
+ allHealthy = false;
160
+ }
161
+ }
162
+
163
+ if (readiness) {
164
+ const readinessProbe = createReadinessProbe({ checks });
165
+ result.readiness = await readinessProbe.check();
166
+ if (result.readiness.status !== HEALTH_STATUS.HEALTHY) {
167
+ allHealthy = false;
168
+ }
169
+ }
170
+
171
+ // Run individual checks if no liveness/readiness specified
172
+ if (!liveness && !readiness && checks.length > 0) {
173
+ for (const { name, check } of checks) {
174
+ try {
175
+ const checkResult = await check();
176
+ result[name] = checkResult;
177
+ if (!checkResult) {
178
+ allHealthy = false;
179
+ }
180
+ } catch (error) {
181
+ result[name] = false;
182
+ allHealthy = false;
183
+ }
184
+ }
185
+ }
186
+
187
+ result.overall = allHealthy ? HEALTH_STATUS.HEALTHY : HEALTH_STATUS.UNHEALTHY;
188
+
189
+ return result;
190
+ }
191
+
192
+ /**
193
+ * Create a health check manager
194
+ * @param {Object} options - Configuration options
195
+ * @param {number} options.interval - Check interval in ms
196
+ * @returns {Object} Health check manager
197
+ */
198
+ export function createHealthCheckManager(options = {}) {
199
+ const { interval = 30000 } = options;
200
+ const checks = [];
201
+
202
+ const livenessProbe = createLivenessProbe();
203
+
204
+ return {
205
+ addCheck(check) {
206
+ checks.push(check);
207
+ },
208
+
209
+ async runAll() {
210
+ return runHealthCheck({ liveness: true, readiness: true, checks });
211
+ },
212
+
213
+ async getLiveness() {
214
+ return livenessProbe.check();
215
+ },
216
+
217
+ async getReadiness() {
218
+ const readinessProbe = createReadinessProbe({ checks });
219
+ return readinessProbe.check();
220
+ },
221
+
222
+ getInterval() {
223
+ return interval;
224
+ },
225
+ };
226
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Health Check Manager Tests
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import {
6
+ createLivenessProbe,
7
+ createReadinessProbe,
8
+ createDeepHealthCheck,
9
+ runHealthCheck,
10
+ HEALTH_STATUS,
11
+ createHealthCheckManager,
12
+ } from './health-check.js';
13
+
14
+ describe('health-check', () => {
15
+ describe('HEALTH_STATUS', () => {
16
+ it('defines status constants', () => {
17
+ expect(HEALTH_STATUS.HEALTHY).toBe('healthy');
18
+ expect(HEALTH_STATUS.UNHEALTHY).toBe('unhealthy');
19
+ expect(HEALTH_STATUS.DEGRADED).toBe('degraded');
20
+ });
21
+ });
22
+
23
+ describe('createLivenessProbe', () => {
24
+ it('returns healthy when process is running', async () => {
25
+ const probe = createLivenessProbe();
26
+ const result = await probe.check();
27
+
28
+ expect(result.status).toBe('healthy');
29
+ expect(result.pid).toBeDefined();
30
+ });
31
+
32
+ it('includes uptime', async () => {
33
+ const probe = createLivenessProbe();
34
+ const result = await probe.check();
35
+
36
+ expect(result.uptime).toBeDefined();
37
+ });
38
+
39
+ it('includes memory usage', async () => {
40
+ const probe = createLivenessProbe({ includeMemory: true });
41
+ const result = await probe.check();
42
+
43
+ expect(result.memory).toBeDefined();
44
+ });
45
+ });
46
+
47
+ describe('createReadinessProbe', () => {
48
+ it('checks all dependencies', async () => {
49
+ const mockDbCheck = vi.fn().mockResolvedValue(true);
50
+ const mockCacheCheck = vi.fn().mockResolvedValue(true);
51
+
52
+ const probe = createReadinessProbe({
53
+ checks: [
54
+ { name: 'database', check: mockDbCheck },
55
+ { name: 'cache', check: mockCacheCheck },
56
+ ],
57
+ });
58
+
59
+ const result = await probe.check();
60
+
61
+ expect(result.status).toBe('healthy');
62
+ expect(mockDbCheck).toHaveBeenCalled();
63
+ expect(mockCacheCheck).toHaveBeenCalled();
64
+ });
65
+
66
+ it('returns unhealthy if any check fails', async () => {
67
+ const probe = createReadinessProbe({
68
+ checks: [
69
+ { name: 'database', check: vi.fn().mockResolvedValue(true) },
70
+ { name: 'cache', check: vi.fn().mockResolvedValue(false) },
71
+ ],
72
+ });
73
+
74
+ const result = await probe.check();
75
+
76
+ expect(result.status).toBe('unhealthy');
77
+ expect(result.failed).toContain('cache');
78
+ });
79
+
80
+ it('handles check timeout', async () => {
81
+ const slowCheck = () => new Promise((r) => setTimeout(() => r(true), 5000));
82
+
83
+ const probe = createReadinessProbe({
84
+ checks: [{ name: 'slow', check: slowCheck }],
85
+ timeout: 100,
86
+ });
87
+
88
+ const result = await probe.check();
89
+
90
+ expect(result.status).toBe('unhealthy');
91
+ });
92
+ });
93
+
94
+ describe('createDeepHealthCheck', () => {
95
+ it('verifies database connection', async () => {
96
+ const mockDb = { query: vi.fn().mockResolvedValue([{ result: 1 }]) };
97
+
98
+ const check = createDeepHealthCheck({ db: mockDb });
99
+ const result = await check.checkDatabase();
100
+
101
+ expect(result.healthy).toBe(true);
102
+ expect(mockDb.query).toHaveBeenCalled();
103
+ });
104
+
105
+ it('verifies cache connection', async () => {
106
+ const mockCache = { ping: vi.fn().mockResolvedValue('PONG') };
107
+
108
+ const check = createDeepHealthCheck({ cache: mockCache });
109
+ const result = await check.checkCache();
110
+
111
+ expect(result.healthy).toBe(true);
112
+ });
113
+
114
+ it('verifies external service', async () => {
115
+ const mockFetch = vi.fn().mockResolvedValue({ ok: true });
116
+
117
+ const check = createDeepHealthCheck({ fetch: mockFetch });
118
+ const result = await check.checkExternal('https://api.example.com/health');
119
+
120
+ expect(result.healthy).toBe(true);
121
+ });
122
+
123
+ it('does not leak sensitive info', async () => {
124
+ const check = createDeepHealthCheck({
125
+ db: { connectionString: 'postgres://user:pass@host/db' },
126
+ });
127
+
128
+ const result = await check.getStatus();
129
+
130
+ expect(JSON.stringify(result)).not.toContain('pass');
131
+ });
132
+ });
133
+
134
+ describe('runHealthCheck', () => {
135
+ it('runs all configured checks', async () => {
136
+ const result = await runHealthCheck({
137
+ liveness: true,
138
+ readiness: true,
139
+ checks: [],
140
+ });
141
+
142
+ expect(result.liveness).toBeDefined();
143
+ expect(result.readiness).toBeDefined();
144
+ });
145
+
146
+ it('aggregates status', async () => {
147
+ const result = await runHealthCheck({
148
+ checks: [
149
+ { name: 'a', check: vi.fn().mockResolvedValue(true) },
150
+ { name: 'b', check: vi.fn().mockResolvedValue(true) },
151
+ ],
152
+ });
153
+
154
+ expect(result.overall).toBe('healthy');
155
+ });
156
+ });
157
+
158
+ describe('createHealthCheckManager', () => {
159
+ it('creates manager with methods', () => {
160
+ const manager = createHealthCheckManager();
161
+
162
+ expect(manager.addCheck).toBeDefined();
163
+ expect(manager.runAll).toBeDefined();
164
+ expect(manager.getLiveness).toBeDefined();
165
+ expect(manager.getReadiness).toBeDefined();
166
+ });
167
+
168
+ it('configures check intervals', () => {
169
+ const manager = createHealthCheckManager({
170
+ interval: 30000,
171
+ });
172
+
173
+ expect(manager.getInterval()).toBe(30000);
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Incident Manager
3
+ * Manages incidents with timeline tracking and MTTR metrics
4
+ */
5
+
6
+ import { randomUUID } from 'crypto';
7
+
8
+ /**
9
+ * Incident status constants
10
+ */
11
+ export const INCIDENT_STATUS = {
12
+ OPEN: 'open',
13
+ INVESTIGATING: 'investigating',
14
+ RESOLVED: 'resolved',
15
+ };
16
+
17
+ /**
18
+ * Creates a new incident from an alert or manual trigger
19
+ * @param {Object} options - Incident options
20
+ * @param {Object} options.alert - Source alert
21
+ * @param {string} options.title - Incident title
22
+ * @returns {Object} Created incident
23
+ */
24
+ export function createIncident(options = {}) {
25
+ const { alert, title } = options;
26
+ const now = new Date();
27
+
28
+ return {
29
+ id: randomUUID(),
30
+ title: alert?.title || title || 'Untitled Incident',
31
+ status: INCIDENT_STATUS.OPEN,
32
+ alerts: alert ? [alert] : [],
33
+ createdAt: now,
34
+ updatedAt: now,
35
+ statusHistory: [
36
+ {
37
+ status: INCIDENT_STATUS.OPEN,
38
+ timestamp: now,
39
+ },
40
+ ],
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Generates a timeline from incident events
46
+ * @param {Object} options - Timeline options
47
+ * @param {Array} options.events - Events to include
48
+ * @returns {Array} Sorted timeline entries
49
+ */
50
+ export function generateTimeline(options) {
51
+ const { events } = options;
52
+ return events
53
+ .map(event => ({
54
+ ...event,
55
+ timestamp: new Date(event.timestamp),
56
+ }))
57
+ .sort((a, b) => a.timestamp - b.timestamp);
58
+ }
59
+
60
+ /**
61
+ * Links related alerts to an incident
62
+ * @param {Object} incident - Incident to link to
63
+ * @param {Array} alerts - Alerts to link
64
+ * @returns {Object} Updated incident
65
+ */
66
+ export function linkAlerts(incident, alerts) {
67
+ return {
68
+ ...incident,
69
+ alerts: [...(incident.alerts || []), ...alerts],
70
+ updatedAt: new Date(),
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Updates incident status
76
+ * @param {Object} incident - Incident to update
77
+ * @param {string} status - New status
78
+ * @returns {Object} Updated incident
79
+ */
80
+ export function updateStatus(incident, status) {
81
+ const now = new Date();
82
+ return {
83
+ ...incident,
84
+ status,
85
+ updatedAt: now,
86
+ resolvedAt: status === INCIDENT_STATUS.RESOLVED ? now : incident.resolvedAt,
87
+ statusHistory: [
88
+ ...(incident.statusHistory || []),
89
+ {
90
+ status,
91
+ timestamp: now,
92
+ },
93
+ ],
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Generates a post-mortem template for an incident
99
+ * @param {Object} options - Post-mortem options
100
+ * @param {Object} options.incident - Incident to generate post-mortem for
101
+ * @returns {string} Markdown post-mortem template
102
+ */
103
+ export function generatePostMortem(options) {
104
+ const { incident } = options;
105
+
106
+ return `# Post-Mortem: ${incident.title}
107
+
108
+ ## Incident ID
109
+ ${incident.id}
110
+
111
+ ## Summary
112
+ [Brief description of what happened]
113
+
114
+ ## Timeline
115
+ [Key events and timestamps]
116
+
117
+ ## Root Cause
118
+ [What caused the incident]
119
+
120
+ ## Impact
121
+ [Who/what was affected]
122
+
123
+ ## Resolution
124
+ [How the incident was resolved]
125
+
126
+ ## Action Items
127
+ - [ ] [Action item 1]
128
+ - [ ] [Action item 2]
129
+
130
+ ## Lessons Learned
131
+ [Key takeaways]
132
+ `;
133
+ }
134
+
135
+ /**
136
+ * Calculates mean time to resolve (MTTR) for incidents
137
+ * @param {Array} incidents - Resolved incidents with createdAt and resolvedAt
138
+ * @returns {number} MTTR in milliseconds
139
+ */
140
+ export function calculateMttr(incidents) {
141
+ if (!incidents || incidents.length === 0) {
142
+ return 0;
143
+ }
144
+
145
+ const resolvedIncidents = incidents.filter(i => i.resolvedAt);
146
+ if (resolvedIncidents.length === 0) {
147
+ return 0;
148
+ }
149
+
150
+ const totalTime = resolvedIncidents.reduce((sum, incident) => {
151
+ const created = new Date(incident.createdAt).getTime();
152
+ const resolved = new Date(incident.resolvedAt).getTime();
153
+ return sum + (resolved - created);
154
+ }, 0);
155
+
156
+ return totalTime / resolvedIncidents.length;
157
+ }
158
+
159
+ /**
160
+ * Creates an incident manager instance
161
+ * @returns {Object} Incident manager with methods
162
+ */
163
+ export function createIncidentManager() {
164
+ const incidents = new Map();
165
+
166
+ return {
167
+ /**
168
+ * Creates a new incident
169
+ * @param {Object} options - Incident options
170
+ * @returns {Object} Created incident
171
+ */
172
+ create(options) {
173
+ const incident = createIncident(options);
174
+ incidents.set(incident.id, incident);
175
+ return incident;
176
+ },
177
+
178
+ /**
179
+ * Updates an existing incident
180
+ * @param {string} id - Incident ID
181
+ * @param {Object} updates - Updates to apply
182
+ * @returns {Object} Updated incident
183
+ */
184
+ update(id, updates) {
185
+ const incident = incidents.get(id);
186
+ if (!incident) {
187
+ throw new Error(`Incident ${id} not found`);
188
+ }
189
+ const updated = {
190
+ ...incident,
191
+ ...updates,
192
+ updatedAt: new Date(),
193
+ };
194
+ incidents.set(id, updated);
195
+ return updated;
196
+ },
197
+
198
+ /**
199
+ * Resolves an incident
200
+ * @param {string} id - Incident ID
201
+ * @returns {Object} Resolved incident
202
+ */
203
+ resolve(id) {
204
+ const incident = incidents.get(id);
205
+ if (!incident) {
206
+ throw new Error(`Incident ${id} not found`);
207
+ }
208
+ const resolved = updateStatus(incident, INCIDENT_STATUS.RESOLVED);
209
+ incidents.set(id, resolved);
210
+ return resolved;
211
+ },
212
+
213
+ /**
214
+ * Gets incident metrics
215
+ * @returns {Object} Metrics including MTTR
216
+ */
217
+ getMetrics() {
218
+ const all = Array.from(incidents.values());
219
+ const resolved = all.filter(i => i.status === INCIDENT_STATUS.RESOLVED);
220
+
221
+ return {
222
+ total: all.length,
223
+ open: all.filter(i => i.status === INCIDENT_STATUS.OPEN).length,
224
+ investigating: all.filter(i => i.status === INCIDENT_STATUS.INVESTIGATING).length,
225
+ resolved: resolved.length,
226
+ mttr: calculateMttr(resolved),
227
+ };
228
+ },
229
+ };
230
+ }