@a-company/paradigm 3.8.0 → 3.11.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 (39) hide show
  1. package/dist/{accept-orchestration-DIGPJVUR.js → accept-orchestration-Z35I5AYN.js} +5 -5
  2. package/dist/{assessment-loader-T4GPBHLB.js → assessment-loader-C5EOUM47.js} +0 -1
  3. package/dist/{chunk-Y4XZWCHK.js → chunk-24AAVLME.js} +8 -8
  4. package/dist/{chunk-4N6AYEEA.js → chunk-3TWXFFZ3.js} +1 -1
  5. package/dist/chunk-4ZO3ZOPM.js +4879 -0
  6. package/dist/{chunk-6RNYVBSG.js → chunk-CP6IZGUN.js} +4 -4
  7. package/dist/{chunk-M2XMTJHQ.js → chunk-DS5QY37M.js} +201 -287
  8. package/dist/chunk-F6EJKLF4.js +4971 -0
  9. package/dist/chunk-MW5DMGBB.js +255 -0
  10. package/dist/{chunk-KFHK6EBI.js → chunk-OSYMVGWX.js} +59 -3
  11. package/dist/{chunk-GY5KO3YZ.js → chunk-RDPXBMHK.js} +1 -1
  12. package/dist/{chunk-ADOBV4PH.js → chunk-UVI3OH3G.js} +6 -2127
  13. package/dist/{diff-J6C5IHPV.js → diff-PZAYCIAE.js} +5 -5
  14. package/dist/{dist-OLFOTUHS.js → dist-6SX5ZKKF.js} +2 -2
  15. package/dist/dist-YB7T54QE.js +57 -0
  16. package/dist/{doctor-TQYRF7KK.js → doctor-3YQ55536.js} +1 -1
  17. package/dist/drift-FH2UY64B.js +251 -0
  18. package/dist/{flow-7JUH6D4H.js → flow-MCKPJGRJ.js} +1 -1
  19. package/dist/{habits-ZJBAL4HD.js → habits-NC2TRMRV.js} +2 -2
  20. package/dist/{hooks-DLZEYHI3.js → hooks-JXYHVGIN.js} +1 -1
  21. package/dist/index.js +77 -51
  22. package/dist/mcp.js +10192 -18116
  23. package/dist/{orchestrate-FAV64G2R.js → orchestrate-BGRFBGBH.js} +5 -5
  24. package/dist/{plugin-update-checker-TWBWUSAG.js → plugin-update-checker-S3W4BUJO.js} +0 -1
  25. package/dist/portal-check-2HI4FFD6.js +42 -0
  26. package/dist/portal-compliance-KQCTAQTJ.js +18 -0
  27. package/dist/{providers-NQ67LO2Z.js → providers-IONB4YRJ.js} +1 -1
  28. package/dist/reindex-ZM6J53UP.js +11 -0
  29. package/dist/{sentinel-KDIGZWKT.js → sentinel-BGCISNIK.js} +1 -1
  30. package/dist/{server-NN7WDAZJ.js → server-3K3TTJH3.js} +1 -1
  31. package/dist/{shift-KJWSJLWN.js → shift-6I6N6RNK.js} +36 -8
  32. package/dist/{spawn-EO7B2UM3.js → spawn-WGFJ5RQZ.js} +5 -5
  33. package/dist/{task-loader-GUX4KS6N.js → task-loader-7M2FCBX6.js} +0 -1
  34. package/dist/{team-6CCNANKE.js → team-AFOKQ7YQ.js} +6 -6
  35. package/dist/{triage-B5W6GZLT.js → triage-MKKIWBSW.js} +2 -2
  36. package/dist/workspace-VBTW7OYL.js +271 -0
  37. package/package.json +2 -1
  38. package/dist/chunk-HPC3JAUP.js +0 -42
  39. /package/dist/{chunk-CCG6KYBT.js → chunk-5N5LR2KS.js} +0 -0
@@ -0,0 +1,4879 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../paradigm-mcp/node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-node/rng.js
4
+ import crypto from "crypto";
5
+ var rnds8Pool = new Uint8Array(256);
6
+ var poolPtr = rnds8Pool.length;
7
+ function rng() {
8
+ if (poolPtr > rnds8Pool.length - 16) {
9
+ crypto.randomFillSync(rnds8Pool);
10
+ poolPtr = 0;
11
+ }
12
+ return rnds8Pool.slice(poolPtr, poolPtr += 16);
13
+ }
14
+
15
+ // ../paradigm-mcp/node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-node/stringify.js
16
+ var byteToHex = [];
17
+ for (let i = 0; i < 256; ++i) {
18
+ byteToHex.push((i + 256).toString(16).slice(1));
19
+ }
20
+ function unsafeStringify(arr, offset = 0) {
21
+ return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
22
+ }
23
+
24
+ // ../paradigm-mcp/node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-node/native.js
25
+ import crypto2 from "crypto";
26
+ var native_default = {
27
+ randomUUID: crypto2.randomUUID
28
+ };
29
+
30
+ // ../paradigm-mcp/node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-node/v4.js
31
+ function v4(options, buf, offset) {
32
+ if (native_default.randomUUID && !buf && !options) {
33
+ return native_default.randomUUID();
34
+ }
35
+ options = options || {};
36
+ const rnds = options.random || (options.rng || rng)();
37
+ rnds[6] = rnds[6] & 15 | 64;
38
+ rnds[8] = rnds[8] & 63 | 128;
39
+ if (buf) {
40
+ offset = offset || 0;
41
+ for (let i = 0; i < 16; ++i) {
42
+ buf[offset + i] = rnds[i];
43
+ }
44
+ return buf;
45
+ }
46
+ return unsafeStringify(rnds);
47
+ }
48
+ var v4_default = v4;
49
+
50
+ // ../paradigm-mcp/node_modules/.pnpm/@a-company+sentinel@3.5.0/node_modules/@a-company/sentinel/dist/chunk-VQ3SIN7S.js
51
+ var DEFAULTS = {
52
+ url: "http://localhost:3838",
53
+ batchSize: 50,
54
+ flushIntervalMs: 5e3,
55
+ maxBufferSize: 1e3,
56
+ maxRetries: 3,
57
+ retryBackoffMs: 1e3
58
+ };
59
+ var SentinelClient = class {
60
+ url;
61
+ service;
62
+ version;
63
+ environment;
64
+ token;
65
+ batchSize;
66
+ maxBufferSize;
67
+ maxRetries;
68
+ retryBackoffMs;
69
+ onDrop;
70
+ onError;
71
+ sessionId;
72
+ logBuffer = [];
73
+ metricsBuffer = [];
74
+ flushTimer = null;
75
+ closed = false;
76
+ beforeUnloadHandler = null;
77
+ constructor(options) {
78
+ this.url = (options.url ?? DEFAULTS.url).replace(/\/+$/, "");
79
+ this.service = options.service;
80
+ this.version = options.version;
81
+ this.environment = options.environment;
82
+ this.token = options.token;
83
+ this.batchSize = options.batchSize ?? DEFAULTS.batchSize;
84
+ this.maxBufferSize = options.maxBufferSize ?? DEFAULTS.maxBufferSize;
85
+ this.maxRetries = options.maxRetries ?? DEFAULTS.maxRetries;
86
+ this.retryBackoffMs = options.retryBackoffMs ?? DEFAULTS.retryBackoffMs;
87
+ this.onDrop = options.onDrop;
88
+ this.onError = options.onError;
89
+ this.sessionId = v4_default();
90
+ const intervalMs = options.flushIntervalMs ?? DEFAULTS.flushIntervalMs;
91
+ this.flushTimer = setInterval(() => {
92
+ this.flush().catch((err) => {
93
+ this.handleError(err);
94
+ });
95
+ }, intervalMs);
96
+ if (this.flushTimer && typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
97
+ this.flushTimer.unref();
98
+ }
99
+ if (typeof globalThis !== "undefined" && typeof globalThis.addEventListener === "function") {
100
+ this.beforeUnloadHandler = () => {
101
+ this.flushSync();
102
+ };
103
+ globalThis.addEventListener("beforeunload", this.beforeUnloadHandler);
104
+ }
105
+ this.registerService();
106
+ }
107
+ // ── Logging Methods ──────────────────────────────────────────────
108
+ /** Log a debug-level message */
109
+ debug(symbol, message, data) {
110
+ this.log("debug", symbol, message, data);
111
+ }
112
+ /** Log an info-level message */
113
+ info(symbol, message, data) {
114
+ this.log("info", symbol, message, data);
115
+ }
116
+ /** Log a warn-level message */
117
+ warn(symbol, message, data) {
118
+ this.log("warn", symbol, message, data);
119
+ }
120
+ /** Log an error-level message */
121
+ error(symbol, message, data) {
122
+ this.log("error", symbol, message, data);
123
+ }
124
+ /** Log a message at the specified level */
125
+ log(level, symbol, message, data) {
126
+ if (this.closed) return;
127
+ const entry = {
128
+ level,
129
+ symbol,
130
+ message,
131
+ service: this.service,
132
+ sessionId: this.sessionId,
133
+ environment: this.environment,
134
+ data
135
+ };
136
+ this.pushToLogBuffer(entry);
137
+ }
138
+ // ── Metrics Methods ──────────────────────────────────────────────
139
+ /** Record a counter metric (increments) */
140
+ counter(name, value, tags) {
141
+ this.metric({ name, type: "counter", value: value ?? 1, tags });
142
+ }
143
+ /** Record a gauge metric (current value) */
144
+ gauge(name, value, tags) {
145
+ this.metric({ name, type: "gauge", value, tags });
146
+ }
147
+ /** Record a histogram metric (distribution) */
148
+ histogram(name, value, tags) {
149
+ this.metric({ name, type: "histogram", value, tags });
150
+ }
151
+ /** Record a metric of any type */
152
+ metric(input) {
153
+ if (this.closed) return;
154
+ const entry = {
155
+ ...input,
156
+ service: this.service,
157
+ environment: this.environment
158
+ };
159
+ this.pushToMetricsBuffer(entry);
160
+ }
161
+ // ── State Push ───────────────────────────────────────────────────
162
+ /** Push an application state snapshot to the server */
163
+ async pushState(state, activeFlows, activeGates) {
164
+ if (this.closed) return;
165
+ await this.post("/api/state", {
166
+ service: this.service,
167
+ sessionId: this.sessionId,
168
+ state,
169
+ activeFlows,
170
+ activeGates
171
+ });
172
+ }
173
+ // ── Tracing ──────────────────────────────────────────────────────
174
+ /** Start a trace span. Call end() on the returned SpanContext when the operation completes. */
175
+ startSpan(symbol, operation, parentSpanId) {
176
+ const traceId = parentSpanId ? parentSpanId.split("-")[0] || v4_default() : v4_default();
177
+ const spanId = v4_default();
178
+ const startTime = (/* @__PURE__ */ new Date()).toISOString();
179
+ const startMs = Date.now();
180
+ const self = this;
181
+ return {
182
+ traceId,
183
+ spanId,
184
+ async end(status = "ok") {
185
+ const endTime = (/* @__PURE__ */ new Date()).toISOString();
186
+ const durationMs = Date.now() - startMs;
187
+ const span = {
188
+ traceId,
189
+ spanId,
190
+ parentSpanId,
191
+ service: self.service,
192
+ symbol,
193
+ operation,
194
+ startTime,
195
+ endTime,
196
+ durationMs,
197
+ status
198
+ };
199
+ await self.post("/api/traces", span);
200
+ }
201
+ };
202
+ }
203
+ // ── Buffer Management ────────────────────────────────────────────
204
+ /** Flush all buffered logs and metrics to the server */
205
+ async flush() {
206
+ const logEntries = this.drainLogBuffer();
207
+ const metricEntries = this.drainMetricsBuffer();
208
+ const promises = [];
209
+ if (logEntries.length > 0) {
210
+ promises.push(this.sendLogs(logEntries));
211
+ }
212
+ if (metricEntries.length > 0) {
213
+ promises.push(this.sendMetrics(metricEntries));
214
+ }
215
+ await Promise.allSettled(promises);
216
+ }
217
+ // ── Lifecycle ────────────────────────────────────────────────────
218
+ /** Flush remaining entries and shut down the client */
219
+ async close() {
220
+ if (this.closed) return;
221
+ this.closed = true;
222
+ if (this.flushTimer !== null) {
223
+ clearInterval(this.flushTimer);
224
+ this.flushTimer = null;
225
+ }
226
+ if (this.beforeUnloadHandler && typeof globalThis !== "undefined" && typeof globalThis.removeEventListener === "function") {
227
+ globalThis.removeEventListener("beforeunload", this.beforeUnloadHandler);
228
+ this.beforeUnloadHandler = null;
229
+ }
230
+ try {
231
+ await this.flush();
232
+ } catch {
233
+ }
234
+ }
235
+ /** Get the session ID assigned to this client instance */
236
+ getSessionId() {
237
+ return this.sessionId;
238
+ }
239
+ /**
240
+ * Synchronous best-effort flush using sendBeacon (browser) or sync XHR fallback.
241
+ * Used in beforeunload where async is unreliable.
242
+ */
243
+ flushSync() {
244
+ const logEntries = this.drainLogBuffer();
245
+ const metricEntries = this.drainMetricsBuffer();
246
+ const sendBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" ? navigator.sendBeacon.bind(navigator) : void 0;
247
+ if (sendBeacon) {
248
+ if (logEntries.length > 0) {
249
+ sendBeacon(`${this.url}/api/logs`, JSON.stringify({ entries: logEntries }));
250
+ }
251
+ if (metricEntries.length > 0) {
252
+ sendBeacon(`${this.url}/api/metrics`, JSON.stringify({ entries: metricEntries }));
253
+ }
254
+ }
255
+ }
256
+ // ═══════════════════════════════════════════════════════════════════
257
+ // PRIVATE METHODS
258
+ // ═══════════════════════════════════════════════════════════════════
259
+ /** Register this service with the Sentinel server (fire-and-forget) */
260
+ registerService() {
261
+ const registration = {
262
+ name: this.service,
263
+ version: this.version,
264
+ pid: typeof process !== "undefined" ? process.pid : void 0,
265
+ environment: this.environment
266
+ };
267
+ this.post("/api/services", registration).catch(() => {
268
+ });
269
+ }
270
+ /** Push a log entry into the ring buffer, enforcing maxBufferSize */
271
+ pushToLogBuffer(entry) {
272
+ if (this.logBuffer.length >= this.maxBufferSize) {
273
+ const dropCount = Math.max(1, Math.floor(this.maxBufferSize * 0.1));
274
+ this.logBuffer.splice(0, dropCount);
275
+ if (this.onDrop) {
276
+ this.onDrop(dropCount);
277
+ }
278
+ }
279
+ this.logBuffer.push(entry);
280
+ if (this.logBuffer.length >= this.batchSize) {
281
+ this.flush().catch((err) => {
282
+ this.handleError(err);
283
+ });
284
+ }
285
+ }
286
+ /** Push a metric entry into the ring buffer, enforcing maxBufferSize */
287
+ pushToMetricsBuffer(entry) {
288
+ if (this.metricsBuffer.length >= this.maxBufferSize) {
289
+ const dropCount = Math.max(1, Math.floor(this.maxBufferSize * 0.1));
290
+ this.metricsBuffer.splice(0, dropCount);
291
+ if (this.onDrop) {
292
+ this.onDrop(dropCount);
293
+ }
294
+ }
295
+ this.metricsBuffer.push(entry);
296
+ if (this.metricsBuffer.length >= this.batchSize) {
297
+ this.flush().catch((err) => {
298
+ this.handleError(err);
299
+ });
300
+ }
301
+ }
302
+ /** Drain and return all entries from the log buffer */
303
+ drainLogBuffer() {
304
+ const entries = this.logBuffer;
305
+ this.logBuffer = [];
306
+ return entries;
307
+ }
308
+ /** Drain and return all entries from the metrics buffer */
309
+ drainMetricsBuffer() {
310
+ const entries = this.metricsBuffer;
311
+ this.metricsBuffer = [];
312
+ return entries;
313
+ }
314
+ /** Send log entries to the server with retry */
315
+ async sendLogs(entries) {
316
+ try {
317
+ await this.post("/api/logs", { entries });
318
+ } catch (err) {
319
+ const capacity = this.maxBufferSize - this.logBuffer.length;
320
+ if (capacity > 0) {
321
+ const toRestore = entries.slice(0, capacity);
322
+ this.logBuffer.unshift(...toRestore);
323
+ const dropped = entries.length - toRestore.length;
324
+ if (dropped > 0 && this.onDrop) {
325
+ this.onDrop(dropped);
326
+ }
327
+ } else if (this.onDrop) {
328
+ this.onDrop(entries.length);
329
+ }
330
+ throw err;
331
+ }
332
+ }
333
+ /** Send metric entries to the server with retry */
334
+ async sendMetrics(entries) {
335
+ try {
336
+ await this.post("/api/metrics", { entries });
337
+ } catch (err) {
338
+ const capacity = this.maxBufferSize - this.metricsBuffer.length;
339
+ if (capacity > 0) {
340
+ const toRestore = entries.slice(0, capacity);
341
+ this.metricsBuffer.unshift(...toRestore);
342
+ const dropped = entries.length - toRestore.length;
343
+ if (dropped > 0 && this.onDrop) {
344
+ this.onDrop(dropped);
345
+ }
346
+ } else if (this.onDrop) {
347
+ this.onDrop(entries.length);
348
+ }
349
+ throw err;
350
+ }
351
+ }
352
+ /**
353
+ * POST JSON to the Sentinel server with exponential backoff retry.
354
+ * Retries on network errors and 5xx responses. Does NOT retry on 4xx.
355
+ */
356
+ async post(path4, body) {
357
+ const fetchFn = this.getFetch();
358
+ if (!fetchFn) {
359
+ return void 0;
360
+ }
361
+ const url = `${this.url}${path4}`;
362
+ const headers = {
363
+ "Content-Type": "application/json"
364
+ };
365
+ if (this.token) {
366
+ headers["Authorization"] = `Bearer ${this.token}`;
367
+ }
368
+ let lastError;
369
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
370
+ try {
371
+ const response = await fetchFn(url, {
372
+ method: "POST",
373
+ headers,
374
+ body: JSON.stringify(body)
375
+ });
376
+ if (response.status >= 400 && response.status < 500) {
377
+ const text = await response.text().catch(() => "");
378
+ const err = new Error(`Sentinel server returned ${response.status}: ${text}`);
379
+ this.handleError(err);
380
+ return void 0;
381
+ }
382
+ if (response.status >= 500) {
383
+ const text = await response.text().catch(() => "");
384
+ lastError = new Error(`Sentinel server returned ${response.status}: ${text}`);
385
+ if (attempt < this.maxRetries) {
386
+ await this.backoff(attempt);
387
+ continue;
388
+ }
389
+ this.handleError(lastError);
390
+ throw lastError;
391
+ }
392
+ const contentType = response.headers.get("content-type") || "";
393
+ if (contentType.includes("application/json")) {
394
+ return await response.json();
395
+ }
396
+ return void 0;
397
+ } catch (err) {
398
+ lastError = err instanceof Error ? err : new Error(String(err));
399
+ if (attempt < this.maxRetries) {
400
+ await this.backoff(attempt);
401
+ continue;
402
+ }
403
+ this.handleError(lastError);
404
+ throw lastError;
405
+ }
406
+ }
407
+ if (lastError) throw lastError;
408
+ return void 0;
409
+ }
410
+ /** Get the fetch function, falling back to globalThis.fetch */
411
+ getFetch() {
412
+ if (typeof globalThis !== "undefined" && typeof globalThis.fetch === "function") {
413
+ return globalThis.fetch;
414
+ }
415
+ return void 0;
416
+ }
417
+ /** Wait with exponential backoff */
418
+ backoff(attempt) {
419
+ const delayMs = this.retryBackoffMs * Math.pow(2, attempt);
420
+ const jitter = Math.floor(Math.random() * delayMs * 0.25);
421
+ return new Promise((resolve) => setTimeout(resolve, delayMs + jitter));
422
+ }
423
+ /** Handle an error, calling the onError callback if provided */
424
+ handleError(err) {
425
+ if (this.onError) {
426
+ const error = err instanceof Error ? err : new Error(String(err));
427
+ this.onError(error);
428
+ }
429
+ }
430
+ };
431
+ function createSentinelClient(options) {
432
+ return new SentinelClient(options);
433
+ }
434
+ var SentinelTransport = class {
435
+ client;
436
+ constructor(client) {
437
+ this.client = client;
438
+ }
439
+ send(entry) {
440
+ this.client.log(
441
+ entry.level,
442
+ entry.symbol,
443
+ entry.message,
444
+ {
445
+ ...entry.data,
446
+ symbolType: entry.symbolType,
447
+ correlationId: entry.correlationId
448
+ }
449
+ );
450
+ }
451
+ };
452
+ function createSentinelTransport(clientOrOptions) {
453
+ const client = clientOrOptions instanceof SentinelClient ? clientOrOptions : new SentinelClient(clientOrOptions);
454
+ return new SentinelTransport(client);
455
+ }
456
+ function enableSentinel(logger, clientOrOptions) {
457
+ const transport = createSentinelTransport(clientOrOptions);
458
+ logger.addTransport(transport);
459
+ return transport;
460
+ }
461
+
462
+ // ../paradigm-mcp/node_modules/.pnpm/@a-company+sentinel@3.5.0/node_modules/@a-company/sentinel/dist/chunk-FOF7CPJ6.js
463
+ import initSqlJs from "sql.js";
464
+ import * as path from "path";
465
+ import * as fs from "fs";
466
+ import * as fs2 from "fs";
467
+ import * as path2 from "path";
468
+ var SCHEMA_VERSION = 4;
469
+ var DEFAULT_CONFIDENCE = {
470
+ score: 50,
471
+ timesMatched: 0,
472
+ timesResolved: 0,
473
+ timesRecurred: 0
474
+ };
475
+ var SQL = null;
476
+ var SentinelStorage = class {
477
+ db = null;
478
+ dbPath;
479
+ incidentCounter = 0;
480
+ initialized = false;
481
+ constructor(dbPath) {
482
+ this.dbPath = dbPath || this.getDefaultDbPath();
483
+ }
484
+ getDefaultDbPath() {
485
+ const dataDir = process.env.SENTINEL_DATA_DIR || process.env.PARADIGM_DATA_DIR || path.join(process.cwd(), ".paradigm", "sentinel");
486
+ return path.join(dataDir, "sentinel.db");
487
+ }
488
+ createSchema() {
489
+ if (!this.db) return;
490
+ this.db.run(`
491
+ -- Metadata table for schema versioning
492
+ CREATE TABLE IF NOT EXISTS metadata (
493
+ key TEXT PRIMARY KEY,
494
+ value TEXT NOT NULL
495
+ );
496
+
497
+ -- Incidents table
498
+ CREATE TABLE IF NOT EXISTS incidents (
499
+ id TEXT PRIMARY KEY,
500
+ timestamp TEXT NOT NULL,
501
+ status TEXT NOT NULL DEFAULT 'open',
502
+ error_message TEXT NOT NULL,
503
+ error_stack TEXT,
504
+ error_code TEXT,
505
+ error_type TEXT,
506
+ symbols TEXT NOT NULL,
507
+ flow_position TEXT,
508
+ environment TEXT NOT NULL,
509
+ service TEXT,
510
+ version TEXT,
511
+ user_id TEXT,
512
+ request_id TEXT,
513
+ group_id TEXT,
514
+ notes TEXT DEFAULT '[]',
515
+ related_incidents TEXT DEFAULT '[]',
516
+ resolved_at TEXT,
517
+ resolved_by TEXT,
518
+ resolution TEXT,
519
+ created_at TEXT NOT NULL,
520
+ updated_at TEXT NOT NULL
521
+ );
522
+
523
+ -- Patterns table
524
+ CREATE TABLE IF NOT EXISTS patterns (
525
+ id TEXT PRIMARY KEY,
526
+ name TEXT NOT NULL,
527
+ description TEXT,
528
+ pattern TEXT NOT NULL,
529
+ resolution TEXT NOT NULL,
530
+ confidence TEXT NOT NULL,
531
+ source TEXT NOT NULL DEFAULT 'manual',
532
+ private INTEGER NOT NULL DEFAULT 0,
533
+ tags TEXT DEFAULT '[]',
534
+ created_at TEXT NOT NULL,
535
+ updated_at TEXT NOT NULL
536
+ );
537
+
538
+ -- Incident groups
539
+ CREATE TABLE IF NOT EXISTS groups (
540
+ id TEXT PRIMARY KEY,
541
+ name TEXT,
542
+ common_symbols TEXT,
543
+ common_error_patterns TEXT,
544
+ suggested_pattern_id TEXT,
545
+ first_seen TEXT NOT NULL,
546
+ last_seen TEXT NOT NULL,
547
+ created_at TEXT NOT NULL,
548
+ updated_at TEXT NOT NULL
549
+ );
550
+
551
+ -- Group members
552
+ CREATE TABLE IF NOT EXISTS group_members (
553
+ group_id TEXT NOT NULL,
554
+ incident_id TEXT NOT NULL,
555
+ added_at TEXT NOT NULL,
556
+ PRIMARY KEY (group_id, incident_id)
557
+ );
558
+
559
+ -- Resolutions history
560
+ CREATE TABLE IF NOT EXISTS resolutions (
561
+ id TEXT PRIMARY KEY,
562
+ incident_id TEXT NOT NULL,
563
+ pattern_id TEXT,
564
+ commit_hash TEXT,
565
+ pr_url TEXT,
566
+ notes TEXT,
567
+ resolved_at TEXT NOT NULL,
568
+ recurred INTEGER DEFAULT 0
569
+ );
570
+
571
+ -- Practice events (habits system)
572
+ CREATE TABLE IF NOT EXISTS practice_events (
573
+ id TEXT PRIMARY KEY,
574
+ timestamp TEXT NOT NULL,
575
+ habit_id TEXT NOT NULL,
576
+ habit_category TEXT NOT NULL,
577
+ result TEXT NOT NULL CHECK (result IN ('followed', 'skipped', 'partial')),
578
+ engineer TEXT NOT NULL,
579
+ session_id TEXT NOT NULL,
580
+ lore_entry_id TEXT,
581
+ task_description TEXT,
582
+ symbols_touched TEXT DEFAULT '[]',
583
+ files_modified TEXT DEFAULT '[]',
584
+ related_incident_id TEXT,
585
+ notes TEXT
586
+ );
587
+
588
+ -- Structured logs table
589
+ CREATE TABLE IF NOT EXISTS logs (
590
+ id TEXT PRIMARY KEY,
591
+ timestamp TEXT NOT NULL,
592
+ level TEXT NOT NULL CHECK (level IN ('debug','info','warn','error')),
593
+ symbol TEXT NOT NULL,
594
+ symbol_type TEXT NOT NULL DEFAULT 'raw',
595
+ message TEXT NOT NULL,
596
+ data_json TEXT,
597
+ service TEXT NOT NULL,
598
+ session_id TEXT,
599
+ correlation_id TEXT,
600
+ duration_ms REAL,
601
+ environment TEXT
602
+ );
603
+
604
+ -- Service registry
605
+ CREATE TABLE IF NOT EXISTS services (
606
+ name TEXT PRIMARY KEY,
607
+ version TEXT,
608
+ pid INTEGER,
609
+ started_at TEXT NOT NULL,
610
+ last_seen_at TEXT NOT NULL,
611
+ environment TEXT,
612
+ metadata_json TEXT
613
+ );
614
+
615
+ -- Live app state snapshots (latest-wins per service+session)
616
+ CREATE TABLE IF NOT EXISTS app_state (
617
+ service TEXT NOT NULL,
618
+ session_id TEXT NOT NULL,
619
+ timestamp TEXT NOT NULL,
620
+ state_json TEXT NOT NULL,
621
+ active_flows_json TEXT,
622
+ active_gates_json TEXT,
623
+ PRIMARY KEY (service, session_id)
624
+ );
625
+
626
+ -- Metrics table
627
+ CREATE TABLE IF NOT EXISTS metrics (
628
+ id TEXT PRIMARY KEY,
629
+ timestamp TEXT NOT NULL,
630
+ name TEXT NOT NULL,
631
+ type TEXT NOT NULL CHECK (type IN ('counter','gauge','histogram')),
632
+ value REAL NOT NULL,
633
+ tags_json TEXT DEFAULT '{}',
634
+ service TEXT NOT NULL,
635
+ environment TEXT
636
+ );
637
+
638
+ -- Traces table
639
+ CREATE TABLE IF NOT EXISTS traces (
640
+ trace_id TEXT NOT NULL,
641
+ span_id TEXT PRIMARY KEY,
642
+ parent_span_id TEXT,
643
+ service TEXT NOT NULL,
644
+ symbol TEXT NOT NULL,
645
+ operation TEXT NOT NULL,
646
+ start_time TEXT NOT NULL,
647
+ end_time TEXT,
648
+ duration_ms REAL,
649
+ status TEXT NOT NULL DEFAULT 'ok',
650
+ tags_json TEXT DEFAULT '{}',
651
+ log_ids_json TEXT DEFAULT '[]'
652
+ );
653
+
654
+ -- Indexes
655
+ CREATE INDEX IF NOT EXISTS idx_incidents_timestamp ON incidents(timestamp);
656
+ CREATE INDEX IF NOT EXISTS idx_incidents_status ON incidents(status);
657
+ CREATE INDEX IF NOT EXISTS idx_incidents_environment ON incidents(environment);
658
+ CREATE INDEX IF NOT EXISTS idx_patterns_source ON patterns(source);
659
+ CREATE INDEX IF NOT EXISTS idx_practice_events_timestamp ON practice_events(timestamp);
660
+ CREATE INDEX IF NOT EXISTS idx_practice_events_habit_id ON practice_events(habit_id);
661
+ CREATE INDEX IF NOT EXISTS idx_practice_events_engineer ON practice_events(engineer);
662
+ CREATE INDEX IF NOT EXISTS idx_practice_events_session_id ON practice_events(session_id);
663
+ CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp);
664
+ CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level);
665
+ CREATE INDEX IF NOT EXISTS idx_logs_symbol ON logs(symbol);
666
+ CREATE INDEX IF NOT EXISTS idx_logs_service ON logs(service);
667
+ CREATE INDEX IF NOT EXISTS idx_logs_session_id ON logs(session_id);
668
+ CREATE INDEX IF NOT EXISTS idx_logs_correlation_id ON logs(correlation_id);
669
+ CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON metrics(timestamp);
670
+ CREATE INDEX IF NOT EXISTS idx_metrics_name ON metrics(name);
671
+ CREATE INDEX IF NOT EXISTS idx_metrics_service ON metrics(service);
672
+ CREATE INDEX IF NOT EXISTS idx_traces_trace_id ON traces(trace_id);
673
+ CREATE INDEX IF NOT EXISTS idx_traces_service ON traces(service);
674
+ CREATE INDEX IF NOT EXISTS idx_traces_start_time ON traces(start_time);
675
+ `);
676
+ this.db.run(
677
+ "INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', ?)",
678
+ [String(SCHEMA_VERSION)]
679
+ );
680
+ }
681
+ save() {
682
+ if (!this.db) return;
683
+ const data = this.db.export();
684
+ const buffer = Buffer.from(data);
685
+ fs.writeFileSync(this.dbPath, buffer);
686
+ }
687
+ // ─── Incidents ───────────────────────────────────────────────────
688
+ recordIncident(input) {
689
+ const db = this.db;
690
+ if (!db) {
691
+ this.initializeSync();
692
+ }
693
+ this.incidentCounter++;
694
+ const id = `INC-${String(this.incidentCounter).padStart(3, "0")}`;
695
+ const now = (/* @__PURE__ */ new Date()).toISOString();
696
+ this.db.run(
697
+ `INSERT INTO incidents (
698
+ id, timestamp, status, error_message, error_stack, error_code, error_type,
699
+ symbols, flow_position, environment, service, version, user_id, request_id,
700
+ group_id, notes, related_incidents, resolved_at, resolved_by, resolution,
701
+ created_at, updated_at
702
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
703
+ [
704
+ id,
705
+ input.timestamp || now,
706
+ input.status || "open",
707
+ input.error.message,
708
+ input.error.stack || null,
709
+ input.error.code || null,
710
+ input.error.type || null,
711
+ JSON.stringify(input.symbols),
712
+ input.flowPosition ? JSON.stringify(input.flowPosition) : null,
713
+ input.environment,
714
+ input.service || null,
715
+ input.version || null,
716
+ input.userId || null,
717
+ input.requestId || null,
718
+ input.groupId || null,
719
+ "[]",
720
+ "[]",
721
+ input.resolvedAt || null,
722
+ input.resolvedBy || null,
723
+ input.resolution ? JSON.stringify(input.resolution) : null,
724
+ now,
725
+ now
726
+ ]
727
+ );
728
+ this.save();
729
+ return id;
730
+ }
731
+ initializeSync() {
732
+ if (this.initialized && this.db) return;
733
+ if (!this.db && SQL) {
734
+ const dir = path.dirname(this.dbPath);
735
+ if (!fs.existsSync(dir)) {
736
+ fs.mkdirSync(dir, { recursive: true });
737
+ }
738
+ if (fs.existsSync(this.dbPath)) {
739
+ const fileData = fs.readFileSync(this.dbPath);
740
+ this.db = new SQL.Database(fileData);
741
+ } else {
742
+ this.db = new SQL.Database();
743
+ this.createSchema();
744
+ }
745
+ try {
746
+ const result = this.db.exec(
747
+ "SELECT MAX(CAST(SUBSTR(id, 5) AS INTEGER)) as max FROM incidents"
748
+ );
749
+ if (result.length > 0 && result[0].values.length > 0 && result[0].values[0][0]) {
750
+ this.incidentCounter = result[0].values[0][0];
751
+ }
752
+ } catch {
753
+ this.incidentCounter = 0;
754
+ }
755
+ this.migrateSchema();
756
+ this.initialized = true;
757
+ this.save();
758
+ }
759
+ }
760
+ /**
761
+ * Run schema migrations from older versions
762
+ */
763
+ migrateSchema() {
764
+ if (!this.db) return;
765
+ let currentVersion = 1;
766
+ try {
767
+ const result = this.db.exec(
768
+ "SELECT value FROM metadata WHERE key = 'schema_version'"
769
+ );
770
+ if (result.length > 0 && result[0].values.length > 0) {
771
+ currentVersion = parseInt(result[0].values[0][0], 10) || 1;
772
+ }
773
+ } catch {
774
+ }
775
+ if (currentVersion < 2) {
776
+ try {
777
+ this.db.run(`
778
+ CREATE TABLE IF NOT EXISTS practice_events (
779
+ id TEXT PRIMARY KEY,
780
+ timestamp TEXT NOT NULL,
781
+ habit_id TEXT NOT NULL,
782
+ habit_category TEXT NOT NULL,
783
+ result TEXT NOT NULL CHECK (result IN ('followed', 'skipped', 'partial')),
784
+ engineer TEXT NOT NULL,
785
+ session_id TEXT NOT NULL,
786
+ lore_entry_id TEXT,
787
+ task_description TEXT,
788
+ symbols_touched TEXT DEFAULT '[]',
789
+ files_modified TEXT DEFAULT '[]',
790
+ related_incident_id TEXT,
791
+ notes TEXT
792
+ );
793
+
794
+ CREATE INDEX IF NOT EXISTS idx_practice_events_timestamp ON practice_events(timestamp);
795
+ CREATE INDEX IF NOT EXISTS idx_practice_events_habit_id ON practice_events(habit_id);
796
+ CREATE INDEX IF NOT EXISTS idx_practice_events_engineer ON practice_events(engineer);
797
+ CREATE INDEX IF NOT EXISTS idx_practice_events_session_id ON practice_events(session_id);
798
+ `);
799
+ } catch {
800
+ }
801
+ this.db.run(
802
+ "INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '2')"
803
+ );
804
+ currentVersion = 2;
805
+ }
806
+ if (currentVersion < 3) {
807
+ try {
808
+ this.db.run(`
809
+ CREATE TABLE IF NOT EXISTS logs (
810
+ id TEXT PRIMARY KEY,
811
+ timestamp TEXT NOT NULL,
812
+ level TEXT NOT NULL CHECK (level IN ('debug','info','warn','error')),
813
+ symbol TEXT NOT NULL,
814
+ symbol_type TEXT NOT NULL DEFAULT 'raw',
815
+ message TEXT NOT NULL,
816
+ data_json TEXT,
817
+ service TEXT NOT NULL,
818
+ session_id TEXT,
819
+ correlation_id TEXT,
820
+ duration_ms REAL,
821
+ environment TEXT
822
+ );
823
+
824
+ CREATE TABLE IF NOT EXISTS services (
825
+ name TEXT PRIMARY KEY,
826
+ version TEXT,
827
+ pid INTEGER,
828
+ started_at TEXT NOT NULL,
829
+ last_seen_at TEXT NOT NULL,
830
+ environment TEXT,
831
+ metadata_json TEXT
832
+ );
833
+
834
+ CREATE TABLE IF NOT EXISTS app_state (
835
+ service TEXT NOT NULL,
836
+ session_id TEXT NOT NULL,
837
+ timestamp TEXT NOT NULL,
838
+ state_json TEXT NOT NULL,
839
+ active_flows_json TEXT,
840
+ active_gates_json TEXT,
841
+ PRIMARY KEY (service, session_id)
842
+ );
843
+
844
+ CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp);
845
+ CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level);
846
+ CREATE INDEX IF NOT EXISTS idx_logs_symbol ON logs(symbol);
847
+ CREATE INDEX IF NOT EXISTS idx_logs_service ON logs(service);
848
+ CREATE INDEX IF NOT EXISTS idx_logs_session_id ON logs(session_id);
849
+ CREATE INDEX IF NOT EXISTS idx_logs_correlation_id ON logs(correlation_id);
850
+ `);
851
+ } catch {
852
+ }
853
+ this.db.run(
854
+ "INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '3')"
855
+ );
856
+ currentVersion = 3;
857
+ }
858
+ if (currentVersion < 4) {
859
+ try {
860
+ this.db.run(`
861
+ CREATE TABLE IF NOT EXISTS metrics (
862
+ id TEXT PRIMARY KEY,
863
+ timestamp TEXT NOT NULL,
864
+ name TEXT NOT NULL,
865
+ type TEXT NOT NULL CHECK (type IN ('counter','gauge','histogram')),
866
+ value REAL NOT NULL,
867
+ tags_json TEXT DEFAULT '{}',
868
+ service TEXT NOT NULL,
869
+ environment TEXT
870
+ );
871
+
872
+ CREATE TABLE IF NOT EXISTS traces (
873
+ trace_id TEXT NOT NULL,
874
+ span_id TEXT PRIMARY KEY,
875
+ parent_span_id TEXT,
876
+ service TEXT NOT NULL,
877
+ symbol TEXT NOT NULL,
878
+ operation TEXT NOT NULL,
879
+ start_time TEXT NOT NULL,
880
+ end_time TEXT,
881
+ duration_ms REAL,
882
+ status TEXT NOT NULL DEFAULT 'ok',
883
+ tags_json TEXT DEFAULT '{}',
884
+ log_ids_json TEXT DEFAULT '[]'
885
+ );
886
+
887
+ CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON metrics(timestamp);
888
+ CREATE INDEX IF NOT EXISTS idx_metrics_name ON metrics(name);
889
+ CREATE INDEX IF NOT EXISTS idx_metrics_service ON metrics(service);
890
+ CREATE INDEX IF NOT EXISTS idx_traces_trace_id ON traces(trace_id);
891
+ CREATE INDEX IF NOT EXISTS idx_traces_service ON traces(service);
892
+ CREATE INDEX IF NOT EXISTS idx_traces_start_time ON traces(start_time);
893
+ `);
894
+ } catch {
895
+ }
896
+ this.db.run(
897
+ "INSERT OR REPLACE INTO metadata (key, value) VALUES ('schema_version', '4')"
898
+ );
899
+ }
900
+ }
901
+ /**
902
+ * Ensure the storage is ready for use. Must be called once before using storage methods.
903
+ */
904
+ async ensureReady() {
905
+ if (!SQL) {
906
+ SQL = await initSqlJs();
907
+ }
908
+ this.initializeSync();
909
+ }
910
+ getIncident(id) {
911
+ this.initializeSync();
912
+ const result = this.db.exec("SELECT * FROM incidents WHERE id = ?", [id]);
913
+ if (result.length === 0 || result[0].values.length === 0) return null;
914
+ return this.rowToIncident(result[0].columns, result[0].values[0]);
915
+ }
916
+ getRecentIncidents(options = {}) {
917
+ this.initializeSync();
918
+ const { limit = 50, offset = 0 } = options;
919
+ const conditions = [];
920
+ const params = [];
921
+ if (options.status && options.status !== "all") {
922
+ conditions.push("status = ?");
923
+ params.push(options.status);
924
+ }
925
+ if (options.environment) {
926
+ conditions.push("environment = ?");
927
+ params.push(options.environment);
928
+ }
929
+ if (options.symbol) {
930
+ conditions.push("symbols LIKE ?");
931
+ params.push(`%${options.symbol}%`);
932
+ }
933
+ if (options.search) {
934
+ conditions.push("(error_message LIKE ? OR notes LIKE ?)");
935
+ params.push(`%${options.search}%`, `%${options.search}%`);
936
+ }
937
+ if (options.dateFrom) {
938
+ conditions.push("timestamp >= ?");
939
+ params.push(options.dateFrom);
940
+ }
941
+ if (options.dateTo) {
942
+ conditions.push("timestamp <= ?");
943
+ params.push(options.dateTo);
944
+ }
945
+ if (options.groupId) {
946
+ conditions.push("group_id = ?");
947
+ params.push(options.groupId);
948
+ }
949
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
950
+ const result = this.db.exec(
951
+ `SELECT * FROM incidents ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
952
+ [...params, limit, offset]
953
+ );
954
+ if (result.length === 0) return [];
955
+ return result[0].values.map(
956
+ (row) => this.rowToIncident(result[0].columns, row)
957
+ );
958
+ }
959
+ updateIncident(id, updates) {
960
+ this.initializeSync();
961
+ const now = (/* @__PURE__ */ new Date()).toISOString();
962
+ const setClauses = ["updated_at = ?"];
963
+ const params = [now];
964
+ if (updates.status !== void 0) {
965
+ setClauses.push("status = ?");
966
+ params.push(updates.status);
967
+ }
968
+ if (updates.error !== void 0) {
969
+ setClauses.push("error_message = ?");
970
+ params.push(updates.error.message);
971
+ if (updates.error.stack !== void 0) {
972
+ setClauses.push("error_stack = ?");
973
+ params.push(updates.error.stack || null);
974
+ }
975
+ }
976
+ if (updates.symbols !== void 0) {
977
+ setClauses.push("symbols = ?");
978
+ params.push(JSON.stringify(updates.symbols));
979
+ }
980
+ if (updates.flowPosition !== void 0) {
981
+ setClauses.push("flow_position = ?");
982
+ params.push(
983
+ updates.flowPosition ? JSON.stringify(updates.flowPosition) : null
984
+ );
985
+ }
986
+ if (updates.groupId !== void 0) {
987
+ setClauses.push("group_id = ?");
988
+ params.push(updates.groupId || null);
989
+ }
990
+ if (updates.resolvedAt !== void 0) {
991
+ setClauses.push("resolved_at = ?");
992
+ params.push(updates.resolvedAt || null);
993
+ }
994
+ if (updates.resolvedBy !== void 0) {
995
+ setClauses.push("resolved_by = ?");
996
+ params.push(updates.resolvedBy || null);
997
+ }
998
+ if (updates.resolution !== void 0) {
999
+ setClauses.push("resolution = ?");
1000
+ params.push(
1001
+ updates.resolution ? JSON.stringify(updates.resolution) : null
1002
+ );
1003
+ }
1004
+ params.push(id);
1005
+ this.db.run(
1006
+ `UPDATE incidents SET ${setClauses.join(", ")} WHERE id = ?`,
1007
+ params
1008
+ );
1009
+ this.save();
1010
+ }
1011
+ addIncidentNote(incidentId, note) {
1012
+ this.initializeSync();
1013
+ const incident = this.getIncident(incidentId);
1014
+ if (!incident) return;
1015
+ const newNote = {
1016
+ id: v4_default(),
1017
+ ...note
1018
+ };
1019
+ const notes = [...incident.notes, newNote];
1020
+ this.db.run(
1021
+ "UPDATE incidents SET notes = ?, updated_at = ? WHERE id = ?",
1022
+ [JSON.stringify(notes), (/* @__PURE__ */ new Date()).toISOString(), incidentId]
1023
+ );
1024
+ this.save();
1025
+ }
1026
+ linkIncidents(incidentId, relatedId) {
1027
+ this.initializeSync();
1028
+ const incident = this.getIncident(incidentId);
1029
+ if (!incident) return;
1030
+ if (!incident.relatedIncidents.includes(relatedId)) {
1031
+ const related = [...incident.relatedIncidents, relatedId];
1032
+ this.db.run(
1033
+ "UPDATE incidents SET related_incidents = ?, updated_at = ? WHERE id = ?",
1034
+ [JSON.stringify(related), (/* @__PURE__ */ new Date()).toISOString(), incidentId]
1035
+ );
1036
+ }
1037
+ const relatedIncident = this.getIncident(relatedId);
1038
+ if (relatedIncident && !relatedIncident.relatedIncidents.includes(incidentId)) {
1039
+ const related = [...relatedIncident.relatedIncidents, incidentId];
1040
+ this.db.run(
1041
+ "UPDATE incidents SET related_incidents = ?, updated_at = ? WHERE id = ?",
1042
+ [JSON.stringify(related), (/* @__PURE__ */ new Date()).toISOString(), relatedId]
1043
+ );
1044
+ }
1045
+ this.save();
1046
+ }
1047
+ getIncidentCount(options = {}) {
1048
+ this.initializeSync();
1049
+ const conditions = [];
1050
+ const params = [];
1051
+ if (options.status && options.status !== "all") {
1052
+ conditions.push("status = ?");
1053
+ params.push(options.status);
1054
+ }
1055
+ if (options.environment) {
1056
+ conditions.push("environment = ?");
1057
+ params.push(options.environment);
1058
+ }
1059
+ if (options.dateFrom) {
1060
+ conditions.push("timestamp >= ?");
1061
+ params.push(options.dateFrom);
1062
+ }
1063
+ if (options.dateTo) {
1064
+ conditions.push("timestamp <= ?");
1065
+ params.push(options.dateTo);
1066
+ }
1067
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1068
+ const result = this.db.exec(
1069
+ `SELECT COUNT(*) as count FROM incidents ${whereClause}`,
1070
+ params
1071
+ );
1072
+ if (result.length === 0 || result[0].values.length === 0) return 0;
1073
+ return result[0].values[0][0];
1074
+ }
1075
+ // ─── Patterns ────────────────────────────────────────────────────
1076
+ addPattern(input) {
1077
+ this.initializeSync();
1078
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1079
+ const confidence = {
1080
+ ...DEFAULT_CONFIDENCE,
1081
+ ...input.confidence
1082
+ };
1083
+ this.db.run(
1084
+ `INSERT INTO patterns (
1085
+ id, name, description, pattern, resolution, confidence,
1086
+ source, private, tags, created_at, updated_at
1087
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1088
+ [
1089
+ input.id,
1090
+ input.name,
1091
+ input.description || null,
1092
+ JSON.stringify(input.pattern),
1093
+ JSON.stringify(input.resolution),
1094
+ JSON.stringify(confidence),
1095
+ input.source,
1096
+ input.private ? 1 : 0,
1097
+ JSON.stringify(input.tags || []),
1098
+ now,
1099
+ now
1100
+ ]
1101
+ );
1102
+ this.save();
1103
+ return input.id;
1104
+ }
1105
+ getPattern(id) {
1106
+ this.initializeSync();
1107
+ const result = this.db.exec("SELECT * FROM patterns WHERE id = ?", [id]);
1108
+ if (result.length === 0 || result[0].values.length === 0) return null;
1109
+ return this.rowToPattern(result[0].columns, result[0].values[0]);
1110
+ }
1111
+ getAllPatterns(options = {}) {
1112
+ this.initializeSync();
1113
+ const conditions = [];
1114
+ const params = [];
1115
+ if (options.source) {
1116
+ conditions.push("source = ?");
1117
+ params.push(options.source);
1118
+ }
1119
+ if (options.minConfidence !== void 0) {
1120
+ conditions.push("json_extract(confidence, '$.score') >= ?");
1121
+ params.push(options.minConfidence);
1122
+ }
1123
+ if (!options.includePrivate) {
1124
+ conditions.push("private = 0");
1125
+ }
1126
+ if (options.tags && options.tags.length > 0) {
1127
+ const tagConditions = options.tags.map(() => "tags LIKE ?");
1128
+ conditions.push(`(${tagConditions.join(" OR ")})`);
1129
+ params.push(...options.tags.map((tag) => `%"${tag}"%`));
1130
+ }
1131
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1132
+ const result = this.db.exec(
1133
+ `SELECT * FROM patterns ${whereClause} ORDER BY json_extract(confidence, '$.score') DESC`,
1134
+ params
1135
+ );
1136
+ if (result.length === 0) return [];
1137
+ return result[0].values.map(
1138
+ (row) => this.rowToPattern(result[0].columns, row)
1139
+ );
1140
+ }
1141
+ updatePattern(id, updates) {
1142
+ this.initializeSync();
1143
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1144
+ const setClauses = ["updated_at = ?"];
1145
+ const params = [now];
1146
+ if (updates.name !== void 0) {
1147
+ setClauses.push("name = ?");
1148
+ params.push(updates.name);
1149
+ }
1150
+ if (updates.description !== void 0) {
1151
+ setClauses.push("description = ?");
1152
+ params.push(updates.description || null);
1153
+ }
1154
+ if (updates.pattern !== void 0) {
1155
+ setClauses.push("pattern = ?");
1156
+ params.push(JSON.stringify(updates.pattern));
1157
+ }
1158
+ if (updates.resolution !== void 0) {
1159
+ setClauses.push("resolution = ?");
1160
+ params.push(JSON.stringify(updates.resolution));
1161
+ }
1162
+ if (updates.confidence !== void 0) {
1163
+ setClauses.push("confidence = ?");
1164
+ params.push(JSON.stringify(updates.confidence));
1165
+ }
1166
+ if (updates.source !== void 0) {
1167
+ setClauses.push("source = ?");
1168
+ params.push(updates.source);
1169
+ }
1170
+ if (updates.private !== void 0) {
1171
+ setClauses.push("private = ?");
1172
+ params.push(updates.private ? 1 : 0);
1173
+ }
1174
+ if (updates.tags !== void 0) {
1175
+ setClauses.push("tags = ?");
1176
+ params.push(JSON.stringify(updates.tags));
1177
+ }
1178
+ params.push(id);
1179
+ this.db.run(
1180
+ `UPDATE patterns SET ${setClauses.join(", ")} WHERE id = ?`,
1181
+ params
1182
+ );
1183
+ this.save();
1184
+ }
1185
+ deletePattern(id) {
1186
+ this.initializeSync();
1187
+ this.db.run("DELETE FROM patterns WHERE id = ?", [id]);
1188
+ this.save();
1189
+ }
1190
+ updatePatternConfidence(patternId, event) {
1191
+ const pattern = this.getPattern(patternId);
1192
+ if (!pattern) return;
1193
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1194
+ const confidence = { ...pattern.confidence };
1195
+ switch (event) {
1196
+ case "matched":
1197
+ confidence.timesMatched++;
1198
+ confidence.lastMatched = now;
1199
+ break;
1200
+ case "resolved":
1201
+ confidence.timesResolved++;
1202
+ confidence.lastResolved = now;
1203
+ confidence.score = Math.min(100, confidence.score + 2);
1204
+ break;
1205
+ case "recurred":
1206
+ confidence.timesRecurred++;
1207
+ confidence.score = Math.max(10, confidence.score - 5);
1208
+ break;
1209
+ }
1210
+ this.updatePattern(patternId, { confidence });
1211
+ }
1212
+ // ─── Groups ──────────────────────────────────────────────────────
1213
+ createGroup(input) {
1214
+ this.initializeSync();
1215
+ const id = `GRP-${v4_default().substring(0, 8)}`;
1216
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1217
+ this.db.run(
1218
+ `INSERT INTO groups (
1219
+ id, name, common_symbols, common_error_patterns,
1220
+ suggested_pattern_id, first_seen, last_seen, created_at, updated_at
1221
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1222
+ [
1223
+ id,
1224
+ input.name || null,
1225
+ JSON.stringify(input.commonSymbols),
1226
+ JSON.stringify(input.commonErrorPatterns),
1227
+ input.suggestedPattern?.id || null,
1228
+ input.firstSeen,
1229
+ input.lastSeen,
1230
+ now,
1231
+ now
1232
+ ]
1233
+ );
1234
+ for (const incidentId of input.incidents) {
1235
+ this.addToGroup(id, incidentId);
1236
+ }
1237
+ this.save();
1238
+ return id;
1239
+ }
1240
+ getGroup(id) {
1241
+ this.initializeSync();
1242
+ const result = this.db.exec("SELECT * FROM groups WHERE id = ?", [id]);
1243
+ if (result.length === 0 || result[0].values.length === 0) return null;
1244
+ return this.rowToGroup(result[0].columns, result[0].values[0]);
1245
+ }
1246
+ getGroups(options = {}) {
1247
+ this.initializeSync();
1248
+ const limit = options.limit || 100;
1249
+ const result = this.db.exec(
1250
+ "SELECT * FROM groups ORDER BY last_seen DESC LIMIT ?",
1251
+ [limit]
1252
+ );
1253
+ if (result.length === 0) return [];
1254
+ return result[0].values.map(
1255
+ (row) => this.rowToGroup(result[0].columns, row)
1256
+ );
1257
+ }
1258
+ addToGroup(groupId, incidentId) {
1259
+ this.initializeSync();
1260
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1261
+ this.db.run(
1262
+ "INSERT OR IGNORE INTO group_members (group_id, incident_id, added_at) VALUES (?, ?, ?)",
1263
+ [groupId, incidentId, now]
1264
+ );
1265
+ this.db.run("UPDATE incidents SET group_id = ? WHERE id = ?", [
1266
+ groupId,
1267
+ incidentId
1268
+ ]);
1269
+ this.db.run(
1270
+ "UPDATE groups SET last_seen = ?, updated_at = ? WHERE id = ?",
1271
+ [now, now, groupId]
1272
+ );
1273
+ this.save();
1274
+ }
1275
+ // ─── Resolutions ─────────────────────────────────────────────────
1276
+ recordResolution(resolution) {
1277
+ this.initializeSync();
1278
+ const id = v4_default();
1279
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1280
+ this.db.run(
1281
+ `INSERT INTO resolutions (id, incident_id, pattern_id, commit_hash, pr_url, notes, resolved_at)
1282
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1283
+ [
1284
+ id,
1285
+ resolution.incidentId,
1286
+ resolution.patternId || null,
1287
+ resolution.commitHash || null,
1288
+ resolution.prUrl || null,
1289
+ resolution.notes || null,
1290
+ now
1291
+ ]
1292
+ );
1293
+ this.updateIncident(resolution.incidentId, {
1294
+ status: "resolved",
1295
+ resolvedAt: now,
1296
+ resolvedBy: resolution.patternId || "manual",
1297
+ resolution: {
1298
+ patternId: resolution.patternId,
1299
+ commitHash: resolution.commitHash,
1300
+ prUrl: resolution.prUrl,
1301
+ notes: resolution.notes
1302
+ }
1303
+ });
1304
+ if (resolution.patternId) {
1305
+ this.updatePatternConfidence(resolution.patternId, "resolved");
1306
+ }
1307
+ this.save();
1308
+ }
1309
+ markRecurred(incidentId) {
1310
+ this.initializeSync();
1311
+ this.db.run(
1312
+ "UPDATE resolutions SET recurred = 1 WHERE incident_id = ?",
1313
+ [incidentId]
1314
+ );
1315
+ const result = this.db.exec(
1316
+ "SELECT pattern_id FROM resolutions WHERE incident_id = ?",
1317
+ [incidentId]
1318
+ );
1319
+ if (result.length > 0 && result[0].values.length > 0 && result[0].values[0][0]) {
1320
+ this.updatePatternConfidence(result[0].values[0][0], "recurred");
1321
+ }
1322
+ this.save();
1323
+ }
1324
+ getResolutionHistory(options = {}) {
1325
+ this.initializeSync();
1326
+ const conditions = [];
1327
+ const params = [];
1328
+ if (options.patternId) {
1329
+ conditions.push("pattern_id = ?");
1330
+ params.push(options.patternId);
1331
+ }
1332
+ if (options.symbol) {
1333
+ conditions.push(`incident_id IN (
1334
+ SELECT id FROM incidents WHERE symbols LIKE ?
1335
+ )`);
1336
+ params.push(`%${options.symbol}%`);
1337
+ }
1338
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1339
+ const limit = options.limit || 100;
1340
+ const result = this.db.exec(
1341
+ `SELECT * FROM resolutions ${whereClause} ORDER BY resolved_at DESC LIMIT ?`,
1342
+ [...params, limit]
1343
+ );
1344
+ if (result.length === 0) return [];
1345
+ const columns = result[0].columns;
1346
+ return result[0].values.map((row) => {
1347
+ const obj = {};
1348
+ columns.forEach((col, i) => {
1349
+ obj[col] = row[i];
1350
+ });
1351
+ return {
1352
+ id: obj.id,
1353
+ incidentId: obj.incident_id,
1354
+ patternId: obj.pattern_id || void 0,
1355
+ commitHash: obj.commit_hash || void 0,
1356
+ prUrl: obj.pr_url || void 0,
1357
+ notes: obj.notes || void 0,
1358
+ resolvedAt: obj.resolved_at,
1359
+ recurred: obj.recurred === 1
1360
+ };
1361
+ });
1362
+ }
1363
+ // ─── Stats ───────────────────────────────────────────────────────
1364
+ getStats(period) {
1365
+ this.initializeSync();
1366
+ const { start, end } = period;
1367
+ const total = this.getIncidentCount({ dateFrom: start, dateTo: end });
1368
+ const open = this.getIncidentCount({
1369
+ dateFrom: start,
1370
+ dateTo: end,
1371
+ status: "open"
1372
+ });
1373
+ const resolved = this.getIncidentCount({
1374
+ dateFrom: start,
1375
+ dateTo: end,
1376
+ status: "resolved"
1377
+ });
1378
+ const envResult = this.db.exec(
1379
+ `SELECT environment, COUNT(*) as count
1380
+ FROM incidents
1381
+ WHERE timestamp >= ? AND timestamp <= ?
1382
+ GROUP BY environment`,
1383
+ [start, end]
1384
+ );
1385
+ const byEnvironment = {};
1386
+ if (envResult.length > 0) {
1387
+ for (const row of envResult[0].values) {
1388
+ byEnvironment[row[0]] = row[1];
1389
+ }
1390
+ }
1391
+ const dayResult = this.db.exec(
1392
+ `SELECT DATE(timestamp) as date, COUNT(*) as count
1393
+ FROM incidents
1394
+ WHERE timestamp >= ? AND timestamp <= ?
1395
+ GROUP BY DATE(timestamp)
1396
+ ORDER BY date`,
1397
+ [start, end]
1398
+ );
1399
+ const byDay = [];
1400
+ if (dayResult.length > 0) {
1401
+ for (const row of dayResult[0].values) {
1402
+ byDay.push({ date: row[0], count: row[1] });
1403
+ }
1404
+ }
1405
+ const patterns = this.getAllPatterns({ includePrivate: true });
1406
+ const avgConfidence = patterns.length > 0 ? patterns.reduce((sum, p) => sum + p.confidence.score, 0) / patterns.length : 0;
1407
+ const mostEffective = patterns.sort((a, b) => b.confidence.timesResolved - a.confidence.timesResolved).slice(0, 5).map((p) => ({ patternId: p.id, resolvedCount: p.confidence.timesResolved }));
1408
+ const leastEffective = patterns.filter((p) => p.confidence.timesMatched > 0).map((p) => ({
1409
+ patternId: p.id,
1410
+ recurrenceRate: p.confidence.timesRecurred / Math.max(1, p.confidence.timesResolved)
1411
+ })).sort((a, b) => b.recurrenceRate - a.recurrenceRate).slice(0, 5);
1412
+ const symbolCounts = /* @__PURE__ */ new Map();
1413
+ const incidents = this.getRecentIncidents({
1414
+ dateFrom: start,
1415
+ dateTo: end,
1416
+ limit: 1e3
1417
+ });
1418
+ for (const incident of incidents) {
1419
+ for (const [, value] of Object.entries(incident.symbols)) {
1420
+ if (value) {
1421
+ symbolCounts.set(value, (symbolCounts.get(value) || 0) + 1);
1422
+ }
1423
+ }
1424
+ }
1425
+ const mostIncidents = Array.from(symbolCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([symbol, count]) => ({ symbol, count }));
1426
+ const resolutions = this.getResolutionHistory({ limit: 1e3 });
1427
+ const periodResolutions = resolutions.filter(
1428
+ (r) => r.resolvedAt >= start && r.resolvedAt <= end
1429
+ );
1430
+ const resolvedWithPattern = periodResolutions.filter(
1431
+ (r) => r.patternId
1432
+ ).length;
1433
+ const resolvedManually = periodResolutions.length - resolvedWithPattern;
1434
+ return {
1435
+ period: { start, end },
1436
+ incidents: {
1437
+ total,
1438
+ open,
1439
+ resolved,
1440
+ byEnvironment,
1441
+ byDay
1442
+ },
1443
+ patterns: {
1444
+ total: patterns.length,
1445
+ avgConfidence: Math.round(avgConfidence),
1446
+ mostEffective,
1447
+ leastEffective
1448
+ },
1449
+ symbols: {
1450
+ mostIncidents,
1451
+ mostResolved: [],
1452
+ hotspots: mostIncidents.slice(0, 5).map((s) => ({
1453
+ symbol: s.symbol,
1454
+ incidentRate: s.count / Math.max(1, total)
1455
+ }))
1456
+ },
1457
+ resolution: {
1458
+ avgTimeToResolve: 0,
1459
+ resolvedWithPattern,
1460
+ resolvedManually,
1461
+ resolutionRate: total > 0 ? resolved / total * 100 : 0
1462
+ }
1463
+ };
1464
+ }
1465
+ getSymbolHealth(symbol) {
1466
+ const incidents = this.getRecentIncidents({ symbol, limit: 1e3 });
1467
+ const incidentCount = incidents.length;
1468
+ const patternCounts = /* @__PURE__ */ new Map();
1469
+ for (const incident of incidents) {
1470
+ if (incident.resolution?.patternId) {
1471
+ const count = patternCounts.get(incident.resolution.patternId) || 0;
1472
+ patternCounts.set(incident.resolution.patternId, count + 1);
1473
+ }
1474
+ }
1475
+ const topPatterns = Array.from(patternCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([patternId, count]) => ({ patternId, count }));
1476
+ return {
1477
+ incidentCount,
1478
+ avgTimeToResolve: 0,
1479
+ topPatterns
1480
+ };
1481
+ }
1482
+ // ─── Import/Export ───────────────────────────────────────────────
1483
+ exportPatterns(options = {}) {
1484
+ const patterns = this.getAllPatterns({
1485
+ includePrivate: options.includePrivate
1486
+ });
1487
+ return {
1488
+ version: "1.0.0",
1489
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1490
+ patterns
1491
+ };
1492
+ }
1493
+ importPatterns(data, options = {}) {
1494
+ let imported = 0;
1495
+ let skipped = 0;
1496
+ for (const pattern of data.patterns) {
1497
+ const existing = this.getPattern(pattern.id);
1498
+ if (existing && !options.overwrite) {
1499
+ skipped++;
1500
+ continue;
1501
+ }
1502
+ if (existing) {
1503
+ this.updatePattern(pattern.id, pattern);
1504
+ } else {
1505
+ this.addPattern({
1506
+ ...pattern,
1507
+ source: "imported"
1508
+ });
1509
+ }
1510
+ imported++;
1511
+ }
1512
+ return { imported, skipped };
1513
+ }
1514
+ exportBackup() {
1515
+ const incidents = this.getRecentIncidents({ limit: 1e5 });
1516
+ const patterns = this.getAllPatterns({ includePrivate: true });
1517
+ const groups = this.getGroups({ limit: 1e4 });
1518
+ return {
1519
+ version: "1.0.0",
1520
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1521
+ incidents,
1522
+ patterns,
1523
+ groups
1524
+ };
1525
+ }
1526
+ importBackup(data) {
1527
+ this.initializeSync();
1528
+ this.db.run("DELETE FROM group_members");
1529
+ this.db.run("DELETE FROM resolutions");
1530
+ this.db.run("DELETE FROM groups");
1531
+ this.db.run("DELETE FROM incidents");
1532
+ this.db.run("DELETE FROM patterns");
1533
+ for (const pattern of data.patterns) {
1534
+ this.addPattern(pattern);
1535
+ }
1536
+ for (const incident of data.incidents) {
1537
+ const now2 = incident.timestamp;
1538
+ this.db.run(
1539
+ `INSERT INTO incidents (
1540
+ id, timestamp, status, error_message, error_stack, error_code, error_type,
1541
+ symbols, flow_position, environment, service, version, user_id, request_id,
1542
+ group_id, notes, related_incidents, resolved_at, resolved_by, resolution,
1543
+ created_at, updated_at
1544
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1545
+ [
1546
+ incident.id,
1547
+ incident.timestamp,
1548
+ incident.status,
1549
+ incident.error.message,
1550
+ incident.error.stack || null,
1551
+ incident.error.code || null,
1552
+ incident.error.type || null,
1553
+ JSON.stringify(incident.symbols),
1554
+ incident.flowPosition ? JSON.stringify(incident.flowPosition) : null,
1555
+ incident.environment,
1556
+ incident.service || null,
1557
+ incident.version || null,
1558
+ incident.userId || null,
1559
+ incident.requestId || null,
1560
+ incident.groupId || null,
1561
+ JSON.stringify(incident.notes),
1562
+ JSON.stringify(incident.relatedIncidents),
1563
+ incident.resolvedAt || null,
1564
+ incident.resolvedBy || null,
1565
+ incident.resolution ? JSON.stringify(incident.resolution) : null,
1566
+ now2,
1567
+ now2
1568
+ ]
1569
+ );
1570
+ }
1571
+ const result = this.db.exec(
1572
+ "SELECT MAX(CAST(SUBSTR(id, 5) AS INTEGER)) as max FROM incidents"
1573
+ );
1574
+ if (result.length > 0 && result[0].values.length > 0 && result[0].values[0][0]) {
1575
+ this.incidentCounter = result[0].values[0][0];
1576
+ }
1577
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1578
+ for (const group of data.groups) {
1579
+ this.db.run(
1580
+ `INSERT INTO groups (
1581
+ id, name, common_symbols, common_error_patterns,
1582
+ suggested_pattern_id, first_seen, last_seen, created_at, updated_at
1583
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1584
+ [
1585
+ group.id,
1586
+ group.name || null,
1587
+ JSON.stringify(group.commonSymbols),
1588
+ JSON.stringify(group.commonErrorPatterns),
1589
+ group.suggestedPattern?.id || null,
1590
+ group.firstSeen,
1591
+ group.lastSeen,
1592
+ now,
1593
+ now
1594
+ ]
1595
+ );
1596
+ for (const incidentId of group.incidents) {
1597
+ this.db.run(
1598
+ "INSERT OR IGNORE INTO group_members (group_id, incident_id, added_at) VALUES (?, ?, ?)",
1599
+ [group.id, incidentId, now]
1600
+ );
1601
+ }
1602
+ }
1603
+ this.save();
1604
+ }
1605
+ // ─── Helper Methods ──────────────────────────────────────────────
1606
+ rowToIncident(columns, row) {
1607
+ const obj = {};
1608
+ columns.forEach((col, i) => {
1609
+ obj[col] = row[i];
1610
+ });
1611
+ return {
1612
+ id: obj.id,
1613
+ timestamp: obj.timestamp,
1614
+ status: obj.status,
1615
+ error: {
1616
+ message: obj.error_message,
1617
+ stack: obj.error_stack || void 0,
1618
+ code: obj.error_code || void 0,
1619
+ type: obj.error_type || void 0
1620
+ },
1621
+ symbols: JSON.parse(obj.symbols || "{}"),
1622
+ flowPosition: obj.flow_position ? JSON.parse(obj.flow_position) : void 0,
1623
+ environment: obj.environment,
1624
+ service: obj.service || void 0,
1625
+ version: obj.version || void 0,
1626
+ userId: obj.user_id || void 0,
1627
+ requestId: obj.request_id || void 0,
1628
+ groupId: obj.group_id || void 0,
1629
+ notes: JSON.parse(obj.notes || "[]"),
1630
+ relatedIncidents: JSON.parse(obj.related_incidents || "[]"),
1631
+ resolvedAt: obj.resolved_at || void 0,
1632
+ resolvedBy: obj.resolved_by || void 0,
1633
+ resolution: obj.resolution ? JSON.parse(obj.resolution) : void 0
1634
+ };
1635
+ }
1636
+ rowToPattern(columns, row) {
1637
+ const obj = {};
1638
+ columns.forEach((col, i) => {
1639
+ obj[col] = row[i];
1640
+ });
1641
+ return {
1642
+ id: obj.id,
1643
+ name: obj.name,
1644
+ description: obj.description || "",
1645
+ pattern: JSON.parse(obj.pattern),
1646
+ resolution: JSON.parse(obj.resolution),
1647
+ confidence: JSON.parse(obj.confidence),
1648
+ source: obj.source,
1649
+ private: obj.private === 1,
1650
+ tags: JSON.parse(obj.tags || "[]"),
1651
+ createdAt: obj.created_at,
1652
+ updatedAt: obj.updated_at
1653
+ };
1654
+ }
1655
+ rowToGroup(columns, row) {
1656
+ const obj = {};
1657
+ columns.forEach((col, i) => {
1658
+ obj[col] = row[i];
1659
+ });
1660
+ const membersResult = this.db.exec(
1661
+ "SELECT incident_id FROM group_members WHERE group_id = ?",
1662
+ [obj.id]
1663
+ );
1664
+ const incidents = [];
1665
+ if (membersResult.length > 0) {
1666
+ for (const r of membersResult[0].values) {
1667
+ incidents.push(r[0]);
1668
+ }
1669
+ }
1670
+ const envResult = this.db.exec(
1671
+ `SELECT DISTINCT environment FROM incidents
1672
+ WHERE id IN (SELECT incident_id FROM group_members WHERE group_id = ?)`,
1673
+ [obj.id]
1674
+ );
1675
+ const environments = [];
1676
+ if (envResult.length > 0) {
1677
+ for (const r of envResult[0].values) {
1678
+ environments.push(r[0]);
1679
+ }
1680
+ }
1681
+ return {
1682
+ id: obj.id,
1683
+ name: obj.name || void 0,
1684
+ incidents,
1685
+ commonSymbols: JSON.parse(obj.common_symbols || "{}"),
1686
+ commonErrorPatterns: JSON.parse(
1687
+ obj.common_error_patterns || "[]"
1688
+ ),
1689
+ count: incidents.length,
1690
+ firstSeen: obj.first_seen,
1691
+ lastSeen: obj.last_seen,
1692
+ environments,
1693
+ suggestedPattern: obj.suggested_pattern_id ? this.getPattern(obj.suggested_pattern_id) || void 0 : void 0
1694
+ };
1695
+ }
1696
+ // ─── Practice Events ─────────────────────────────────────────────
1697
+ recordPracticeEvent(input) {
1698
+ this.initializeSync();
1699
+ const id = `PE-${v4_default().substring(0, 8)}`;
1700
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1701
+ this.db.run(
1702
+ `INSERT INTO practice_events (
1703
+ id, timestamp, habit_id, habit_category, result,
1704
+ engineer, session_id, lore_entry_id, task_description,
1705
+ symbols_touched, files_modified, related_incident_id, notes
1706
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1707
+ [
1708
+ id,
1709
+ now,
1710
+ input.habitId,
1711
+ input.habitCategory,
1712
+ input.result,
1713
+ input.engineer,
1714
+ input.sessionId,
1715
+ input.loreEntryId || null,
1716
+ input.taskDescription || null,
1717
+ JSON.stringify(input.symbolsTouched || []),
1718
+ JSON.stringify(input.filesModified || []),
1719
+ input.relatedIncidentId || null,
1720
+ input.notes || null
1721
+ ]
1722
+ );
1723
+ this.save();
1724
+ return id;
1725
+ }
1726
+ getPracticeEvents(options = {}) {
1727
+ this.initializeSync();
1728
+ const { limit = 100, offset = 0 } = options;
1729
+ const conditions = [];
1730
+ const params = [];
1731
+ if (options.habitId) {
1732
+ conditions.push("habit_id = ?");
1733
+ params.push(options.habitId);
1734
+ }
1735
+ if (options.habitCategory) {
1736
+ conditions.push("habit_category = ?");
1737
+ params.push(options.habitCategory);
1738
+ }
1739
+ if (options.result) {
1740
+ conditions.push("result = ?");
1741
+ params.push(options.result);
1742
+ }
1743
+ if (options.engineer) {
1744
+ conditions.push("engineer = ?");
1745
+ params.push(options.engineer);
1746
+ }
1747
+ if (options.sessionId) {
1748
+ conditions.push("session_id = ?");
1749
+ params.push(options.sessionId);
1750
+ }
1751
+ if (options.dateFrom) {
1752
+ conditions.push("timestamp >= ?");
1753
+ params.push(options.dateFrom);
1754
+ }
1755
+ if (options.dateTo) {
1756
+ conditions.push("timestamp <= ?");
1757
+ params.push(options.dateTo);
1758
+ }
1759
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1760
+ const result = this.db.exec(
1761
+ `SELECT * FROM practice_events ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
1762
+ [...params, limit, offset]
1763
+ );
1764
+ if (result.length === 0) return [];
1765
+ return result[0].values.map(
1766
+ (row) => this.rowToPracticeEvent(result[0].columns, row)
1767
+ );
1768
+ }
1769
+ getPracticeEventCount(options = {}) {
1770
+ this.initializeSync();
1771
+ const conditions = [];
1772
+ const params = [];
1773
+ if (options.habitId) {
1774
+ conditions.push("habit_id = ?");
1775
+ params.push(options.habitId);
1776
+ }
1777
+ if (options.habitCategory) {
1778
+ conditions.push("habit_category = ?");
1779
+ params.push(options.habitCategory);
1780
+ }
1781
+ if (options.result) {
1782
+ conditions.push("result = ?");
1783
+ params.push(options.result);
1784
+ }
1785
+ if (options.engineer) {
1786
+ conditions.push("engineer = ?");
1787
+ params.push(options.engineer);
1788
+ }
1789
+ if (options.dateFrom) {
1790
+ conditions.push("timestamp >= ?");
1791
+ params.push(options.dateFrom);
1792
+ }
1793
+ if (options.dateTo) {
1794
+ conditions.push("timestamp <= ?");
1795
+ params.push(options.dateTo);
1796
+ }
1797
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1798
+ const result = this.db.exec(
1799
+ `SELECT COUNT(*) as count FROM practice_events ${whereClause}`,
1800
+ params
1801
+ );
1802
+ if (result.length === 0 || result[0].values.length === 0) return 0;
1803
+ return result[0].values[0][0];
1804
+ }
1805
+ getComplianceRate(options = {}) {
1806
+ this.initializeSync();
1807
+ const conditions = [];
1808
+ const params = [];
1809
+ if (options.habitId) {
1810
+ conditions.push("habit_id = ?");
1811
+ params.push(options.habitId);
1812
+ }
1813
+ if (options.habitCategory) {
1814
+ conditions.push("habit_category = ?");
1815
+ params.push(options.habitCategory);
1816
+ }
1817
+ if (options.engineer) {
1818
+ conditions.push("engineer = ?");
1819
+ params.push(options.engineer);
1820
+ }
1821
+ if (options.dateFrom) {
1822
+ conditions.push("timestamp >= ?");
1823
+ params.push(options.dateFrom);
1824
+ }
1825
+ if (options.dateTo) {
1826
+ conditions.push("timestamp <= ?");
1827
+ params.push(options.dateTo);
1828
+ }
1829
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1830
+ const result = this.db.exec(
1831
+ `SELECT result, COUNT(*) as count
1832
+ FROM practice_events ${whereClause}
1833
+ GROUP BY result`,
1834
+ params
1835
+ );
1836
+ let followed = 0;
1837
+ let skipped = 0;
1838
+ let partial = 0;
1839
+ if (result.length > 0) {
1840
+ for (const row of result[0].values) {
1841
+ const r = row[0];
1842
+ const count = row[1];
1843
+ if (r === "followed") followed = count;
1844
+ else if (r === "skipped") skipped = count;
1845
+ else if (r === "partial") partial = count;
1846
+ }
1847
+ }
1848
+ const total = followed + skipped + partial;
1849
+ const rate = total > 0 ? (followed + partial * 0.5) / total * 100 : 100;
1850
+ return { total, followed, skipped, partial, rate: Math.round(rate) };
1851
+ }
1852
+ rowToPracticeEvent(columns, row) {
1853
+ const obj = {};
1854
+ columns.forEach((col, i) => {
1855
+ obj[col] = row[i];
1856
+ });
1857
+ return {
1858
+ id: obj.id,
1859
+ timestamp: obj.timestamp,
1860
+ habitId: obj.habit_id,
1861
+ habitCategory: obj.habit_category,
1862
+ result: obj.result,
1863
+ engineer: obj.engineer,
1864
+ sessionId: obj.session_id,
1865
+ loreEntryId: obj.lore_entry_id || void 0,
1866
+ taskDescription: obj.task_description || void 0,
1867
+ symbolsTouched: JSON.parse(obj.symbols_touched || "[]"),
1868
+ filesModified: JSON.parse(obj.files_modified || "[]"),
1869
+ relatedIncidentId: obj.related_incident_id || void 0,
1870
+ notes: obj.notes || void 0
1871
+ };
1872
+ }
1873
+ // ─── Structured Logs ─────────────────────────────────────────────
1874
+ inferSymbolType(symbol) {
1875
+ if (symbol.startsWith("#")) return "component";
1876
+ if (symbol.startsWith("^")) return "gate";
1877
+ if (symbol.startsWith("!")) return "signal";
1878
+ if (symbol.startsWith("$")) return "flow";
1879
+ if (symbol.startsWith("~")) return "aspect";
1880
+ return "raw";
1881
+ }
1882
+ insertLog(input) {
1883
+ this.initializeSync();
1884
+ const id = input.id || v4_default();
1885
+ const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1886
+ const symbolType = input.symbolType || this.inferSymbolType(input.symbol);
1887
+ this.db.run(
1888
+ `INSERT INTO logs (
1889
+ id, timestamp, level, symbol, symbol_type, message, data_json,
1890
+ service, session_id, correlation_id, duration_ms, environment
1891
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1892
+ [
1893
+ id,
1894
+ timestamp,
1895
+ input.level,
1896
+ input.symbol,
1897
+ symbolType,
1898
+ input.message,
1899
+ input.data ? JSON.stringify(input.data) : null,
1900
+ input.service,
1901
+ input.sessionId || null,
1902
+ input.correlationId || null,
1903
+ input.durationMs ?? null,
1904
+ input.environment || null
1905
+ ]
1906
+ );
1907
+ this.save();
1908
+ return id;
1909
+ }
1910
+ insertLogBatch(entries) {
1911
+ this.initializeSync();
1912
+ let accepted = 0;
1913
+ const errors = [];
1914
+ for (const input of entries) {
1915
+ try {
1916
+ const id = input.id || v4_default();
1917
+ const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1918
+ const symbolType = input.symbolType || this.inferSymbolType(input.symbol);
1919
+ this.db.run(
1920
+ `INSERT INTO logs (
1921
+ id, timestamp, level, symbol, symbol_type, message, data_json,
1922
+ service, session_id, correlation_id, duration_ms, environment
1923
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1924
+ [
1925
+ id,
1926
+ timestamp,
1927
+ input.level,
1928
+ input.symbol,
1929
+ symbolType,
1930
+ input.message,
1931
+ input.data ? JSON.stringify(input.data) : null,
1932
+ input.service,
1933
+ input.sessionId || null,
1934
+ input.correlationId || null,
1935
+ input.durationMs ?? null,
1936
+ input.environment || null
1937
+ ]
1938
+ );
1939
+ accepted++;
1940
+ } catch (err) {
1941
+ errors.push(err instanceof Error ? err.message : String(err));
1942
+ }
1943
+ }
1944
+ this.save();
1945
+ return { accepted, errors };
1946
+ }
1947
+ queryLogs(options = {}) {
1948
+ this.initializeSync();
1949
+ const { limit = 100, offset = 0 } = options;
1950
+ const conditions = [];
1951
+ const params = [];
1952
+ if (options.level) {
1953
+ conditions.push("level = ?");
1954
+ params.push(options.level);
1955
+ }
1956
+ if (options.symbol) {
1957
+ conditions.push("symbol LIKE ?");
1958
+ params.push(`%${options.symbol}%`);
1959
+ }
1960
+ if (options.service) {
1961
+ conditions.push("service = ?");
1962
+ params.push(options.service);
1963
+ }
1964
+ if (options.sessionId) {
1965
+ conditions.push("session_id = ?");
1966
+ params.push(options.sessionId);
1967
+ }
1968
+ if (options.correlationId) {
1969
+ conditions.push("correlation_id = ?");
1970
+ params.push(options.correlationId);
1971
+ }
1972
+ if (options.search) {
1973
+ conditions.push("message LIKE ?");
1974
+ params.push(`%${options.search}%`);
1975
+ }
1976
+ if (options.since) {
1977
+ conditions.push("timestamp >= ?");
1978
+ params.push(options.since);
1979
+ }
1980
+ if (options.until) {
1981
+ conditions.push("timestamp <= ?");
1982
+ params.push(options.until);
1983
+ }
1984
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1985
+ const result = this.db.exec(
1986
+ `SELECT * FROM logs ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
1987
+ [...params, limit, offset]
1988
+ );
1989
+ if (result.length === 0) return [];
1990
+ return result[0].values.map(
1991
+ (row) => this.rowToLogEntry(result[0].columns, row)
1992
+ );
1993
+ }
1994
+ getLogCount(options = {}) {
1995
+ this.initializeSync();
1996
+ const conditions = [];
1997
+ const params = [];
1998
+ if (options.level) {
1999
+ conditions.push("level = ?");
2000
+ params.push(options.level);
2001
+ }
2002
+ if (options.symbol) {
2003
+ conditions.push("symbol LIKE ?");
2004
+ params.push(`%${options.symbol}%`);
2005
+ }
2006
+ if (options.service) {
2007
+ conditions.push("service = ?");
2008
+ params.push(options.service);
2009
+ }
2010
+ if (options.since) {
2011
+ conditions.push("timestamp >= ?");
2012
+ params.push(options.since);
2013
+ }
2014
+ if (options.until) {
2015
+ conditions.push("timestamp <= ?");
2016
+ params.push(options.until);
2017
+ }
2018
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2019
+ const result = this.db.exec(
2020
+ `SELECT COUNT(*) as count FROM logs ${whereClause}`,
2021
+ params
2022
+ );
2023
+ if (result.length === 0 || result[0].values.length === 0) return 0;
2024
+ return result[0].values[0][0];
2025
+ }
2026
+ pruneLogs(maxCount) {
2027
+ this.initializeSync();
2028
+ if (maxCount <= 0) return 0;
2029
+ const currentCount = this.getLogCount();
2030
+ if (currentCount <= maxCount) return 0;
2031
+ const deleteCount = currentCount - maxCount;
2032
+ this.db.run(
2033
+ `DELETE FROM logs WHERE id IN (
2034
+ SELECT id FROM logs ORDER BY timestamp ASC LIMIT ?
2035
+ )`,
2036
+ [deleteCount]
2037
+ );
2038
+ this.save();
2039
+ return deleteCount;
2040
+ }
2041
+ rowToLogEntry(columns, row) {
2042
+ const obj = {};
2043
+ columns.forEach((col, i) => {
2044
+ obj[col] = row[i];
2045
+ });
2046
+ return {
2047
+ id: obj.id,
2048
+ timestamp: obj.timestamp,
2049
+ level: obj.level,
2050
+ symbol: obj.symbol,
2051
+ symbolType: obj.symbol_type || "raw",
2052
+ message: obj.message,
2053
+ data: obj.data_json ? JSON.parse(obj.data_json) : void 0,
2054
+ service: obj.service,
2055
+ sessionId: obj.session_id || void 0,
2056
+ correlationId: obj.correlation_id || void 0,
2057
+ durationMs: obj.duration_ms || void 0,
2058
+ environment: obj.environment || void 0
2059
+ };
2060
+ }
2061
+ // ─── Service Registry ──────────────────────────────────────────
2062
+ registerService(reg) {
2063
+ this.initializeSync();
2064
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2065
+ this.db.run(
2066
+ `INSERT INTO services (name, version, pid, started_at, last_seen_at, environment, metadata_json)
2067
+ VALUES (?, ?, ?, ?, ?, ?, ?)
2068
+ ON CONFLICT(name) DO UPDATE SET
2069
+ version = excluded.version,
2070
+ pid = excluded.pid,
2071
+ last_seen_at = excluded.last_seen_at,
2072
+ environment = excluded.environment,
2073
+ metadata_json = excluded.metadata_json`,
2074
+ [
2075
+ reg.name,
2076
+ reg.version || null,
2077
+ reg.pid ?? null,
2078
+ now,
2079
+ now,
2080
+ reg.environment || null,
2081
+ reg.metadata ? JSON.stringify(reg.metadata) : null
2082
+ ]
2083
+ );
2084
+ this.save();
2085
+ }
2086
+ updateServiceLastSeen(name) {
2087
+ this.initializeSync();
2088
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2089
+ this.db.run(
2090
+ "UPDATE services SET last_seen_at = ? WHERE name = ?",
2091
+ [now, name]
2092
+ );
2093
+ this.save();
2094
+ }
2095
+ getServices() {
2096
+ this.initializeSync();
2097
+ const result = this.db.exec(
2098
+ "SELECT * FROM services ORDER BY last_seen_at DESC"
2099
+ );
2100
+ if (result.length === 0) return [];
2101
+ return result[0].values.map((row) => {
2102
+ const obj = {};
2103
+ result[0].columns.forEach((col, i) => {
2104
+ obj[col] = row[i];
2105
+ });
2106
+ return {
2107
+ name: obj.name,
2108
+ version: obj.version || void 0,
2109
+ pid: obj.pid || void 0,
2110
+ startedAt: obj.started_at,
2111
+ lastSeenAt: obj.last_seen_at,
2112
+ environment: obj.environment || void 0,
2113
+ metadata: obj.metadata_json ? JSON.parse(obj.metadata_json) : void 0
2114
+ };
2115
+ });
2116
+ }
2117
+ // ─── App State ──────────────────────────────────────────────────
2118
+ upsertAppState(state) {
2119
+ this.initializeSync();
2120
+ this.db.run(
2121
+ `INSERT INTO app_state (service, session_id, timestamp, state_json, active_flows_json, active_gates_json)
2122
+ VALUES (?, ?, ?, ?, ?, ?)
2123
+ ON CONFLICT(service, session_id) DO UPDATE SET
2124
+ timestamp = excluded.timestamp,
2125
+ state_json = excluded.state_json,
2126
+ active_flows_json = excluded.active_flows_json,
2127
+ active_gates_json = excluded.active_gates_json`,
2128
+ [
2129
+ state.service,
2130
+ state.sessionId,
2131
+ state.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2132
+ JSON.stringify(state.state),
2133
+ state.activeFlows ? JSON.stringify(state.activeFlows) : null,
2134
+ state.activeGates ? JSON.stringify(state.activeGates) : null
2135
+ ]
2136
+ );
2137
+ this.save();
2138
+ }
2139
+ getAppState(service, sessionId) {
2140
+ this.initializeSync();
2141
+ let query = "SELECT * FROM app_state WHERE service = ?";
2142
+ const params = [service];
2143
+ if (sessionId) {
2144
+ query += " AND session_id = ?";
2145
+ params.push(sessionId);
2146
+ }
2147
+ query += " ORDER BY timestamp DESC";
2148
+ const result = this.db.exec(query, params);
2149
+ if (result.length === 0) return [];
2150
+ return result[0].values.map((row) => this.rowToAppState(result[0].columns, row));
2151
+ }
2152
+ getAllAppStates() {
2153
+ this.initializeSync();
2154
+ const result = this.db.exec(
2155
+ "SELECT * FROM app_state ORDER BY timestamp DESC"
2156
+ );
2157
+ if (result.length === 0) return [];
2158
+ return result[0].values.map((row) => this.rowToAppState(result[0].columns, row));
2159
+ }
2160
+ rowToAppState(columns, row) {
2161
+ const obj = {};
2162
+ columns.forEach((col, i) => {
2163
+ obj[col] = row[i];
2164
+ });
2165
+ return {
2166
+ service: obj.service,
2167
+ sessionId: obj.session_id,
2168
+ timestamp: obj.timestamp,
2169
+ state: JSON.parse(obj.state_json),
2170
+ activeFlows: obj.active_flows_json ? JSON.parse(obj.active_flows_json) : void 0,
2171
+ activeGates: obj.active_gates_json ? JSON.parse(obj.active_gates_json) : void 0
2172
+ };
2173
+ }
2174
+ // ─── Metrics ───────────────────────────────────────────────────
2175
+ insertMetric(input) {
2176
+ this.initializeSync();
2177
+ const id = v4_default();
2178
+ const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
2179
+ this.db.run(
2180
+ `INSERT INTO metrics (
2181
+ id, timestamp, name, type, value, tags_json, service, environment
2182
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
2183
+ [
2184
+ id,
2185
+ timestamp,
2186
+ input.name,
2187
+ input.type,
2188
+ input.value,
2189
+ JSON.stringify(input.tags || {}),
2190
+ input.service,
2191
+ input.environment || null
2192
+ ]
2193
+ );
2194
+ this.save();
2195
+ return id;
2196
+ }
2197
+ insertMetricBatch(entries) {
2198
+ this.initializeSync();
2199
+ let accepted = 0;
2200
+ const errors = [];
2201
+ for (const input of entries) {
2202
+ try {
2203
+ const id = v4_default();
2204
+ const timestamp = input.timestamp || (/* @__PURE__ */ new Date()).toISOString();
2205
+ this.db.run(
2206
+ `INSERT INTO metrics (
2207
+ id, timestamp, name, type, value, tags_json, service, environment
2208
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
2209
+ [
2210
+ id,
2211
+ timestamp,
2212
+ input.name,
2213
+ input.type,
2214
+ input.value,
2215
+ JSON.stringify(input.tags || {}),
2216
+ input.service,
2217
+ input.environment || null
2218
+ ]
2219
+ );
2220
+ accepted++;
2221
+ } catch (err) {
2222
+ errors.push(err instanceof Error ? err.message : String(err));
2223
+ }
2224
+ }
2225
+ this.save();
2226
+ return { accepted, errors };
2227
+ }
2228
+ queryMetrics(options = {}) {
2229
+ this.initializeSync();
2230
+ const { limit = 100, offset = 0 } = options;
2231
+ const conditions = [];
2232
+ const params = [];
2233
+ if (options.name) {
2234
+ conditions.push("name = ?");
2235
+ params.push(options.name);
2236
+ }
2237
+ if (options.type) {
2238
+ conditions.push("type = ?");
2239
+ params.push(options.type);
2240
+ }
2241
+ if (options.service) {
2242
+ conditions.push("service = ?");
2243
+ params.push(options.service);
2244
+ }
2245
+ if (options.tag) {
2246
+ const eqIdx = options.tag.indexOf("=");
2247
+ if (eqIdx > 0) {
2248
+ const tagKey = options.tag.substring(0, eqIdx);
2249
+ const tagValue = options.tag.substring(eqIdx + 1);
2250
+ conditions.push("tags_json LIKE ?");
2251
+ params.push(`%"${tagKey}":"${tagValue}"%`);
2252
+ }
2253
+ }
2254
+ if (options.since) {
2255
+ conditions.push("timestamp >= ?");
2256
+ params.push(options.since);
2257
+ }
2258
+ if (options.until) {
2259
+ conditions.push("timestamp <= ?");
2260
+ params.push(options.until);
2261
+ }
2262
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2263
+ const result = this.db.exec(
2264
+ `SELECT * FROM metrics ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
2265
+ [...params, limit, offset]
2266
+ );
2267
+ if (result.length === 0) return [];
2268
+ return result[0].values.map(
2269
+ (row) => this.rowToMetricEntry(result[0].columns, row)
2270
+ );
2271
+ }
2272
+ getMetricCount(options = {}) {
2273
+ this.initializeSync();
2274
+ const conditions = [];
2275
+ const params = [];
2276
+ if (options.name) {
2277
+ conditions.push("name = ?");
2278
+ params.push(options.name);
2279
+ }
2280
+ if (options.type) {
2281
+ conditions.push("type = ?");
2282
+ params.push(options.type);
2283
+ }
2284
+ if (options.service) {
2285
+ conditions.push("service = ?");
2286
+ params.push(options.service);
2287
+ }
2288
+ if (options.tag) {
2289
+ const eqIdx = options.tag.indexOf("=");
2290
+ if (eqIdx > 0) {
2291
+ const tagKey = options.tag.substring(0, eqIdx);
2292
+ const tagValue = options.tag.substring(eqIdx + 1);
2293
+ conditions.push("tags_json LIKE ?");
2294
+ params.push(`%"${tagKey}":"${tagValue}"%`);
2295
+ }
2296
+ }
2297
+ if (options.since) {
2298
+ conditions.push("timestamp >= ?");
2299
+ params.push(options.since);
2300
+ }
2301
+ if (options.until) {
2302
+ conditions.push("timestamp <= ?");
2303
+ params.push(options.until);
2304
+ }
2305
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2306
+ const result = this.db.exec(
2307
+ `SELECT COUNT(*) as count FROM metrics ${whereClause}`,
2308
+ params
2309
+ );
2310
+ if (result.length === 0 || result[0].values.length === 0) return 0;
2311
+ return result[0].values[0][0];
2312
+ }
2313
+ aggregateMetric(name, options) {
2314
+ this.initializeSync();
2315
+ const conditions = ["name = ?"];
2316
+ const params = [name];
2317
+ if (options?.service) {
2318
+ conditions.push("service = ?");
2319
+ params.push(options.service);
2320
+ }
2321
+ if (options?.since) {
2322
+ conditions.push("timestamp >= ?");
2323
+ params.push(options.since);
2324
+ }
2325
+ if (options?.until) {
2326
+ conditions.push("timestamp <= ?");
2327
+ params.push(options.until);
2328
+ }
2329
+ const whereClause = `WHERE ${conditions.join(" AND ")}`;
2330
+ const result = this.db.exec(
2331
+ `SELECT COUNT(*) as count, SUM(value) as sum, MIN(value) as min, MAX(value) as max, AVG(value) as avg
2332
+ FROM metrics ${whereClause}`,
2333
+ params
2334
+ );
2335
+ if (result.length === 0 || result[0].values.length === 0) {
2336
+ return { name, count: 0, sum: 0, min: 0, max: 0, avg: 0 };
2337
+ }
2338
+ const row = result[0].values[0];
2339
+ return {
2340
+ name,
2341
+ count: row[0] || 0,
2342
+ sum: row[1] || 0,
2343
+ min: row[2] || 0,
2344
+ max: row[3] || 0,
2345
+ avg: row[4] || 0
2346
+ };
2347
+ }
2348
+ pruneMetrics(maxCount) {
2349
+ this.initializeSync();
2350
+ if (maxCount <= 0) return 0;
2351
+ const currentCount = this.getMetricCount();
2352
+ if (currentCount <= maxCount) return 0;
2353
+ const deleteCount = currentCount - maxCount;
2354
+ this.db.run(
2355
+ `DELETE FROM metrics WHERE id IN (
2356
+ SELECT id FROM metrics ORDER BY timestamp ASC LIMIT ?
2357
+ )`,
2358
+ [deleteCount]
2359
+ );
2360
+ this.save();
2361
+ return deleteCount;
2362
+ }
2363
+ rowToMetricEntry(columns, row) {
2364
+ const obj = {};
2365
+ columns.forEach((col, i) => {
2366
+ obj[col] = row[i];
2367
+ });
2368
+ return {
2369
+ id: obj.id,
2370
+ timestamp: obj.timestamp,
2371
+ name: obj.name,
2372
+ type: obj.type,
2373
+ value: obj.value,
2374
+ tags: obj.tags_json ? JSON.parse(obj.tags_json) : {},
2375
+ service: obj.service,
2376
+ environment: obj.environment || void 0
2377
+ };
2378
+ }
2379
+ // ─── Traces ───────────────────────────────────────────────────
2380
+ insertSpan(input) {
2381
+ this.initializeSync();
2382
+ const spanId = input.spanId || v4_default();
2383
+ const startTime = input.startTime || (/* @__PURE__ */ new Date()).toISOString();
2384
+ this.db.run(
2385
+ `INSERT INTO traces (
2386
+ trace_id, span_id, parent_span_id, service, symbol, operation,
2387
+ start_time, end_time, duration_ms, status, tags_json, log_ids_json
2388
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2389
+ [
2390
+ input.traceId,
2391
+ spanId,
2392
+ input.parentSpanId || null,
2393
+ input.service,
2394
+ input.symbol,
2395
+ input.operation,
2396
+ startTime,
2397
+ input.endTime || null,
2398
+ input.durationMs ?? null,
2399
+ input.status || "ok",
2400
+ JSON.stringify(input.tags || {}),
2401
+ JSON.stringify(input.logIds || [])
2402
+ ]
2403
+ );
2404
+ this.save();
2405
+ return spanId;
2406
+ }
2407
+ getTrace(traceId) {
2408
+ this.initializeSync();
2409
+ const result = this.db.exec(
2410
+ "SELECT * FROM traces WHERE trace_id = ? ORDER BY start_time ASC",
2411
+ [traceId]
2412
+ );
2413
+ if (result.length === 0 || result[0].values.length === 0) return null;
2414
+ const spans = result[0].values.map(
2415
+ (row) => this.rowToTraceSpan(result[0].columns, row)
2416
+ );
2417
+ const services = [...new Set(spans.map((s) => s.service))];
2418
+ const startTimes = spans.map((s) => s.startTime);
2419
+ const endTimes = spans.filter((s) => s.endTime).map((s) => s.endTime);
2420
+ const startTime = startTimes.sort()[0];
2421
+ const endTime = endTimes.length > 0 ? endTimes.sort().reverse()[0] : startTime;
2422
+ const startMs = new Date(startTime).getTime();
2423
+ const endMs = new Date(endTime).getTime();
2424
+ const totalDurationMs = endMs - startMs;
2425
+ return {
2426
+ traceId,
2427
+ spans,
2428
+ services,
2429
+ totalDurationMs: totalDurationMs > 0 ? totalDurationMs : 0,
2430
+ startTime,
2431
+ endTime
2432
+ };
2433
+ }
2434
+ queryTraces(options = {}) {
2435
+ this.initializeSync();
2436
+ const conditions = [];
2437
+ const params = [];
2438
+ if (options.service) {
2439
+ conditions.push("service = ?");
2440
+ params.push(options.service);
2441
+ }
2442
+ if (options.symbol) {
2443
+ conditions.push("symbol = ?");
2444
+ params.push(options.symbol);
2445
+ }
2446
+ if (options.since) {
2447
+ conditions.push("start_time >= ?");
2448
+ params.push(options.since);
2449
+ }
2450
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2451
+ const traceLimit = Math.min(options.limit || 20, 20);
2452
+ const result = this.db.exec(
2453
+ `SELECT DISTINCT trace_id FROM traces ${whereClause} ORDER BY start_time DESC LIMIT ?`,
2454
+ [...params, traceLimit]
2455
+ );
2456
+ if (result.length === 0) return [];
2457
+ const traces = [];
2458
+ for (const row of result[0].values) {
2459
+ const traceId = row[0];
2460
+ const trace = this.getTrace(traceId);
2461
+ if (trace) {
2462
+ traces.push(trace);
2463
+ }
2464
+ }
2465
+ return traces;
2466
+ }
2467
+ rowToTraceSpan(columns, row) {
2468
+ const obj = {};
2469
+ columns.forEach((col, i) => {
2470
+ obj[col] = row[i];
2471
+ });
2472
+ return {
2473
+ traceId: obj.trace_id,
2474
+ spanId: obj.span_id,
2475
+ parentSpanId: obj.parent_span_id || void 0,
2476
+ service: obj.service,
2477
+ symbol: obj.symbol,
2478
+ operation: obj.operation,
2479
+ startTime: obj.start_time,
2480
+ endTime: obj.end_time || void 0,
2481
+ durationMs: obj.duration_ms || void 0,
2482
+ status: obj.status || "ok",
2483
+ tags: obj.tags_json ? JSON.parse(obj.tags_json) : {},
2484
+ logs: obj.log_ids_json ? JSON.parse(obj.log_ids_json) : []
2485
+ };
2486
+ }
2487
+ close() {
2488
+ if (this.db) {
2489
+ this.save();
2490
+ this.db.close();
2491
+ this.db = null;
2492
+ }
2493
+ }
2494
+ };
2495
+ var DEFAULT_AUTH_CONFIG = {
2496
+ enabled: false,
2497
+ tokens: []
2498
+ };
2499
+ var DEFAULT_RATE_LIMIT_CONFIG = {
2500
+ enabled: false,
2501
+ global: {
2502
+ maxRequestsPerMinute: 600,
2503
+ maxEntriesPerBatch: 500,
2504
+ samplingRate: 1
2505
+ },
2506
+ perService: {}
2507
+ };
2508
+ var DEFAULT_SERVER_CONFIG = {
2509
+ port: 3838,
2510
+ maxLogs: 1e4,
2511
+ maxBatchSize: 500,
2512
+ wsMaxSubscribers: 256,
2513
+ pruneIntervalInserts: 100,
2514
+ logRetentionDays: 0,
2515
+ auth: DEFAULT_AUTH_CONFIG,
2516
+ rateLimit: DEFAULT_RATE_LIMIT_CONFIG
2517
+ };
2518
+ var CONFIG_FILES = [".sentinel.yaml", ".sentinel.yml"];
2519
+ function loadConfig(projectDir) {
2520
+ for (const filename of CONFIG_FILES) {
2521
+ const filePath = path2.join(projectDir, filename);
2522
+ if (fs2.existsSync(filePath)) {
2523
+ const content = fs2.readFileSync(filePath, "utf-8");
2524
+ return parseSimpleYaml(content);
2525
+ }
2526
+ }
2527
+ return null;
2528
+ }
2529
+ function writeConfig(projectDir, config) {
2530
+ const filePath = path2.join(projectDir, ".sentinel.yaml");
2531
+ const content = serializeSimpleYaml(config);
2532
+ fs2.writeFileSync(filePath, content, "utf-8");
2533
+ }
2534
+ function parseSimpleYaml(content) {
2535
+ const config = { version: "1.0", project: "" };
2536
+ const lines = content.split("\n");
2537
+ let currentSection = null;
2538
+ let currentSubSection = null;
2539
+ for (const line of lines) {
2540
+ const trimmed = line.trimEnd();
2541
+ if (!trimmed || trimmed.startsWith("#")) continue;
2542
+ const topMatch = trimmed.match(/^(\w+):\s*(.+)$/);
2543
+ if (topMatch) {
2544
+ const [, key, value] = topMatch;
2545
+ if (key === "version") config.version = value.replace(/['"]/g, "");
2546
+ else if (key === "project") config.project = value.replace(/['"]/g, "");
2547
+ else if (key === "environment") config.environment = value.replace(/['"]/g, "");
2548
+ currentSection = null;
2549
+ currentSubSection = null;
2550
+ continue;
2551
+ }
2552
+ const sectionMatch = trimmed.match(/^(\w+):$/);
2553
+ if (sectionMatch) {
2554
+ currentSection = sectionMatch[1];
2555
+ currentSubSection = null;
2556
+ if (currentSection === "symbols" && !config.symbols) {
2557
+ config.symbols = {};
2558
+ }
2559
+ if (currentSection === "routes" && !config.routes) {
2560
+ config.routes = {};
2561
+ }
2562
+ if (currentSection === "scrub" && !config.scrub) {
2563
+ config.scrub = {};
2564
+ }
2565
+ if (currentSection === "server" && !config.server) {
2566
+ config.server = {};
2567
+ }
2568
+ continue;
2569
+ }
2570
+ const subMatch = trimmed.match(/^\s{2}(\w+):$/);
2571
+ if (subMatch && currentSection) {
2572
+ currentSubSection = subMatch[1];
2573
+ if (currentSection === "symbols" && config.symbols) {
2574
+ config.symbols[currentSubSection] = [];
2575
+ }
2576
+ if (currentSection === "scrub" && config.scrub) {
2577
+ config.scrub[currentSubSection] = [];
2578
+ }
2579
+ continue;
2580
+ }
2581
+ const listMatch = trimmed.match(/^\s+-\s+(.+)$/);
2582
+ if (listMatch && currentSection && currentSubSection) {
2583
+ const value = listMatch[1].replace(/['"]/g, "");
2584
+ if (currentSection === "symbols" && config.symbols) {
2585
+ const arr = config.symbols[currentSubSection];
2586
+ if (Array.isArray(arr)) arr.push(value);
2587
+ }
2588
+ if (currentSection === "scrub" && config.scrub) {
2589
+ const arr = config.scrub[currentSubSection];
2590
+ if (Array.isArray(arr)) arr.push(value);
2591
+ }
2592
+ continue;
2593
+ }
2594
+ const routeMatch = trimmed.match(/^\s+(['"]?\/[^'"]+['"]?):\s+['"]?([^'"]+)['"]?$/);
2595
+ if (routeMatch && currentSection === "routes" && config.routes) {
2596
+ const route = routeMatch[1].replace(/['"]/g, "");
2597
+ config.routes[route] = routeMatch[2];
2598
+ continue;
2599
+ }
2600
+ const serverKvMatch = trimmed.match(/^\s+(\w+):\s+(\d+)$/);
2601
+ if (serverKvMatch && currentSection === "server" && config.server) {
2602
+ const key = serverKvMatch[1];
2603
+ const value = parseInt(serverKvMatch[2], 10);
2604
+ if (key in { port: 1, maxLogs: 1, maxBatchSize: 1, wsMaxSubscribers: 1, pruneIntervalInserts: 1, logRetentionDays: 1 }) {
2605
+ config.server[key] = value;
2606
+ }
2607
+ continue;
2608
+ }
2609
+ }
2610
+ return config;
2611
+ }
2612
+ function serializeSimpleYaml(config) {
2613
+ const lines = [];
2614
+ lines.push(`# Sentinel Configuration`);
2615
+ lines.push(`# Auto-generated \u2014 edit freely`);
2616
+ lines.push("");
2617
+ lines.push(`version: "${config.version}"`);
2618
+ lines.push(`project: "${config.project}"`);
2619
+ if (config.environment) {
2620
+ lines.push(`environment: "${config.environment}"`);
2621
+ }
2622
+ if (config.symbols) {
2623
+ lines.push("");
2624
+ lines.push("symbols:");
2625
+ for (const [key, values] of Object.entries(config.symbols)) {
2626
+ if (values && values.length > 0) {
2627
+ lines.push(` ${key}:`);
2628
+ for (const v of values) {
2629
+ lines.push(` - ${v}`);
2630
+ }
2631
+ }
2632
+ }
2633
+ }
2634
+ if (config.routes && Object.keys(config.routes).length > 0) {
2635
+ lines.push("");
2636
+ lines.push("routes:");
2637
+ for (const [route, symbol] of Object.entries(config.routes)) {
2638
+ lines.push(` "${route}": ${symbol}`);
2639
+ }
2640
+ }
2641
+ if (config.scrub) {
2642
+ lines.push("");
2643
+ lines.push("scrub:");
2644
+ if (config.scrub.headers?.length) {
2645
+ lines.push(" headers:");
2646
+ for (const h of config.scrub.headers) {
2647
+ lines.push(` - ${h}`);
2648
+ }
2649
+ }
2650
+ if (config.scrub.fields?.length) {
2651
+ lines.push(" fields:");
2652
+ for (const f of config.scrub.fields) {
2653
+ lines.push(` - ${f}`);
2654
+ }
2655
+ }
2656
+ }
2657
+ if (config.server && Object.keys(config.server).length > 0) {
2658
+ lines.push("");
2659
+ lines.push("server:");
2660
+ for (const [key, value] of Object.entries(config.server)) {
2661
+ if (value !== void 0) {
2662
+ lines.push(` ${key}: ${value}`);
2663
+ }
2664
+ }
2665
+ }
2666
+ lines.push("");
2667
+ return lines.join("\n");
2668
+ }
2669
+ function loadServerConfig(projectDir) {
2670
+ const config = { ...DEFAULT_SERVER_CONFIG };
2671
+ const yamlConfig = projectDir ? loadConfig(projectDir) : null;
2672
+ const globalDir = path2.join(process.env.HOME || "~", ".paradigm");
2673
+ const globalConfig = loadConfig(globalDir);
2674
+ for (const src of [globalConfig, yamlConfig]) {
2675
+ if (src?.server) {
2676
+ if (src.server.port !== void 0) config.port = src.server.port;
2677
+ if (src.server.maxLogs !== void 0) config.maxLogs = src.server.maxLogs;
2678
+ if (src.server.maxBatchSize !== void 0) config.maxBatchSize = src.server.maxBatchSize;
2679
+ if (src.server.wsMaxSubscribers !== void 0) config.wsMaxSubscribers = src.server.wsMaxSubscribers;
2680
+ if (src.server.pruneIntervalInserts !== void 0) config.pruneIntervalInserts = src.server.pruneIntervalInserts;
2681
+ if (src.server.logRetentionDays !== void 0) config.logRetentionDays = src.server.logRetentionDays;
2682
+ }
2683
+ }
2684
+ if (process.env.SENTINEL_PORT) config.port = parseInt(process.env.SENTINEL_PORT, 10);
2685
+ if (process.env.SENTINEL_MAX_LOGS) config.maxLogs = parseInt(process.env.SENTINEL_MAX_LOGS, 10);
2686
+ return config;
2687
+ }
2688
+
2689
+ // ../paradigm-mcp/node_modules/.pnpm/@a-company+sentinel@3.5.0/node_modules/@a-company/sentinel/dist/index.js
2690
+ import * as path3 from "path";
2691
+ import * as fs3 from "fs";
2692
+ import { fileURLToPath } from "url";
2693
+ import * as fs22 from "fs";
2694
+ import * as path22 from "path";
2695
+ import * as fs32 from "fs";
2696
+ import * as path32 from "path";
2697
+ import * as fs4 from "fs";
2698
+ var DEFAULT_CONFIG = {
2699
+ minScore: 30,
2700
+ maxResults: 5,
2701
+ boostConfidence: true
2702
+ };
2703
+ var PatternMatcher = class {
2704
+ constructor(storage) {
2705
+ this.storage = storage;
2706
+ }
2707
+ /**
2708
+ * Match an incident against all patterns and return ranked results
2709
+ */
2710
+ match(incident, config = {}) {
2711
+ const { minScore, maxResults, boostConfidence } = {
2712
+ ...DEFAULT_CONFIG,
2713
+ ...config
2714
+ };
2715
+ const patterns = this.storage.getAllPatterns({ includePrivate: true });
2716
+ const matches = [];
2717
+ for (const pattern of patterns) {
2718
+ if (!this.matchEnvironment(pattern, incident)) {
2719
+ continue;
2720
+ }
2721
+ const { score, matchedCriteria } = this.scoreMatch(pattern, incident);
2722
+ if (score >= minScore) {
2723
+ let confidence = score;
2724
+ if (boostConfidence) {
2725
+ const confidenceFactor = pattern.confidence.score / 100;
2726
+ confidence = score * (0.5 + 0.5 * confidenceFactor);
2727
+ }
2728
+ matches.push({
2729
+ pattern,
2730
+ score,
2731
+ matchedCriteria,
2732
+ confidence: Math.round(confidence)
2733
+ });
2734
+ this.storage.updatePatternConfidence(pattern.id, "matched");
2735
+ }
2736
+ }
2737
+ return matches.sort((a, b) => b.confidence - a.confidence).slice(0, maxResults);
2738
+ }
2739
+ /**
2740
+ * Test a pattern against historical incidents
2741
+ */
2742
+ testPattern(pattern, limit = 100) {
2743
+ const incidents = this.storage.getRecentIncidents({ limit });
2744
+ const wouldMatch = [];
2745
+ let totalScore = 0;
2746
+ for (const incident of incidents) {
2747
+ if (!this.matchEnvironment(pattern, incident)) {
2748
+ continue;
2749
+ }
2750
+ const { score } = this.scoreMatch(pattern, incident);
2751
+ if (score >= 30) {
2752
+ wouldMatch.push(incident);
2753
+ totalScore += score;
2754
+ }
2755
+ }
2756
+ return {
2757
+ wouldMatch,
2758
+ matchCount: wouldMatch.length,
2759
+ avgScore: wouldMatch.length > 0 ? Math.round(totalScore / wouldMatch.length) : 0
2760
+ };
2761
+ }
2762
+ /**
2763
+ * Score how well a pattern matches an incident
2764
+ */
2765
+ scoreMatch(pattern, incident) {
2766
+ let score = 0;
2767
+ const matchedCriteria = {
2768
+ symbols: [],
2769
+ errorKeywords: [],
2770
+ missingSignals: []
2771
+ };
2772
+ const symbolScore = this.matchSymbols(
2773
+ pattern.pattern.symbols,
2774
+ incident.symbols,
2775
+ matchedCriteria.symbols
2776
+ );
2777
+ score += Math.min(symbolScore, 50);
2778
+ const errorScore = this.matchErrorText(
2779
+ pattern,
2780
+ incident,
2781
+ matchedCriteria.errorKeywords
2782
+ );
2783
+ score += Math.min(errorScore, 25);
2784
+ const signalScore = this.matchMissingSignals(
2785
+ pattern,
2786
+ incident,
2787
+ matchedCriteria.missingSignals
2788
+ );
2789
+ score += Math.min(signalScore, 25);
2790
+ score = Math.min(score, 100);
2791
+ return { score, matchedCriteria };
2792
+ }
2793
+ /**
2794
+ * Match symbols between pattern and incident
2795
+ */
2796
+ matchSymbols(patternSymbols, incidentSymbols, matched) {
2797
+ let score = 0;
2798
+ const symbolTypes = [
2799
+ "feature",
2800
+ "component",
2801
+ "flow",
2802
+ "gate",
2803
+ "signal",
2804
+ "state",
2805
+ "integration"
2806
+ ];
2807
+ for (const type of symbolTypes) {
2808
+ const patternValue = patternSymbols[type];
2809
+ const incidentValue = incidentSymbols[type];
2810
+ if (!patternValue || !incidentValue) {
2811
+ continue;
2812
+ }
2813
+ if (typeof patternValue === "string") {
2814
+ if (this.matchSingleSymbol(patternValue, incidentValue)) {
2815
+ score += patternValue.includes("*") ? 5 : 10;
2816
+ matched.push(type);
2817
+ }
2818
+ } else if (Array.isArray(patternValue)) {
2819
+ for (const pv of patternValue) {
2820
+ if (this.matchSingleSymbol(pv, incidentValue)) {
2821
+ score += 7;
2822
+ matched.push(type);
2823
+ break;
2824
+ }
2825
+ }
2826
+ }
2827
+ }
2828
+ return score;
2829
+ }
2830
+ /**
2831
+ * Match a single symbol value (supports wildcards)
2832
+ */
2833
+ matchSingleSymbol(pattern, value) {
2834
+ if (pattern === "*") {
2835
+ return true;
2836
+ }
2837
+ if (pattern.endsWith("*")) {
2838
+ const prefix = pattern.slice(0, -1);
2839
+ return value.startsWith(prefix);
2840
+ }
2841
+ if (pattern.startsWith("*")) {
2842
+ const suffix = pattern.slice(1);
2843
+ return value.endsWith(suffix);
2844
+ }
2845
+ if (pattern.includes("*")) {
2846
+ const regex = new RegExp(
2847
+ "^" + pattern.replace(/\*/g, ".*") + "$"
2848
+ );
2849
+ return regex.test(value);
2850
+ }
2851
+ return pattern === value;
2852
+ }
2853
+ /**
2854
+ * Match error text keywords and regex
2855
+ */
2856
+ matchErrorText(pattern, incident, matched) {
2857
+ let score = 0;
2858
+ const errorMessage = incident.error.message.toLowerCase();
2859
+ const errorType = incident.error.type?.toLowerCase();
2860
+ if (pattern.pattern.errorContains) {
2861
+ for (const keyword of pattern.pattern.errorContains) {
2862
+ if (errorMessage.includes(keyword.toLowerCase())) {
2863
+ score += 5;
2864
+ matched.push(keyword);
2865
+ }
2866
+ }
2867
+ }
2868
+ if (pattern.pattern.errorMatches) {
2869
+ try {
2870
+ const regex = new RegExp(pattern.pattern.errorMatches, "i");
2871
+ if (regex.test(incident.error.message)) {
2872
+ score += 10;
2873
+ matched.push(`regex:${pattern.pattern.errorMatches}`);
2874
+ }
2875
+ } catch {
2876
+ }
2877
+ }
2878
+ if (pattern.pattern.errorType && errorType) {
2879
+ for (const type of pattern.pattern.errorType) {
2880
+ if (errorType.includes(type.toLowerCase())) {
2881
+ score += 5;
2882
+ matched.push(`type:${type}`);
2883
+ }
2884
+ }
2885
+ }
2886
+ return score;
2887
+ }
2888
+ /**
2889
+ * Match missing signals from flow position
2890
+ */
2891
+ matchMissingSignals(pattern, incident, matched) {
2892
+ if (!pattern.pattern.missingSignals || !incident.flowPosition?.missing) {
2893
+ return 0;
2894
+ }
2895
+ let score = 0;
2896
+ for (const expectedSignal of pattern.pattern.missingSignals) {
2897
+ for (const missingSignal of incident.flowPosition.missing) {
2898
+ if (this.matchSingleSymbol(expectedSignal, missingSignal)) {
2899
+ score += 12;
2900
+ matched.push(missingSignal);
2901
+ break;
2902
+ }
2903
+ }
2904
+ }
2905
+ return score;
2906
+ }
2907
+ /**
2908
+ * Check if pattern's environment filter matches incident
2909
+ */
2910
+ matchEnvironment(pattern, incident) {
2911
+ if (!pattern.pattern.environment || pattern.pattern.environment.length === 0) {
2912
+ return true;
2913
+ }
2914
+ return pattern.pattern.environment.includes(incident.environment);
2915
+ }
2916
+ };
2917
+ var __filename = fileURLToPath(import.meta.url);
2918
+ var __dirname = path3.dirname(__filename);
2919
+ function loadUniversalPatterns() {
2920
+ const filePath = path3.join(__dirname, "universal-patterns.json");
2921
+ const content = fs3.readFileSync(filePath, "utf-8");
2922
+ return JSON.parse(content);
2923
+ }
2924
+ function loadParadigmPatterns() {
2925
+ const filePath = path3.join(__dirname, "paradigm-patterns.json");
2926
+ const content = fs3.readFileSync(filePath, "utf-8");
2927
+ return JSON.parse(content);
2928
+ }
2929
+ function loadAllSeedPatterns() {
2930
+ const universal = loadUniversalPatterns();
2931
+ const paradigm = loadParadigmPatterns();
2932
+ return {
2933
+ version: "1.0.0",
2934
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
2935
+ patterns: [...universal.patterns, ...paradigm.patterns]
2936
+ };
2937
+ }
2938
+ function ensurePrefix(id, prefix) {
2939
+ return id.startsWith(prefix) ? id : `${prefix}${id}`;
2940
+ }
2941
+ var FlowTracker = class {
2942
+ flowId;
2943
+ sentinel;
2944
+ actual = [];
2945
+ expected = [];
2946
+ completed = false;
2947
+ constructor(flowId, sentinel) {
2948
+ this.flowId = ensurePrefix(flowId, "$");
2949
+ this.sentinel = sentinel;
2950
+ }
2951
+ /** Declare which signals/gates are expected in this flow */
2952
+ expect(...symbols) {
2953
+ this.expected.push(...symbols);
2954
+ return this;
2955
+ }
2956
+ /** Record a generic step in the flow */
2957
+ step(symbol) {
2958
+ this.actual.push(symbol);
2959
+ return this;
2960
+ }
2961
+ /** Record a gate check result */
2962
+ gate(id, passed) {
2963
+ const gateId = ensurePrefix(id, "^");
2964
+ this.actual.push(gateId);
2965
+ if (!passed) {
2966
+ this.fail(new Error(`Gate ${gateId} failed`));
2967
+ }
2968
+ return this;
2969
+ }
2970
+ /** Record a signal emission */
2971
+ signal(id, _data) {
2972
+ this.actual.push(ensurePrefix(id, "!"));
2973
+ return this;
2974
+ }
2975
+ /** Mark the flow as successfully completed */
2976
+ complete() {
2977
+ this.completed = true;
2978
+ }
2979
+ /** Capture an error with full flow position context */
2980
+ fail(error) {
2981
+ if (this.completed) return;
2982
+ this.completed = true;
2983
+ const missing = this.expected.filter((s) => !this.actual.includes(s));
2984
+ const failedAt = this.actual.length > 0 ? this.actual[this.actual.length - 1] : void 0;
2985
+ const flowPosition = {
2986
+ flowId: this.flowId,
2987
+ expected: this.expected,
2988
+ actual: this.actual,
2989
+ missing,
2990
+ failedAt
2991
+ };
2992
+ this.sentinel.capture(error, { flow: this.flowId }, flowPosition);
2993
+ }
2994
+ };
2995
+ var Sentinel = class {
2996
+ storage;
2997
+ matcher;
2998
+ config;
2999
+ ready = false;
3000
+ readyPromise = null;
3001
+ seeded = false;
3002
+ constructor(config) {
3003
+ this.config = config;
3004
+ this.storage = new SentinelStorage(config.dbPath);
3005
+ this.matcher = new PatternMatcher(this.storage);
3006
+ }
3007
+ /** Explicitly initialize storage. Optional — auto-called on first capture. */
3008
+ async init() {
3009
+ if (this.ready) return;
3010
+ if (this.readyPromise) return this.readyPromise;
3011
+ this.readyPromise = this.doInit();
3012
+ return this.readyPromise;
3013
+ }
3014
+ async doInit() {
3015
+ await this.storage.ensureReady();
3016
+ if (!this.seeded) {
3017
+ try {
3018
+ const { patterns } = loadAllSeedPatterns();
3019
+ for (const pattern of patterns) {
3020
+ try {
3021
+ this.storage.addPattern(pattern);
3022
+ } catch {
3023
+ }
3024
+ }
3025
+ } catch {
3026
+ }
3027
+ this.seeded = true;
3028
+ }
3029
+ this.ready = true;
3030
+ }
3031
+ ensureReady() {
3032
+ if (!this.ready) {
3033
+ if (!this.readyPromise) {
3034
+ this.readyPromise = this.doInit();
3035
+ }
3036
+ }
3037
+ }
3038
+ // ── Symbol Context ──────────────────────────────────────────────
3039
+ /**
3040
+ * Create a component context for scoped error capture.
3041
+ *
3042
+ * @param id - Component symbol (e.g. '#checkout' or 'checkout')
3043
+ * @returns ComponentContext with capture() and wrap() methods
3044
+ */
3045
+ component(id) {
3046
+ const componentId = ensurePrefix(id, "#");
3047
+ const self = this;
3048
+ return {
3049
+ id: componentId,
3050
+ capture(error, extra) {
3051
+ return self.capture(error, { component: componentId, ...extra });
3052
+ },
3053
+ wrap(fn) {
3054
+ const wrapped = ((...args) => {
3055
+ try {
3056
+ const result = fn(...args);
3057
+ if (result && typeof result.catch === "function") {
3058
+ return result.catch((err) => {
3059
+ self.capture(err, { component: componentId });
3060
+ throw err;
3061
+ });
3062
+ }
3063
+ return result;
3064
+ } catch (err) {
3065
+ if (err instanceof Error) {
3066
+ self.capture(err, { component: componentId });
3067
+ }
3068
+ throw err;
3069
+ }
3070
+ });
3071
+ return wrapped;
3072
+ }
3073
+ };
3074
+ }
3075
+ /**
3076
+ * Record a gate check result.
3077
+ * If the gate fails, auto-captures an incident.
3078
+ *
3079
+ * @param id - Gate symbol (e.g. '^authenticated' or 'authenticated')
3080
+ * @param passed - Whether the gate passed
3081
+ */
3082
+ gate(id, passed) {
3083
+ if (!passed) {
3084
+ const gateId = ensurePrefix(id, "^");
3085
+ this.capture(new Error(`Gate ${gateId} failed`), { gate: gateId });
3086
+ }
3087
+ }
3088
+ /**
3089
+ * Record a signal emission. Primarily for flow tracking context.
3090
+ *
3091
+ * @param id - Signal symbol (e.g. '!payment-authorized' or 'payment-authorized')
3092
+ */
3093
+ signal(id, _data) {
3094
+ void ensurePrefix(id, "!");
3095
+ }
3096
+ // ── Flow Tracking ───────────────────────────────────────────────
3097
+ /**
3098
+ * Create a flow tracker for monitoring multi-step operations.
3099
+ *
3100
+ * @param id - Flow symbol (e.g. '$checkout-flow' or 'checkout-flow')
3101
+ * @returns FlowTracker instance
3102
+ */
3103
+ flow(id) {
3104
+ return new FlowTracker(id, this);
3105
+ }
3106
+ // ── Error Capture ───────────────────────────────────────────────
3107
+ /**
3108
+ * Capture an error with symbolic context.
3109
+ *
3110
+ * @param error - The error to capture
3111
+ * @param context - Symbolic context (component, gate, flow, signal)
3112
+ * @param flowPosition - Optional flow position data
3113
+ * @returns Incident ID (e.g. 'INC-001')
3114
+ */
3115
+ capture(error, context, flowPosition) {
3116
+ this.ensureReady();
3117
+ const input = {
3118
+ error: {
3119
+ message: error.message,
3120
+ stack: error.stack,
3121
+ type: error.constructor.name !== "Error" ? error.constructor.name : void 0
3122
+ },
3123
+ symbols: context || {},
3124
+ environment: this.config.environment || "development",
3125
+ service: this.config.service,
3126
+ version: this.config.version,
3127
+ flowPosition
3128
+ };
3129
+ const incidentId = this.storage.recordIncident(input);
3130
+ const incident = this.storage.getIncident(incidentId);
3131
+ if (incident && this.config.onCapture) {
3132
+ this.config.onCapture(incident);
3133
+ }
3134
+ return incidentId;
3135
+ }
3136
+ /**
3137
+ * Get pattern matches for a captured incident.
3138
+ *
3139
+ * @param incidentId - The incident ID to match
3140
+ * @returns Array of pattern matches sorted by confidence
3141
+ */
3142
+ match(incidentId) {
3143
+ const incident = this.storage.getIncident(incidentId);
3144
+ if (!incident) return [];
3145
+ return this.matcher.match(incident);
3146
+ }
3147
+ // ── Framework Integration ───────────────────────────────────────
3148
+ /**
3149
+ * Create Express error-handling middleware.
3150
+ *
3151
+ * Usage:
3152
+ * app.use(sentinel.express());
3153
+ */
3154
+ express() {
3155
+ const self = this;
3156
+ return (err, req, res, next) => {
3157
+ const context = {};
3158
+ const routeParts = (req.path || req.url || "").split("/").filter(Boolean);
3159
+ if (routeParts.length >= 2) {
3160
+ context.component = `#${routeParts[1]}`;
3161
+ }
3162
+ const incidentId = self.capture(err, context);
3163
+ if (res.setHeader) {
3164
+ res.setHeader("X-Sentinel-Incident", incidentId);
3165
+ }
3166
+ next(err);
3167
+ };
3168
+ }
3169
+ // ── Lifecycle ───────────────────────────────────────────────────
3170
+ /** Close the database connection. Call when shutting down. */
3171
+ close() {
3172
+ this.storage.close();
3173
+ this.ready = false;
3174
+ this.readyPromise = null;
3175
+ }
3176
+ /** Get the underlying storage instance (for advanced usage). */
3177
+ getStorage() {
3178
+ return this.storage;
3179
+ }
3180
+ /** Get the underlying pattern matcher (for advanced usage). */
3181
+ getMatcher() {
3182
+ return this.matcher;
3183
+ }
3184
+ };
3185
+ var DIR_PATTERNS = [
3186
+ { dirs: ["services", "src/services"], prefix: "#", type: "components" },
3187
+ { dirs: ["routes", "src/routes", "api", "src/api"], prefix: "#", type: "components" },
3188
+ { dirs: ["handlers", "src/handlers"], prefix: "#", type: "components" },
3189
+ { dirs: ["controllers", "src/controllers"], prefix: "#", type: "components" },
3190
+ { dirs: ["components", "src/components"], prefix: "#", type: "components" },
3191
+ { dirs: ["lib", "src/lib"], prefix: "#", type: "components" },
3192
+ { dirs: ["middleware", "src/middleware"], prefix: "^", type: "gates" },
3193
+ { dirs: ["guards", "src/guards"], prefix: "^", type: "gates" },
3194
+ { dirs: ["auth", "src/auth"], prefix: "^", type: "gates" },
3195
+ { dirs: ["events", "src/events"], prefix: "!", type: "signals" },
3196
+ { dirs: ["listeners", "src/listeners"], prefix: "!", type: "signals" },
3197
+ { dirs: ["flows", "src/flows"], prefix: "$", type: "flows" },
3198
+ { dirs: ["workflows", "src/workflows"], prefix: "$", type: "flows" },
3199
+ { dirs: ["pipelines", "src/pipelines"], prefix: "$", type: "flows" }
3200
+ ];
3201
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".js", ".tsx", ".jsx", ".mjs", ".mts"]);
3202
+ function detectSymbols(projectDir) {
3203
+ const result = {
3204
+ components: [],
3205
+ gates: [],
3206
+ flows: [],
3207
+ signals: [],
3208
+ routes: {}
3209
+ };
3210
+ const purposeSymbols = readPurposeFiles(projectDir);
3211
+ if (purposeSymbols) {
3212
+ result.components.push(...purposeSymbols.components);
3213
+ result.gates.push(...purposeSymbols.gates);
3214
+ result.flows.push(...purposeSymbols.flows);
3215
+ result.signals.push(...purposeSymbols.signals);
3216
+ }
3217
+ for (const pattern of DIR_PATTERNS) {
3218
+ for (const dir of pattern.dirs) {
3219
+ const fullPath = path22.join(projectDir, dir);
3220
+ if (!fs22.existsSync(fullPath)) continue;
3221
+ const files = safeReaddir(fullPath);
3222
+ for (const file of files) {
3223
+ const ext = path22.extname(file);
3224
+ if (!CODE_EXTENSIONS.has(ext)) continue;
3225
+ const name = path22.basename(file, ext);
3226
+ if (name === "index" || name.endsWith(".test") || name.endsWith(".spec")) continue;
3227
+ const symbol = `${pattern.prefix}${toKebabCase(name)}`;
3228
+ if (!result[pattern.type].includes(symbol)) {
3229
+ result[pattern.type].push(symbol);
3230
+ }
3231
+ }
3232
+ }
3233
+ }
3234
+ scanRoutes(projectDir, result);
3235
+ return result;
3236
+ }
3237
+ function generateConfig(projectDir) {
3238
+ const detected = detectSymbols(projectDir);
3239
+ return {
3240
+ version: "1.0",
3241
+ project: path22.basename(projectDir),
3242
+ symbols: {
3243
+ components: detected.components.length > 0 ? detected.components : void 0,
3244
+ gates: detected.gates.length > 0 ? detected.gates : void 0,
3245
+ flows: detected.flows.length > 0 ? detected.flows : void 0,
3246
+ signals: detected.signals.length > 0 ? detected.signals : void 0
3247
+ },
3248
+ routes: Object.keys(detected.routes).length > 0 ? detected.routes : void 0
3249
+ };
3250
+ }
3251
+ function readPurposeFiles(projectDir) {
3252
+ const paradigmDir = path22.join(projectDir, ".paradigm");
3253
+ if (!fs22.existsSync(paradigmDir)) return null;
3254
+ const result = {
3255
+ components: [],
3256
+ gates: [],
3257
+ flows: [],
3258
+ signals: [],
3259
+ routes: {}
3260
+ };
3261
+ const purposeFiles = findFiles(projectDir, ".purpose");
3262
+ for (const file of purposeFiles) {
3263
+ try {
3264
+ const content = fs22.readFileSync(file, "utf-8");
3265
+ extractPurposeSymbols(content, result);
3266
+ } catch {
3267
+ }
3268
+ }
3269
+ const hasAny = result.components.length > 0 || result.gates.length > 0 || result.flows.length > 0 || result.signals.length > 0;
3270
+ return hasAny ? result : null;
3271
+ }
3272
+ function extractPurposeSymbols(content, result) {
3273
+ const lines = content.split("\n");
3274
+ let currentSection = "";
3275
+ for (const line of lines) {
3276
+ const trimmed = line.trim();
3277
+ if (trimmed === "components:" || trimmed === "features:") {
3278
+ currentSection = "components";
3279
+ continue;
3280
+ }
3281
+ if (trimmed === "gates:") {
3282
+ currentSection = "gates";
3283
+ continue;
3284
+ }
3285
+ if (trimmed === "flows:") {
3286
+ currentSection = "flows";
3287
+ continue;
3288
+ }
3289
+ if (trimmed === "signals:") {
3290
+ currentSection = "signals";
3291
+ continue;
3292
+ }
3293
+ if (currentSection && /^\s{2}\S/.test(line)) {
3294
+ const idMatch = trimmed.match(/^([a-zA-Z][\w-]*):$/);
3295
+ if (idMatch) {
3296
+ const prefixes = {
3297
+ components: "#",
3298
+ gates: "^",
3299
+ flows: "$",
3300
+ signals: "!"
3301
+ };
3302
+ const prefix = prefixes[currentSection] || "#";
3303
+ const symbol = `${prefix}${idMatch[1]}`;
3304
+ if (!result[currentSection]?.includes(symbol)) {
3305
+ result[currentSection]?.push(symbol);
3306
+ }
3307
+ }
3308
+ }
3309
+ if (trimmed && !line.startsWith(" ") && !trimmed.endsWith(":")) {
3310
+ currentSection = "";
3311
+ }
3312
+ }
3313
+ }
3314
+ function scanRoutes(projectDir, result) {
3315
+ const routeDirs = ["routes", "src/routes", "api", "src/api"];
3316
+ for (const dir of routeDirs) {
3317
+ const fullPath = path22.join(projectDir, dir);
3318
+ if (!fs22.existsSync(fullPath)) continue;
3319
+ const files = safeReaddir(fullPath);
3320
+ for (const file of files) {
3321
+ const ext = path22.extname(file);
3322
+ if (!CODE_EXTENSIONS.has(ext)) continue;
3323
+ const name = path22.basename(file, ext);
3324
+ if (name === "index") continue;
3325
+ const routePrefix = `/api/${toKebabCase(name)}`;
3326
+ const component = `#${toKebabCase(name)}`;
3327
+ result.routes[routePrefix] = component;
3328
+ }
3329
+ }
3330
+ }
3331
+ function toKebabCase(str) {
3332
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/\..*$/, "").toLowerCase();
3333
+ }
3334
+ function safeReaddir(dir) {
3335
+ try {
3336
+ return fs22.readdirSync(dir).filter((f) => {
3337
+ const fullPath = path22.join(dir, f);
3338
+ try {
3339
+ return fs22.statSync(fullPath).isFile();
3340
+ } catch {
3341
+ return false;
3342
+ }
3343
+ });
3344
+ } catch {
3345
+ return [];
3346
+ }
3347
+ }
3348
+ function findFiles(dir, filename, maxDepth = 4, depth = 0) {
3349
+ if (depth > maxDepth) return [];
3350
+ const results = [];
3351
+ const skipDirs = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".next", ".nuxt"]);
3352
+ try {
3353
+ const entries = fs22.readdirSync(dir, { withFileTypes: true });
3354
+ for (const entry of entries) {
3355
+ if (entry.isFile() && entry.name === filename) {
3356
+ results.push(path22.join(dir, entry.name));
3357
+ } else if (entry.isDirectory() && !skipDirs.has(entry.name)) {
3358
+ results.push(...findFiles(path22.join(dir, entry.name), filename, maxDepth, depth + 1));
3359
+ }
3360
+ }
3361
+ } catch {
3362
+ }
3363
+ return results;
3364
+ }
3365
+ var DEFAULT_SIMILARITY_THRESHOLD = 0.6;
3366
+ var DECAY_HALF_LIFE_DAYS = 14;
3367
+ var IncidentGrouper = class {
3368
+ constructor(storage, config) {
3369
+ this.storage = storage;
3370
+ this.similarityThreshold = config?.similarityThreshold ?? DEFAULT_SIMILARITY_THRESHOLD;
3371
+ this.decayHalfLifeDays = config?.decayHalfLifeDays ?? DECAY_HALF_LIFE_DAYS;
3372
+ this.useStackFingerprint = config?.useStackFingerprint ?? true;
3373
+ }
3374
+ similarityThreshold;
3375
+ decayHalfLifeDays;
3376
+ useStackFingerprint;
3377
+ /**
3378
+ * Try to find or create a group for an incident
3379
+ * Returns the group ID if grouped, null if no suitable group
3380
+ */
3381
+ group(incident) {
3382
+ const groups = this.storage.getGroups({ limit: 100 });
3383
+ for (const group of groups) {
3384
+ if (this.shouldJoinGroup(incident, group)) {
3385
+ this.storage.addToGroup(group.id, incident.id);
3386
+ return group.id;
3387
+ }
3388
+ }
3389
+ const similar = this.findSimilar(incident, 10);
3390
+ if (similar.length >= 1) {
3391
+ const commonSymbols = this.extractCommonSymbols([incident, ...similar]);
3392
+ const commonErrorPatterns = this.extractCommonErrorPatterns([
3393
+ incident,
3394
+ ...similar
3395
+ ]);
3396
+ const groupId = this.storage.createGroup({
3397
+ incidents: [incident.id, ...similar.map((i) => i.id)],
3398
+ commonSymbols,
3399
+ commonErrorPatterns,
3400
+ firstSeen: this.getEarliestTimestamp([incident, ...similar]),
3401
+ lastSeen: incident.timestamp,
3402
+ environments: this.getUniqueEnvironments([incident, ...similar])
3403
+ });
3404
+ return groupId;
3405
+ }
3406
+ return null;
3407
+ }
3408
+ /**
3409
+ * Find incidents similar to the given one
3410
+ */
3411
+ findSimilar(incident, limit = 10) {
3412
+ const candidates = this.storage.getRecentIncidents({
3413
+ limit: 500,
3414
+ status: "all"
3415
+ });
3416
+ const similar = [];
3417
+ for (const candidate of candidates) {
3418
+ if (candidate.id === incident.id) {
3419
+ continue;
3420
+ }
3421
+ const score = this.calculateSimilarity(incident, candidate);
3422
+ if (score >= this.similarityThreshold) {
3423
+ similar.push({ incident: candidate, score });
3424
+ }
3425
+ }
3426
+ return similar.sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.incident);
3427
+ }
3428
+ /**
3429
+ * Analyze ungrouped incidents and create groups automatically
3430
+ */
3431
+ analyzeAndGroup(options = {}) {
3432
+ const minSize = options.minSize || 3;
3433
+ const ungrouped = this.storage.getRecentIncidents({
3434
+ limit: 1e3
3435
+ }).filter((i) => !i.groupId);
3436
+ const newGroups = [];
3437
+ const processed = /* @__PURE__ */ new Set();
3438
+ for (const incident of ungrouped) {
3439
+ if (processed.has(incident.id)) {
3440
+ continue;
3441
+ }
3442
+ const similar = ungrouped.filter(
3443
+ (other) => other.id !== incident.id && !processed.has(other.id) && this.calculateSimilarity(incident, other) >= this.similarityThreshold
3444
+ );
3445
+ if (similar.length + 1 >= minSize) {
3446
+ const members = [incident, ...similar];
3447
+ const commonSymbols = this.extractCommonSymbols(members);
3448
+ const commonErrorPatterns = this.extractCommonErrorPatterns(members);
3449
+ const groupId = this.storage.createGroup({
3450
+ incidents: members.map((m) => m.id),
3451
+ commonSymbols,
3452
+ commonErrorPatterns,
3453
+ firstSeen: this.getEarliestTimestamp(members),
3454
+ lastSeen: this.getLatestTimestamp(members),
3455
+ environments: this.getUniqueEnvironments(members)
3456
+ });
3457
+ for (const m of members) {
3458
+ processed.add(m.id);
3459
+ }
3460
+ const group = this.storage.getGroup(groupId);
3461
+ if (group) {
3462
+ newGroups.push(group);
3463
+ }
3464
+ }
3465
+ }
3466
+ return newGroups;
3467
+ }
3468
+ /**
3469
+ * Calculate similarity between two incidents (0-1)
3470
+ * Applies time-decay so older incidents contribute less, and optionally
3471
+ * uses stack trace fingerprinting for more accurate grouping.
3472
+ */
3473
+ calculateSimilarity(a, b) {
3474
+ let score = 0;
3475
+ let maxScore = 0;
3476
+ const hasStacks = this.useStackFingerprint && a.error.stack && b.error.stack;
3477
+ const symbolWeight = hasStacks ? 0.45 : 0.6;
3478
+ const errorWeight = hasStacks ? 0.25 : 0.3;
3479
+ const envWeight = 0.1;
3480
+ const stackWeight = hasStacks ? 0.2 : 0;
3481
+ const symbolTypes = [
3482
+ "feature",
3483
+ "component",
3484
+ "flow",
3485
+ "gate",
3486
+ "signal",
3487
+ "state",
3488
+ "integration"
3489
+ ];
3490
+ for (const type of symbolTypes) {
3491
+ const aValue = a.symbols[type];
3492
+ const bValue = b.symbols[type];
3493
+ if (aValue || bValue) {
3494
+ maxScore += symbolWeight / symbolTypes.length;
3495
+ if (aValue === bValue) {
3496
+ score += symbolWeight / symbolTypes.length;
3497
+ }
3498
+ }
3499
+ }
3500
+ const errorSimilarity = this.stringSimilarity(
3501
+ a.error.message,
3502
+ b.error.message
3503
+ );
3504
+ score += errorWeight * errorSimilarity;
3505
+ maxScore += errorWeight;
3506
+ if (a.environment === b.environment) {
3507
+ score += envWeight;
3508
+ }
3509
+ maxScore += envWeight;
3510
+ if (hasStacks) {
3511
+ const aFingerprint = this.fingerprintStack(a.error.stack);
3512
+ const bFingerprint = this.fingerprintStack(b.error.stack);
3513
+ const stackSimilarity = this.compareFingerprints(aFingerprint, bFingerprint);
3514
+ score += stackWeight * stackSimilarity;
3515
+ maxScore += stackWeight;
3516
+ }
3517
+ const rawScore = maxScore > 0 ? score / maxScore : 0;
3518
+ const timeDelta = Math.abs(
3519
+ new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
3520
+ );
3521
+ const daysDelta = timeDelta / (1e3 * 60 * 60 * 24);
3522
+ const decayFactor = Math.pow(0.5, daysDelta / this.decayHalfLifeDays);
3523
+ return rawScore * decayFactor;
3524
+ }
3525
+ /**
3526
+ * Extract a fingerprint from a stack trace by normalizing frames.
3527
+ * Strips line numbers, column numbers, and absolute paths to capture
3528
+ * the structural signature of the call stack.
3529
+ */
3530
+ fingerprintStack(stack) {
3531
+ return stack.split("\n").filter((line) => line.trim().startsWith("at ")).slice(0, 10).map((frame) => {
3532
+ return frame.trim().replace(/:\d+:\d+\)?$/, "").replace(/\(.*[/\\]/, "(").replace(/^\s*at\s+/, "");
3533
+ });
3534
+ }
3535
+ /**
3536
+ * Compare two stack fingerprints (0-1 similarity)
3537
+ */
3538
+ compareFingerprints(a, b) {
3539
+ if (a.length === 0 && b.length === 0) return 1;
3540
+ if (a.length === 0 || b.length === 0) return 0;
3541
+ let matches = 0;
3542
+ const maxLen = Math.max(a.length, b.length);
3543
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
3544
+ if (a[i] === b[i]) {
3545
+ matches++;
3546
+ }
3547
+ }
3548
+ return matches / maxLen;
3549
+ }
3550
+ /**
3551
+ * Calculate string similarity using Levenshtein distance
3552
+ */
3553
+ stringSimilarity(a, b) {
3554
+ const maxLen = Math.max(a.length, b.length);
3555
+ if (maxLen === 0) return 1;
3556
+ const distance = this.levenshteinDistance(
3557
+ a.toLowerCase(),
3558
+ b.toLowerCase()
3559
+ );
3560
+ return 1 - distance / maxLen;
3561
+ }
3562
+ /**
3563
+ * Levenshtein distance for string comparison
3564
+ */
3565
+ levenshteinDistance(a, b) {
3566
+ if (a.length === 0) return b.length;
3567
+ if (b.length === 0) return a.length;
3568
+ const matrix = [];
3569
+ for (let i = 0; i <= b.length; i++) {
3570
+ matrix[i] = [i];
3571
+ }
3572
+ for (let j = 0; j <= a.length; j++) {
3573
+ matrix[0][j] = j;
3574
+ }
3575
+ for (let i = 1; i <= b.length; i++) {
3576
+ for (let j = 1; j <= a.length; j++) {
3577
+ const cost = a[j - 1] === b[i - 1] ? 0 : 1;
3578
+ matrix[i][j] = Math.min(
3579
+ matrix[i - 1][j] + 1,
3580
+ matrix[i][j - 1] + 1,
3581
+ matrix[i - 1][j - 1] + cost
3582
+ );
3583
+ }
3584
+ }
3585
+ return matrix[b.length][a.length];
3586
+ }
3587
+ /**
3588
+ * Check if incident should join existing group
3589
+ */
3590
+ shouldJoinGroup(incident, group) {
3591
+ let matchCount = 0;
3592
+ let totalCommon = 0;
3593
+ for (const [key, value] of Object.entries(group.commonSymbols)) {
3594
+ if (value) {
3595
+ totalCommon++;
3596
+ const incidentValue = incident.symbols[key];
3597
+ if (incidentValue === value) {
3598
+ matchCount++;
3599
+ }
3600
+ }
3601
+ }
3602
+ if (totalCommon === 0) {
3603
+ return false;
3604
+ }
3605
+ const symbolMatch = matchCount / totalCommon;
3606
+ const errorLower = incident.error.message.toLowerCase();
3607
+ const errorMatch = group.commonErrorPatterns.some(
3608
+ (pattern) => errorLower.includes(pattern.toLowerCase())
3609
+ );
3610
+ return symbolMatch >= 0.5 || errorMatch;
3611
+ }
3612
+ /**
3613
+ * Extract symbols common to all incidents
3614
+ */
3615
+ extractCommonSymbols(incidents) {
3616
+ if (incidents.length === 0) return {};
3617
+ const first = incidents[0].symbols;
3618
+ const common = {};
3619
+ for (const [key, value] of Object.entries(first)) {
3620
+ if (!value) continue;
3621
+ const allMatch = incidents.every(
3622
+ (i) => i.symbols[key] === value
3623
+ );
3624
+ if (allMatch) {
3625
+ common[key] = value;
3626
+ }
3627
+ }
3628
+ return common;
3629
+ }
3630
+ /**
3631
+ * Extract common error patterns from incidents
3632
+ */
3633
+ extractCommonErrorPatterns(incidents) {
3634
+ if (incidents.length === 0) return [];
3635
+ const wordCounts = /* @__PURE__ */ new Map();
3636
+ const stopWords = /* @__PURE__ */ new Set([
3637
+ "the",
3638
+ "a",
3639
+ "an",
3640
+ "is",
3641
+ "are",
3642
+ "was",
3643
+ "were",
3644
+ "in",
3645
+ "on",
3646
+ "at",
3647
+ "to",
3648
+ "for",
3649
+ "of",
3650
+ "with",
3651
+ "error",
3652
+ "failed",
3653
+ "cannot"
3654
+ ]);
3655
+ for (const incident of incidents) {
3656
+ const words = incident.error.message.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
3657
+ const uniqueWords = new Set(words);
3658
+ for (const word of uniqueWords) {
3659
+ wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
3660
+ }
3661
+ }
3662
+ const threshold = Math.ceil(incidents.length * 0.6);
3663
+ const commonPatterns = Array.from(wordCounts.entries()).filter(([, count]) => count >= threshold).map(([word]) => word).slice(0, 5);
3664
+ return commonPatterns;
3665
+ }
3666
+ getEarliestTimestamp(incidents) {
3667
+ return incidents.reduce(
3668
+ (earliest, i) => i.timestamp < earliest ? i.timestamp : earliest,
3669
+ incidents[0].timestamp
3670
+ );
3671
+ }
3672
+ getLatestTimestamp(incidents) {
3673
+ return incidents.reduce(
3674
+ (latest, i) => i.timestamp > latest ? i.timestamp : latest,
3675
+ incidents[0].timestamp
3676
+ );
3677
+ }
3678
+ getUniqueEnvironments(incidents) {
3679
+ return [...new Set(incidents.map((i) => i.environment))];
3680
+ }
3681
+ };
3682
+ var TimelineBuilder = class {
3683
+ /**
3684
+ * Build a timeline from an incident with flow position
3685
+ */
3686
+ build(incident) {
3687
+ if (!incident.flowPosition) {
3688
+ return null;
3689
+ }
3690
+ const events = [];
3691
+ const baseTime = new Date(incident.timestamp).getTime();
3692
+ events.push({
3693
+ timestamp: new Date(baseTime - 5e3).toISOString(),
3694
+ symbol: incident.flowPosition.flowId,
3695
+ type: "flow-started"
3696
+ });
3697
+ let eventOffset = 1e3;
3698
+ for (const signal of incident.flowPosition.actual) {
3699
+ const type = this.inferEventType(signal);
3700
+ events.push({
3701
+ timestamp: new Date(baseTime - 4e3 + eventOffset).toISOString(),
3702
+ symbol: signal,
3703
+ type
3704
+ });
3705
+ eventOffset += Math.random() * 1e3 + 500;
3706
+ }
3707
+ const failedSymbol = incident.flowPosition.failedAt || incident.flowPosition.missing[0] || incident.symbols.gate || incident.symbols.signal || "unknown";
3708
+ events.push({
3709
+ timestamp: incident.timestamp,
3710
+ symbol: failedSymbol,
3711
+ type: "error",
3712
+ data: {
3713
+ message: incident.error.message,
3714
+ missing: incident.flowPosition.missing
3715
+ }
3716
+ });
3717
+ return {
3718
+ incidentId: incident.id,
3719
+ flowId: incident.flowPosition.flowId,
3720
+ events,
3721
+ failure: {
3722
+ at: incident.timestamp,
3723
+ symbol: failedSymbol,
3724
+ reason: incident.error.message
3725
+ }
3726
+ };
3727
+ }
3728
+ /**
3729
+ * Render timeline as ASCII art
3730
+ */
3731
+ renderAscii(timeline) {
3732
+ const lines = [];
3733
+ lines.push(`${timeline.flowId} Timeline`);
3734
+ lines.push("\u2550".repeat(40));
3735
+ lines.push("");
3736
+ for (const event of timeline.events) {
3737
+ const time = this.formatTime(event.timestamp);
3738
+ const icon = this.getEventIcon(event.type);
3739
+ const status = this.getEventStatus(event.type);
3740
+ let line = `${time} ${icon} ${event.symbol}`;
3741
+ if (status) {
3742
+ line += ` (${status})`;
3743
+ }
3744
+ lines.push(line);
3745
+ if (event.type === "error" && event.data) {
3746
+ lines.push(` \u2514\u2500 ${event.data.message}`);
3747
+ if (event.data.missing && Array.isArray(event.data.missing) && event.data.missing.length > 0) {
3748
+ lines.push(
3749
+ ` \u2514\u2500 Expected: ${event.data.missing.join(", ")}`
3750
+ );
3751
+ }
3752
+ }
3753
+ }
3754
+ const missing = timeline.events.find((e) => e.type === "error")?.data?.missing;
3755
+ if (missing && missing.length > 0) {
3756
+ lines.push("");
3757
+ lines.push(`Missing signals: ${missing.join(", ")}`);
3758
+ }
3759
+ return lines.join("\n");
3760
+ }
3761
+ /**
3762
+ * Render timeline as structured data (for MCP/JSON output)
3763
+ */
3764
+ renderStructured(timeline) {
3765
+ return {
3766
+ incidentId: timeline.incidentId,
3767
+ flow: {
3768
+ id: timeline.flowId,
3769
+ eventCount: timeline.events.length
3770
+ },
3771
+ events: timeline.events.map((event) => ({
3772
+ time: this.formatTime(event.timestamp),
3773
+ symbol: event.symbol,
3774
+ type: event.type,
3775
+ status: this.getEventStatus(event.type),
3776
+ data: event.data
3777
+ })),
3778
+ failure: {
3779
+ at: this.formatTime(timeline.failure.at),
3780
+ symbol: timeline.failure.symbol,
3781
+ reason: timeline.failure.reason
3782
+ }
3783
+ };
3784
+ }
3785
+ /**
3786
+ * Infer event type from symbol prefix
3787
+ */
3788
+ inferEventType(symbol) {
3789
+ if (symbol.startsWith("^")) {
3790
+ return "gate-passed";
3791
+ }
3792
+ if (symbol.startsWith("!")) {
3793
+ return "signal-emitted";
3794
+ }
3795
+ if (symbol.startsWith("%")) {
3796
+ return "state-changed";
3797
+ }
3798
+ return "signal-emitted";
3799
+ }
3800
+ /**
3801
+ * Get icon for event type
3802
+ */
3803
+ getEventIcon(type) {
3804
+ switch (type) {
3805
+ case "flow-started":
3806
+ return "\u25B6";
3807
+ case "flow-ended":
3808
+ return "\u25A0";
3809
+ case "gate-passed":
3810
+ return "\u2713";
3811
+ case "gate-failed":
3812
+ return "\u2717";
3813
+ case "signal-emitted":
3814
+ return "\u26A1";
3815
+ case "state-changed":
3816
+ return "\u25C6";
3817
+ case "error":
3818
+ return "\u2717";
3819
+ default:
3820
+ return "\u2022";
3821
+ }
3822
+ }
3823
+ /**
3824
+ * Get status text for event type
3825
+ */
3826
+ getEventStatus(type) {
3827
+ switch (type) {
3828
+ case "gate-passed":
3829
+ return "PASSED";
3830
+ case "gate-failed":
3831
+ return "FAILED";
3832
+ case "signal-emitted":
3833
+ return "EMITTED";
3834
+ case "state-changed":
3835
+ return "CHANGED";
3836
+ case "error":
3837
+ return "ERROR";
3838
+ default:
3839
+ return "";
3840
+ }
3841
+ }
3842
+ /**
3843
+ * Format timestamp for display
3844
+ */
3845
+ formatTime(timestamp) {
3846
+ const date = new Date(timestamp);
3847
+ const hours = String(date.getHours()).padStart(2, "0");
3848
+ const minutes = String(date.getMinutes()).padStart(2, "0");
3849
+ const seconds = String(date.getSeconds()).padStart(2, "0");
3850
+ const millis = String(date.getMilliseconds()).padStart(3, "0");
3851
+ return `${hours}:${minutes}:${seconds}.${millis}`;
3852
+ }
3853
+ };
3854
+ var StatsCalculator = class {
3855
+ constructor(storage) {
3856
+ this.storage = storage;
3857
+ }
3858
+ /**
3859
+ * Get comprehensive statistics for a time period
3860
+ */
3861
+ getStats(periodDays = 7) {
3862
+ const end = (/* @__PURE__ */ new Date()).toISOString();
3863
+ const start = new Date(
3864
+ Date.now() - periodDays * 24 * 60 * 60 * 1e3
3865
+ ).toISOString();
3866
+ return this.storage.getStats({ start, end });
3867
+ }
3868
+ /**
3869
+ * Get health metrics for a specific symbol
3870
+ */
3871
+ getSymbolHealth(symbol) {
3872
+ return this.storage.getSymbolHealth(symbol);
3873
+ }
3874
+ /**
3875
+ * Get trending issues (symbols with increasing incident rates)
3876
+ */
3877
+ getTrendingIssues(days = 7) {
3878
+ const now = Date.now();
3879
+ const halfPeriod = days * 24 * 60 * 60 * 1e3 / 2;
3880
+ const firstHalfStart = new Date(now - days * 24 * 60 * 60 * 1e3).toISOString();
3881
+ const midpoint = new Date(now - halfPeriod).toISOString();
3882
+ const secondHalfEnd = new Date(now).toISOString();
3883
+ const firstHalfIncidents = this.storage.getRecentIncidents({
3884
+ dateFrom: firstHalfStart,
3885
+ dateTo: midpoint,
3886
+ limit: 1e3
3887
+ });
3888
+ const secondHalfIncidents = this.storage.getRecentIncidents({
3889
+ dateFrom: midpoint,
3890
+ dateTo: secondHalfEnd,
3891
+ limit: 1e3
3892
+ });
3893
+ const firstHalfCounts = this.countSymbols(firstHalfIncidents);
3894
+ const secondHalfCounts = this.countSymbols(secondHalfIncidents);
3895
+ const trends = [];
3896
+ const allSymbols = /* @__PURE__ */ new Set([
3897
+ ...firstHalfCounts.keys(),
3898
+ ...secondHalfCounts.keys()
3899
+ ]);
3900
+ for (const symbol of allSymbols) {
3901
+ const first = firstHalfCounts.get(symbol) || 0;
3902
+ const second = secondHalfCounts.get(symbol) || 0;
3903
+ if (first === 0 && second > 0) {
3904
+ trends.push({ symbol, trend: second * 100 });
3905
+ } else if (first > 0) {
3906
+ const change = (second - first) / first * 100;
3907
+ trends.push({ symbol, trend: change });
3908
+ }
3909
+ }
3910
+ return trends.filter((t) => t.trend > 0).sort((a, b) => b.trend - a.trend).slice(0, 10);
3911
+ }
3912
+ /**
3913
+ * Get resolution metrics
3914
+ */
3915
+ getResolutionMetrics() {
3916
+ const stats = this.getStats(30);
3917
+ return {
3918
+ avgTimeToResolve: stats.resolution.avgTimeToResolve,
3919
+ resolvedWithPattern: stats.resolution.resolvedWithPattern,
3920
+ resolvedManually: stats.resolution.resolvedManually,
3921
+ totalResolved: stats.incidents.resolved,
3922
+ resolutionRate: stats.resolution.resolutionRate
3923
+ };
3924
+ }
3925
+ /**
3926
+ * Get pattern effectiveness metrics
3927
+ */
3928
+ getPatternEffectiveness() {
3929
+ const patterns = this.storage.getAllPatterns({ includePrivate: true });
3930
+ return patterns.filter((p) => p.confidence.timesMatched > 0).map((p) => ({
3931
+ patternId: p.id,
3932
+ name: p.name,
3933
+ matches: p.confidence.timesMatched,
3934
+ resolutions: p.confidence.timesResolved,
3935
+ recurrences: p.confidence.timesRecurred,
3936
+ effectiveness: p.confidence.timesMatched > 0 ? Math.round(
3937
+ (p.confidence.timesResolved - p.confidence.timesRecurred) / p.confidence.timesMatched * 100
3938
+ ) : 0
3939
+ })).sort((a, b) => b.effectiveness - a.effectiveness);
3940
+ }
3941
+ /**
3942
+ * Get incident rate by hour of day
3943
+ */
3944
+ getIncidentsByHour(days = 7) {
3945
+ const start = new Date(
3946
+ Date.now() - days * 24 * 60 * 60 * 1e3
3947
+ ).toISOString();
3948
+ const incidents = this.storage.getRecentIncidents({
3949
+ dateFrom: start,
3950
+ limit: 1e4
3951
+ });
3952
+ const hourCounts = /* @__PURE__ */ new Map();
3953
+ for (let i = 0; i < 24; i++) {
3954
+ hourCounts.set(i, 0);
3955
+ }
3956
+ for (const incident of incidents) {
3957
+ const hour = new Date(incident.timestamp).getHours();
3958
+ hourCounts.set(hour, (hourCounts.get(hour) || 0) + 1);
3959
+ }
3960
+ return Array.from(hourCounts.entries()).map(([hour, count]) => ({
3961
+ hour,
3962
+ count
3963
+ }));
3964
+ }
3965
+ /**
3966
+ * Get incident rate by environment
3967
+ */
3968
+ getIncidentsByEnvironment() {
3969
+ const stats = this.getStats(30);
3970
+ const total = stats.incidents.total;
3971
+ return Object.entries(stats.incidents.byEnvironment).map(([environment, count]) => ({
3972
+ environment,
3973
+ count,
3974
+ percentage: total > 0 ? Math.round(count / total * 100) : 0
3975
+ })).sort((a, b) => b.count - a.count);
3976
+ }
3977
+ /**
3978
+ * Get symbol correlation matrix (which symbols fail together)
3979
+ */
3980
+ getSymbolCorrelation() {
3981
+ const incidents = this.storage.getRecentIncidents({ limit: 1e3 });
3982
+ const correlations = /* @__PURE__ */ new Map();
3983
+ const symbolCounts = /* @__PURE__ */ new Map();
3984
+ for (const incident of incidents) {
3985
+ const symbols = this.getSymbolsFromIncident(incident);
3986
+ for (const symbol of symbols) {
3987
+ symbolCounts.set(symbol, (symbolCounts.get(symbol) || 0) + 1);
3988
+ }
3989
+ for (let i = 0; i < symbols.length; i++) {
3990
+ for (let j = i + 1; j < symbols.length; j++) {
3991
+ const key = [symbols[i], symbols[j]].sort().join("|");
3992
+ correlations.set(key, (correlations.get(key) || 0) + 1);
3993
+ }
3994
+ }
3995
+ }
3996
+ const results = [];
3997
+ for (const [key, count] of correlations) {
3998
+ const [symbol1, symbol2] = key.split("|");
3999
+ const count1 = symbolCounts.get(symbol1) || 1;
4000
+ const count2 = symbolCounts.get(symbol2) || 1;
4001
+ const correlation = count / Math.max(count1, count2);
4002
+ if (correlation > 0.3) {
4003
+ results.push({
4004
+ symbol1,
4005
+ symbol2,
4006
+ correlation: Math.round(correlation * 100) / 100
4007
+ });
4008
+ }
4009
+ }
4010
+ return results.sort((a, b) => b.correlation - a.correlation).slice(0, 20);
4011
+ }
4012
+ /**
4013
+ * Generate a summary dashboard string
4014
+ */
4015
+ generateDashboard(periodDays = 7) {
4016
+ const stats = this.getStats(periodDays);
4017
+ const lines = [];
4018
+ lines.push("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
4019
+ lines.push("\u2551 PARADIGM SENTINEL DASHBOARD \u2551");
4020
+ lines.push("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
4021
+ const todayCount = stats.incidents.byDay[stats.incidents.byDay.length - 1]?.count || 0;
4022
+ lines.push(
4023
+ `\u2551 Open: ${String(stats.incidents.open).padEnd(4)} \u2502 Investigating: ${String(stats.incidents.total - stats.incidents.open - stats.incidents.resolved).padEnd(3)} \u2502 Resolved: ${String(stats.incidents.resolved).padEnd(4)} \u2502 Today: +${todayCount} \u2551`
4024
+ );
4025
+ lines.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
4026
+ lines.push("");
4027
+ lines.push("Incidents by Day (last 7 days):");
4028
+ lines.push("\u2500".repeat(50));
4029
+ const maxDayCount = Math.max(...stats.incidents.byDay.map((d) => d.count), 1);
4030
+ for (const day of stats.incidents.byDay.slice(-7)) {
4031
+ const barLength = Math.round(day.count / maxDayCount * 30);
4032
+ const bar = "\u2588".repeat(barLength);
4033
+ lines.push(`${day.date.substring(5)} ${bar} ${day.count}`);
4034
+ }
4035
+ lines.push("");
4036
+ lines.push("Most Affected Symbols:");
4037
+ lines.push("\u2500".repeat(50));
4038
+ for (const { symbol, count } of stats.symbols.mostIncidents.slice(0, 5)) {
4039
+ lines.push(` ${symbol.padEnd(25)} ${count} incidents`);
4040
+ }
4041
+ lines.push("");
4042
+ lines.push("Top Patterns:");
4043
+ lines.push("\u2500".repeat(50));
4044
+ for (const { patternId, resolvedCount } of stats.patterns.mostEffective.slice(0, 5)) {
4045
+ lines.push(` ${patternId.padEnd(25)} ${resolvedCount} resolved`);
4046
+ }
4047
+ lines.push("");
4048
+ lines.push("Resolution Stats:");
4049
+ lines.push("\u2500".repeat(50));
4050
+ lines.push(` Resolution rate: ${Math.round(stats.resolution.resolutionRate)}%`);
4051
+ lines.push(` With pattern: ${stats.resolution.resolvedWithPattern}`);
4052
+ lines.push(` Manual: ${stats.resolution.resolvedManually}`);
4053
+ return lines.join("\n");
4054
+ }
4055
+ /**
4056
+ * Helper: Count symbols across incidents
4057
+ */
4058
+ countSymbols(incidents) {
4059
+ const counts = /* @__PURE__ */ new Map();
4060
+ for (const incident of incidents) {
4061
+ for (const [, value] of Object.entries(incident.symbols)) {
4062
+ if (value) {
4063
+ counts.set(value, (counts.get(value) || 0) + 1);
4064
+ }
4065
+ }
4066
+ }
4067
+ return counts;
4068
+ }
4069
+ /**
4070
+ * Helper: Get all symbols from incident
4071
+ */
4072
+ getSymbolsFromIncident(incident) {
4073
+ const symbols = [];
4074
+ for (const [, value] of Object.entries(incident.symbols)) {
4075
+ if (value) {
4076
+ symbols.push(value);
4077
+ }
4078
+ }
4079
+ return symbols;
4080
+ }
4081
+ };
4082
+ var ContextEnricher = class {
4083
+ constructor(projectRoot = process.cwd()) {
4084
+ this.projectRoot = projectRoot;
4085
+ }
4086
+ symbolCache = /* @__PURE__ */ new Map();
4087
+ purposeCache = /* @__PURE__ */ new Map();
4088
+ /**
4089
+ * Enrich an incident with symbol context
4090
+ */
4091
+ enrich(incident) {
4092
+ const symbolEnrichments = {};
4093
+ for (const [, value] of Object.entries(incident.symbols)) {
4094
+ if (value) {
4095
+ const enrichment = this.getSymbolContext(value);
4096
+ if (enrichment && Object.keys(enrichment).length > 0) {
4097
+ symbolEnrichments[value] = enrichment;
4098
+ }
4099
+ }
4100
+ }
4101
+ let flowDescription;
4102
+ if (incident.symbols.flow) {
4103
+ const flowContext = this.getSymbolContext(incident.symbols.flow);
4104
+ flowDescription = flowContext?.description;
4105
+ }
4106
+ return {
4107
+ ...incident,
4108
+ enriched: {
4109
+ symbols: symbolEnrichments,
4110
+ flowDescription
4111
+ }
4112
+ };
4113
+ }
4114
+ /**
4115
+ * Get symbol metadata from index or .purpose files
4116
+ */
4117
+ getSymbolContext(symbol) {
4118
+ const cached = this.symbolCache.get(symbol);
4119
+ if (cached) {
4120
+ return cached;
4121
+ }
4122
+ const enrichment = {};
4123
+ const indexEntry = this.findInSymbolIndex(symbol);
4124
+ if (indexEntry) {
4125
+ enrichment.description = indexEntry.description;
4126
+ enrichment.definedIn = indexEntry.file;
4127
+ enrichment.references = indexEntry.references;
4128
+ enrichment.referencedBy = indexEntry.referencedBy;
4129
+ }
4130
+ const purposeEntry = this.findInPurposeFiles(symbol);
4131
+ if (purposeEntry) {
4132
+ if (!enrichment.description && purposeEntry.description) {
4133
+ enrichment.description = purposeEntry.description;
4134
+ }
4135
+ if (purposeEntry.references) {
4136
+ enrichment.references = [
4137
+ .../* @__PURE__ */ new Set([...enrichment.references || [], ...purposeEntry.references])
4138
+ ];
4139
+ }
4140
+ if (purposeEntry.referencedBy) {
4141
+ enrichment.referencedBy = [
4142
+ .../* @__PURE__ */ new Set([...enrichment.referencedBy || [], ...purposeEntry.referencedBy])
4143
+ ];
4144
+ }
4145
+ }
4146
+ this.symbolCache.set(symbol, enrichment);
4147
+ return enrichment;
4148
+ }
4149
+ /**
4150
+ * Find symbol in premise index
4151
+ */
4152
+ findInSymbolIndex(symbol) {
4153
+ const indexPath = path32.join(this.projectRoot, ".paradigm", "index.json");
4154
+ if (!fs32.existsSync(indexPath)) {
4155
+ return null;
4156
+ }
4157
+ try {
4158
+ const indexContent = fs32.readFileSync(indexPath, "utf-8");
4159
+ const index = JSON.parse(indexContent);
4160
+ if (index.symbols && Array.isArray(index.symbols)) {
4161
+ return index.symbols.find(
4162
+ (s) => s.id === symbol
4163
+ ) || null;
4164
+ }
4165
+ return null;
4166
+ } catch {
4167
+ return null;
4168
+ }
4169
+ }
4170
+ /**
4171
+ * Find symbol in .purpose files
4172
+ */
4173
+ findInPurposeFiles(symbol) {
4174
+ const searchPaths = this.getSearchPathsForSymbol(symbol);
4175
+ for (const searchPath of searchPaths) {
4176
+ const fullPath = path32.join(this.projectRoot, searchPath);
4177
+ if (!fs32.existsSync(fullPath)) {
4178
+ continue;
4179
+ }
4180
+ const cached = this.purposeCache.get(fullPath);
4181
+ if (cached) {
4182
+ if (cached.symbol === symbol) {
4183
+ return cached;
4184
+ }
4185
+ continue;
4186
+ }
4187
+ try {
4188
+ const content = fs32.readFileSync(fullPath, "utf-8");
4189
+ const purpose = this.parsePurposeFile(content);
4190
+ this.purposeCache.set(fullPath, purpose);
4191
+ if (purpose.symbol === symbol) {
4192
+ return purpose;
4193
+ }
4194
+ } catch {
4195
+ continue;
4196
+ }
4197
+ }
4198
+ return null;
4199
+ }
4200
+ /**
4201
+ * Get potential file paths for a symbol
4202
+ */
4203
+ getSearchPathsForSymbol(symbol) {
4204
+ const paths = [];
4205
+ const cleanSymbol = symbol.replace(/^[@#$%^!&~?]/, "");
4206
+ const prefixDirs = {
4207
+ "@": ["features", "src/features"],
4208
+ "#": ["components", "src/components"],
4209
+ "$": ["flows", "src/flows"],
4210
+ "^": ["middleware", "gates", "src/middleware"],
4211
+ "!": ["signals", "events", "src/signals"],
4212
+ "%": ["state", "store", "src/state"],
4213
+ "&": ["integrations", "services", "src/integrations"]
4214
+ };
4215
+ const prefix = symbol[0];
4216
+ const dirs = prefixDirs[prefix] || [];
4217
+ for (const dir of dirs) {
4218
+ paths.push(path32.join(dir, cleanSymbol, ".purpose"));
4219
+ paths.push(path32.join(dir, `${cleanSymbol}.purpose`));
4220
+ }
4221
+ paths.push(path32.join(".paradigm", "purposes", `${cleanSymbol}.yaml`));
4222
+ paths.push(path32.join(".paradigm", "purposes", `${cleanSymbol}.json`));
4223
+ return paths;
4224
+ }
4225
+ /**
4226
+ * Parse a .purpose file
4227
+ */
4228
+ parsePurposeFile(content) {
4229
+ const result = {};
4230
+ const lines = content.split("\n");
4231
+ for (const line of lines) {
4232
+ const trimmed = line.trim();
4233
+ if (trimmed.startsWith("symbol:")) {
4234
+ result.symbol = trimmed.substring(7).trim();
4235
+ } else if (trimmed.startsWith("description:")) {
4236
+ result.description = trimmed.substring(12).trim();
4237
+ } else if (trimmed.startsWith("purpose:")) {
4238
+ result.description = trimmed.substring(8).trim();
4239
+ }
4240
+ }
4241
+ if (!result.description) {
4242
+ const firstLine = lines.find((l) => l.trim() && !l.startsWith("#"));
4243
+ if (firstLine) {
4244
+ result.description = firstLine.trim();
4245
+ }
4246
+ }
4247
+ return result;
4248
+ }
4249
+ /**
4250
+ * Clear caches
4251
+ */
4252
+ clearCache() {
4253
+ this.symbolCache.clear();
4254
+ this.purposeCache.clear();
4255
+ }
4256
+ /**
4257
+ * Batch enrich multiple incidents
4258
+ */
4259
+ enrichBatch(incidents) {
4260
+ return incidents.map((i) => this.enrich(i));
4261
+ }
4262
+ };
4263
+ var PatternSuggester = class {
4264
+ constructor(storage) {
4265
+ this.storage = storage;
4266
+ }
4267
+ /**
4268
+ * Suggest a pattern from a resolved incident
4269
+ */
4270
+ suggestFromIncident(incident) {
4271
+ const baseId = this.generatePatternId(incident);
4272
+ const symbols = this.buildSymbolCriteria(incident.symbols);
4273
+ const errorKeywords = this.extractErrorKeywords(incident.error.message);
4274
+ const pattern = {
4275
+ id: baseId,
4276
+ name: this.generatePatternName(incident),
4277
+ description: `Auto-suggested pattern from incident ${incident.id}`,
4278
+ pattern: {
4279
+ symbols,
4280
+ errorContains: errorKeywords.length > 0 ? errorKeywords : void 0,
4281
+ missingSignals: incident.flowPosition?.missing
4282
+ },
4283
+ resolution: {
4284
+ description: incident.resolution?.notes || "Resolution approach TBD",
4285
+ strategy: this.inferStrategy([incident]),
4286
+ priority: "medium"
4287
+ },
4288
+ source: "suggested",
4289
+ private: false,
4290
+ tags: this.generateTags(incident)
4291
+ };
4292
+ return pattern;
4293
+ }
4294
+ /**
4295
+ * Suggest a pattern from an incident group
4296
+ */
4297
+ suggestFromGroup(group) {
4298
+ const baseId = `group-${group.id.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
4299
+ const symbols = this.buildSymbolCriteria(group.commonSymbols);
4300
+ const groupIncidents = group.incidents.slice(0, 20).map((id) => this.storage.getIncident(id)).filter((i) => i != null);
4301
+ const pattern = {
4302
+ id: baseId,
4303
+ name: group.name || `Pattern from group ${group.id}`,
4304
+ description: `Auto-suggested pattern from incident group with ${group.count} incidents`,
4305
+ pattern: {
4306
+ symbols,
4307
+ errorContains: group.commonErrorPatterns.length > 0 ? group.commonErrorPatterns : void 0
4308
+ },
4309
+ resolution: {
4310
+ description: "Resolution approach TBD based on grouped incidents",
4311
+ strategy: groupIncidents.length > 0 ? this.inferStrategy(groupIncidents) : "fix-code",
4312
+ priority: this.getPriorityFromCount(group.count)
4313
+ },
4314
+ source: "suggested",
4315
+ private: false,
4316
+ tags: this.generateTagsFromGroup(group)
4317
+ };
4318
+ return pattern;
4319
+ }
4320
+ /**
4321
+ * Find incidents that could become patterns
4322
+ */
4323
+ findPatternCandidates(minOccurrences = 3) {
4324
+ const incidents = this.storage.getRecentIncidents({
4325
+ limit: 1e3,
4326
+ status: "resolved"
4327
+ });
4328
+ const signatureGroups = /* @__PURE__ */ new Map();
4329
+ for (const incident of incidents) {
4330
+ const signature = this.getSymbolSignature(incident.symbols);
4331
+ const existing = signatureGroups.get(signature) || [];
4332
+ existing.push(incident);
4333
+ signatureGroups.set(signature, existing);
4334
+ }
4335
+ const candidates = [];
4336
+ for (const [, groupIncidents] of signatureGroups) {
4337
+ if (groupIncidents.length >= minOccurrences) {
4338
+ const hasPattern = this.hasMatchingPattern(groupIncidents[0]);
4339
+ if (hasPattern) continue;
4340
+ const suggestedPattern = this.suggestFromIncidents(groupIncidents);
4341
+ candidates.push({
4342
+ incidents: groupIncidents,
4343
+ suggestedPattern,
4344
+ occurrenceCount: groupIncidents.length
4345
+ });
4346
+ }
4347
+ }
4348
+ return candidates.sort((a, b) => b.occurrenceCount - a.occurrenceCount);
4349
+ }
4350
+ /**
4351
+ * Generate pattern from multiple similar incidents
4352
+ */
4353
+ suggestFromIncidents(incidents) {
4354
+ const commonSymbols = this.extractCommonSymbols(incidents);
4355
+ const symbols = this.buildSymbolCriteria(commonSymbols);
4356
+ const errorKeywords = this.extractCommonErrorKeywords(incidents);
4357
+ const missingSignals = this.extractCommonMissingSignals(incidents);
4358
+ const baseId = this.generatePatternId(incidents[0]);
4359
+ return {
4360
+ id: baseId,
4361
+ name: this.generatePatternName(incidents[0]),
4362
+ description: `Auto-suggested pattern from ${incidents.length} similar incidents`,
4363
+ pattern: {
4364
+ symbols,
4365
+ errorContains: errorKeywords.length > 0 ? errorKeywords : void 0,
4366
+ missingSignals: missingSignals.length > 0 ? missingSignals : void 0
4367
+ },
4368
+ resolution: {
4369
+ description: "Resolution approach based on previous resolutions",
4370
+ strategy: this.inferStrategy(incidents),
4371
+ priority: this.getPriorityFromCount(incidents.length)
4372
+ },
4373
+ source: "suggested",
4374
+ private: false,
4375
+ tags: this.generateTagsFromIncidents(incidents)
4376
+ };
4377
+ }
4378
+ /**
4379
+ * Build symbol criteria for pattern, adding wildcards where appropriate
4380
+ */
4381
+ buildSymbolCriteria(symbols) {
4382
+ const criteria = {};
4383
+ for (const [key, value] of Object.entries(symbols)) {
4384
+ if (value) {
4385
+ criteria[key] = value;
4386
+ }
4387
+ }
4388
+ return criteria;
4389
+ }
4390
+ /**
4391
+ * Extract keywords from error message
4392
+ */
4393
+ extractErrorKeywords(message) {
4394
+ const stopWords = /* @__PURE__ */ new Set([
4395
+ "the",
4396
+ "a",
4397
+ "an",
4398
+ "is",
4399
+ "are",
4400
+ "was",
4401
+ "were",
4402
+ "in",
4403
+ "on",
4404
+ "at",
4405
+ "to",
4406
+ "for",
4407
+ "of",
4408
+ "with",
4409
+ "and",
4410
+ "or",
4411
+ "but",
4412
+ "not",
4413
+ "no",
4414
+ "be",
4415
+ "been",
4416
+ "have",
4417
+ "has",
4418
+ "had",
4419
+ "do",
4420
+ "does",
4421
+ "did"
4422
+ ]);
4423
+ const words = message.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
4424
+ const unique = [...new Set(words)];
4425
+ return unique.slice(0, 5);
4426
+ }
4427
+ /**
4428
+ * Extract common error keywords from multiple incidents
4429
+ */
4430
+ extractCommonErrorKeywords(incidents) {
4431
+ const wordCounts = /* @__PURE__ */ new Map();
4432
+ for (const incident of incidents) {
4433
+ const keywords = this.extractErrorKeywords(incident.error.message);
4434
+ for (const keyword of keywords) {
4435
+ wordCounts.set(keyword, (wordCounts.get(keyword) || 0) + 1);
4436
+ }
4437
+ }
4438
+ const threshold = Math.ceil(incidents.length * 0.5);
4439
+ return Array.from(wordCounts.entries()).filter(([, count]) => count >= threshold).map(([word]) => word).slice(0, 5);
4440
+ }
4441
+ /**
4442
+ * Extract symbols common to all incidents
4443
+ */
4444
+ extractCommonSymbols(incidents) {
4445
+ if (incidents.length === 0) return {};
4446
+ const first = incidents[0].symbols;
4447
+ const common = {};
4448
+ for (const [key, value] of Object.entries(first)) {
4449
+ if (!value) continue;
4450
+ const allMatch = incidents.every(
4451
+ (i) => i.symbols[key] === value
4452
+ );
4453
+ if (allMatch) {
4454
+ common[key] = value;
4455
+ }
4456
+ }
4457
+ return common;
4458
+ }
4459
+ /**
4460
+ * Extract missing signals common to multiple incidents
4461
+ */
4462
+ extractCommonMissingSignals(incidents) {
4463
+ const signalCounts = /* @__PURE__ */ new Map();
4464
+ for (const incident of incidents) {
4465
+ if (!incident.flowPosition?.missing) continue;
4466
+ for (const signal of incident.flowPosition.missing) {
4467
+ signalCounts.set(signal, (signalCounts.get(signal) || 0) + 1);
4468
+ }
4469
+ }
4470
+ const threshold = Math.ceil(incidents.length * 0.5);
4471
+ return Array.from(signalCounts.entries()).filter(([, count]) => count >= threshold).map(([signal]) => signal);
4472
+ }
4473
+ /**
4474
+ * Generate a pattern ID from incident
4475
+ */
4476
+ generatePatternId(incident) {
4477
+ const parts = [];
4478
+ if (incident.symbols.gate) {
4479
+ parts.push(incident.symbols.gate.replace(/[^a-z0-9]/gi, ""));
4480
+ } else if (incident.symbols.feature) {
4481
+ parts.push(incident.symbols.feature.replace(/[^a-z0-9]/gi, ""));
4482
+ } else if (incident.symbols.component) {
4483
+ parts.push(incident.symbols.component.replace(/[^a-z0-9]/gi, ""));
4484
+ } else if (incident.symbols.integration) {
4485
+ parts.push(incident.symbols.integration.replace(/[^a-z0-9]/gi, ""));
4486
+ } else {
4487
+ parts.push("unknown");
4488
+ }
4489
+ const errorType = incident.error.type?.toLowerCase() || "error";
4490
+ parts.push(errorType.replace(/[^a-z0-9]/gi, ""));
4491
+ parts.push(String(Date.now() % 1e3).padStart(3, "0"));
4492
+ return parts.join("-");
4493
+ }
4494
+ /**
4495
+ * Generate a human-readable pattern name
4496
+ */
4497
+ generatePatternName(incident) {
4498
+ const parts = [];
4499
+ if (incident.symbols.feature) {
4500
+ parts.push(
4501
+ incident.symbols.feature.replace("@", "").replace(/-/g, " ")
4502
+ );
4503
+ }
4504
+ if (incident.symbols.gate) {
4505
+ parts.push("gate " + incident.symbols.gate.replace("^", ""));
4506
+ }
4507
+ if (incident.error.type) {
4508
+ parts.push(incident.error.type);
4509
+ }
4510
+ if (parts.length === 0) {
4511
+ return "Unnamed Pattern";
4512
+ }
4513
+ const name = parts.join(" - ");
4514
+ return name.charAt(0).toUpperCase() + name.slice(1);
4515
+ }
4516
+ /**
4517
+ * Generate tags from incident
4518
+ */
4519
+ generateTags(incident) {
4520
+ const tags = [];
4521
+ if (incident.symbols.feature) {
4522
+ tags.push("feature");
4523
+ }
4524
+ if (incident.symbols.gate) {
4525
+ tags.push("gate");
4526
+ }
4527
+ if (incident.symbols.integration) {
4528
+ tags.push("integration");
4529
+ tags.push(incident.symbols.integration.replace("&", ""));
4530
+ }
4531
+ if (incident.error.type) {
4532
+ tags.push(incident.error.type.toLowerCase());
4533
+ }
4534
+ tags.push(incident.environment);
4535
+ return [...new Set(tags)].slice(0, 5);
4536
+ }
4537
+ /**
4538
+ * Generate tags from incident group
4539
+ */
4540
+ generateTagsFromGroup(group) {
4541
+ const tags = ["grouped"];
4542
+ if (group.commonSymbols.feature) {
4543
+ tags.push("feature");
4544
+ }
4545
+ if (group.commonSymbols.gate) {
4546
+ tags.push("gate");
4547
+ }
4548
+ if (group.commonSymbols.integration) {
4549
+ tags.push("integration");
4550
+ }
4551
+ for (const env of group.environments) {
4552
+ tags.push(env);
4553
+ }
4554
+ return [...new Set(tags)].slice(0, 5);
4555
+ }
4556
+ /**
4557
+ * Generate tags from multiple incidents
4558
+ */
4559
+ generateTagsFromIncidents(incidents) {
4560
+ const tagCounts = /* @__PURE__ */ new Map();
4561
+ for (const incident of incidents) {
4562
+ const tags = this.generateTags(incident);
4563
+ for (const tag of tags) {
4564
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
4565
+ }
4566
+ }
4567
+ return Array.from(tagCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([tag]) => tag);
4568
+ }
4569
+ /**
4570
+ * Get symbol signature for grouping
4571
+ */
4572
+ getSymbolSignature(symbols) {
4573
+ const parts = [];
4574
+ if (symbols.feature) parts.push(`f:${symbols.feature}`);
4575
+ if (symbols.component) parts.push(`c:${symbols.component}`);
4576
+ if (symbols.flow) parts.push(`fl:${symbols.flow}`);
4577
+ if (symbols.gate) parts.push(`g:${symbols.gate}`);
4578
+ if (symbols.integration) parts.push(`i:${symbols.integration}`);
4579
+ return parts.sort().join("|");
4580
+ }
4581
+ /**
4582
+ * Check if there's already a pattern matching this incident
4583
+ */
4584
+ hasMatchingPattern(incident) {
4585
+ const patterns = this.storage.getAllPatterns({ includePrivate: true });
4586
+ for (const pattern of patterns) {
4587
+ let matchCount = 0;
4588
+ const symbolTypes = [
4589
+ "feature",
4590
+ "component",
4591
+ "flow",
4592
+ "gate",
4593
+ "signal",
4594
+ "integration"
4595
+ ];
4596
+ for (const type of symbolTypes) {
4597
+ const patternValue = pattern.pattern.symbols[type];
4598
+ const incidentValue = incident.symbols[type];
4599
+ if (patternValue && incidentValue && patternValue === incidentValue) {
4600
+ matchCount++;
4601
+ }
4602
+ }
4603
+ if (matchCount >= 2) {
4604
+ return true;
4605
+ }
4606
+ }
4607
+ return false;
4608
+ }
4609
+ /**
4610
+ * Infer resolution strategy from incident error patterns and context.
4611
+ * Uses keyword heuristics across all incident messages to pick the
4612
+ * most likely resolution approach.
4613
+ */
4614
+ inferStrategy(incidents) {
4615
+ const messages = incidents.map((i) => i.error.message.toLowerCase());
4616
+ const hasKeyword = (keywords) => messages.some((m) => keywords.some((k) => m.includes(k)));
4617
+ if (hasKeyword(["revert", "rollback", "regression", "broke after deploy", "since deploy"])) {
4618
+ return "rollback";
4619
+ }
4620
+ if (hasKeyword(["config", "environment variable", "env var", "missing key", "secret", "credential"])) {
4621
+ return "config-change";
4622
+ }
4623
+ if (hasKeyword(["out of memory", "oom", "heap", "memory limit", "capacity", "too many connections", "pool exhausted"])) {
4624
+ return "scale-up";
4625
+ }
4626
+ if (hasKeyword(["timeout", "network", "econnrefused", "econnreset", "dns", "socket hang up"])) {
4627
+ return "retry";
4628
+ }
4629
+ if (hasKeyword(["unavailable", "service down", "circuit breaker", "fallback", "503", "502"])) {
4630
+ return "fallback";
4631
+ }
4632
+ if (hasKeyword(["validation", "invalid", "required", "constraint", "duplicate", "not found", "404"])) {
4633
+ return "fix-data";
4634
+ }
4635
+ if (hasKeyword(["permission", "forbidden", "403", "401", "unauthorized", "access denied"])) {
4636
+ return "escalate";
4637
+ }
4638
+ const uniqueTypes = new Set(incidents.map((i) => i.error.type).filter(Boolean));
4639
+ if (uniqueTypes.size > 2) {
4640
+ return "investigate";
4641
+ }
4642
+ return "fix-code";
4643
+ }
4644
+ /**
4645
+ * Get priority based on occurrence count
4646
+ */
4647
+ getPriorityFromCount(count) {
4648
+ if (count >= 20) return "critical";
4649
+ if (count >= 10) return "high";
4650
+ if (count >= 5) return "medium";
4651
+ return "low";
4652
+ }
4653
+ };
4654
+ var PatternImporter = class {
4655
+ /**
4656
+ * Validate a pattern export file
4657
+ */
4658
+ validate(data) {
4659
+ const errors = [];
4660
+ const warnings = [];
4661
+ if (!data || typeof data !== "object") {
4662
+ return { valid: false, errors: ["Invalid data: expected object"], warnings: [] };
4663
+ }
4664
+ const obj = data;
4665
+ if (!obj.version) {
4666
+ errors.push("Missing version field");
4667
+ }
4668
+ if (!Array.isArray(obj.patterns)) {
4669
+ errors.push("Missing or invalid patterns array");
4670
+ return { valid: false, errors, warnings };
4671
+ }
4672
+ for (let i = 0; i < obj.patterns.length; i++) {
4673
+ const pattern = obj.patterns[i];
4674
+ const patternErrors = this.validatePattern(pattern, i);
4675
+ errors.push(...patternErrors.errors);
4676
+ warnings.push(...patternErrors.warnings);
4677
+ }
4678
+ return {
4679
+ valid: errors.length === 0,
4680
+ errors,
4681
+ warnings
4682
+ };
4683
+ }
4684
+ /**
4685
+ * Validate a single pattern
4686
+ */
4687
+ validatePattern(pattern, index) {
4688
+ const errors = [];
4689
+ const warnings = [];
4690
+ const prefix = `Pattern[${index}]`;
4691
+ if (!pattern.id || typeof pattern.id !== "string") {
4692
+ errors.push(`${prefix}: Missing or invalid id`);
4693
+ } else if (!/^[a-z0-9-]+$/.test(pattern.id)) {
4694
+ warnings.push(`${prefix}: ID "${pattern.id}" should be kebab-case`);
4695
+ }
4696
+ if (!pattern.name || typeof pattern.name !== "string") {
4697
+ errors.push(`${prefix}: Missing or invalid name`);
4698
+ }
4699
+ if (!pattern.pattern || typeof pattern.pattern !== "object") {
4700
+ errors.push(`${prefix}: Missing or invalid pattern criteria`);
4701
+ } else {
4702
+ const criteria = pattern.pattern;
4703
+ const hasSymbols = criteria.symbols && typeof criteria.symbols === "object" && Object.keys(criteria.symbols).length > 0;
4704
+ const hasErrorContains = Array.isArray(criteria.errorContains) && criteria.errorContains.length > 0;
4705
+ const hasErrorMatches = criteria.errorMatches && typeof criteria.errorMatches === "string";
4706
+ const hasMissingSignals = Array.isArray(criteria.missingSignals) && criteria.missingSignals.length > 0;
4707
+ if (!hasSymbols && !hasErrorContains && !hasErrorMatches && !hasMissingSignals) {
4708
+ errors.push(`${prefix}: Pattern must have at least one matching criteria`);
4709
+ }
4710
+ }
4711
+ if (!pattern.resolution || typeof pattern.resolution !== "object") {
4712
+ errors.push(`${prefix}: Missing or invalid resolution`);
4713
+ } else {
4714
+ const resolution = pattern.resolution;
4715
+ if (!resolution.description || typeof resolution.description !== "string") {
4716
+ errors.push(`${prefix}: Missing resolution description`);
4717
+ }
4718
+ if (!resolution.strategy || typeof resolution.strategy !== "string") {
4719
+ errors.push(`${prefix}: Missing resolution strategy`);
4720
+ } else {
4721
+ const validStrategies = [
4722
+ "retry",
4723
+ "fallback",
4724
+ "fix-data",
4725
+ "fix-code",
4726
+ "ignore",
4727
+ "escalate"
4728
+ ];
4729
+ if (!validStrategies.includes(resolution.strategy)) {
4730
+ errors.push(`${prefix}: Invalid strategy "${resolution.strategy}"`);
4731
+ }
4732
+ }
4733
+ if (!resolution.priority || typeof resolution.priority !== "string") {
4734
+ warnings.push(`${prefix}: Missing priority, will default to medium`);
4735
+ } else {
4736
+ const validPriorities = ["low", "medium", "high", "critical"];
4737
+ if (!validPriorities.includes(resolution.priority)) {
4738
+ warnings.push(`${prefix}: Invalid priority "${resolution.priority}"`);
4739
+ }
4740
+ }
4741
+ }
4742
+ return { errors, warnings };
4743
+ }
4744
+ /**
4745
+ * Load patterns from a JSON file
4746
+ */
4747
+ loadFromFile(filePath) {
4748
+ if (!fs4.existsSync(filePath)) {
4749
+ throw new Error(`File not found: ${filePath}`);
4750
+ }
4751
+ const content = fs4.readFileSync(filePath, "utf-8");
4752
+ const data = JSON.parse(content);
4753
+ const validation = this.validate(data);
4754
+ if (!validation.valid) {
4755
+ throw new Error(`Invalid pattern file: ${validation.errors.join(", ")}`);
4756
+ }
4757
+ return this.normalizeExport(data);
4758
+ }
4759
+ /**
4760
+ * Load patterns from a URL
4761
+ */
4762
+ async loadFromUrl(url) {
4763
+ const response = await fetch(url);
4764
+ if (!response.ok) {
4765
+ throw new Error(`Failed to fetch patterns: ${response.statusText}`);
4766
+ }
4767
+ const data = await response.json();
4768
+ const validation = this.validate(data);
4769
+ if (!validation.valid) {
4770
+ throw new Error(`Invalid pattern data: ${validation.errors.join(", ")}`);
4771
+ }
4772
+ return this.normalizeExport(data);
4773
+ }
4774
+ /**
4775
+ * Normalize raw data to PatternExport
4776
+ */
4777
+ normalizeExport(data) {
4778
+ const patterns = data.patterns.map(
4779
+ (p) => this.normalizePattern(p)
4780
+ );
4781
+ return {
4782
+ version: data.version || "1.0.0",
4783
+ exportedAt: data.exportedAt || (/* @__PURE__ */ new Date()).toISOString(),
4784
+ patterns
4785
+ };
4786
+ }
4787
+ /**
4788
+ * Normalize a raw pattern object
4789
+ */
4790
+ normalizePattern(data) {
4791
+ const pattern = data.pattern;
4792
+ const resolution = data.resolution;
4793
+ const confidence = data.confidence || {};
4794
+ return {
4795
+ id: data.id,
4796
+ name: data.name,
4797
+ description: data.description || "",
4798
+ pattern: {
4799
+ symbols: pattern.symbols || {},
4800
+ errorContains: pattern.errorContains,
4801
+ errorMatches: pattern.errorMatches,
4802
+ errorType: pattern.errorType,
4803
+ missingSignals: pattern.missingSignals,
4804
+ environment: pattern.environment
4805
+ },
4806
+ resolution: {
4807
+ description: resolution.description,
4808
+ strategy: resolution.strategy,
4809
+ priority: resolution.priority || "medium",
4810
+ codeHint: resolution.codeHint,
4811
+ codeSnippet: resolution.codeSnippet,
4812
+ symbolsToModify: resolution.symbolsToModify,
4813
+ filesLikelyInvolved: resolution.filesLikelyInvolved,
4814
+ commitRef: resolution.commitRef,
4815
+ prRef: resolution.prRef,
4816
+ docsRef: resolution.docsRef
4817
+ },
4818
+ confidence: {
4819
+ score: confidence.score || 50,
4820
+ timesMatched: confidence.timesMatched || 0,
4821
+ timesResolved: confidence.timesResolved || 0,
4822
+ timesRecurred: confidence.timesRecurred || 0,
4823
+ avgTimeToResolve: confidence.avgTimeToResolve,
4824
+ lastMatched: confidence.lastMatched,
4825
+ lastResolved: confidence.lastResolved
4826
+ },
4827
+ source: data.source || "imported",
4828
+ private: Boolean(data.private),
4829
+ tags: data.tags || [],
4830
+ createdAt: data.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
4831
+ updatedAt: data.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
4832
+ };
4833
+ }
4834
+ /**
4835
+ * Merge patterns from multiple sources
4836
+ */
4837
+ mergePatterns(...exports) {
4838
+ const patternMap = /* @__PURE__ */ new Map();
4839
+ for (const exp of exports) {
4840
+ for (const pattern of exp.patterns) {
4841
+ patternMap.set(pattern.id, pattern);
4842
+ }
4843
+ }
4844
+ return {
4845
+ version: "1.0.0",
4846
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
4847
+ patterns: Array.from(patternMap.values())
4848
+ };
4849
+ }
4850
+ };
4851
+
4852
+ export {
4853
+ SentinelClient,
4854
+ createSentinelClient,
4855
+ SentinelTransport,
4856
+ createSentinelTransport,
4857
+ enableSentinel,
4858
+ SentinelStorage,
4859
+ DEFAULT_AUTH_CONFIG,
4860
+ DEFAULT_RATE_LIMIT_CONFIG,
4861
+ DEFAULT_SERVER_CONFIG,
4862
+ loadConfig,
4863
+ writeConfig,
4864
+ loadServerConfig,
4865
+ PatternMatcher,
4866
+ loadUniversalPatterns,
4867
+ loadParadigmPatterns,
4868
+ loadAllSeedPatterns,
4869
+ FlowTracker,
4870
+ Sentinel,
4871
+ detectSymbols,
4872
+ generateConfig,
4873
+ IncidentGrouper,
4874
+ TimelineBuilder,
4875
+ StatsCalculator,
4876
+ ContextEnricher,
4877
+ PatternSuggester,
4878
+ PatternImporter
4879
+ };