Kraft102 commited on
Commit
a9f0bb1
·
verified ·
1 Parent(s): a84b07b

Deploy from GitHub Actions 2025-12-15_17-15-49

Browse files
apps/backend/package.json CHANGED
@@ -10,6 +10,7 @@
10
  "build:tsc": "tsc",
11
  "start": "node dist/index.js",
12
  "test": "vitest run",
 
13
  "neural-bridge": "tsx src/mcp/servers/NeuralBridgeServer.ts",
14
  "neural-bridge:build": "tsc && node dist/mcp/servers/NeuralBridgeServer.js",
15
  "ingest-drive": "tsx src/scripts/ingest-drive.ts"
@@ -69,7 +70,6 @@
69
  "zod": "^3.25.76"
70
  },
71
  "devDependencies": {
72
- "esbuild": "^0.24.2",
73
  "@types/cors": "^2.8.17",
74
  "@types/express": "^4.17.21",
75
  "@types/imap": "^0.8.40",
@@ -84,7 +84,11 @@
84
  "@types/uuid": "^9.0.7",
85
  "@types/ws": "^8.5.10",
86
  "@types/xml2js": "^0.4.14",
 
 
 
 
87
  "tsx": "^4.20.6",
88
  "typescript": "~5.8.2"
89
  }
90
- }
 
10
  "build:tsc": "tsc",
11
  "start": "node dist/index.js",
12
  "test": "vitest run",
13
+ "lint": "eslint .",
14
  "neural-bridge": "tsx src/mcp/servers/NeuralBridgeServer.ts",
15
  "neural-bridge:build": "tsc && node dist/mcp/servers/NeuralBridgeServer.js",
16
  "ingest-drive": "tsx src/scripts/ingest-drive.ts"
 
70
  "zod": "^3.25.76"
71
  },
72
  "devDependencies": {
 
73
  "@types/cors": "^2.8.17",
74
  "@types/express": "^4.17.21",
75
  "@types/imap": "^0.8.40",
 
84
  "@types/uuid": "^9.0.7",
85
  "@types/ws": "^8.5.10",
86
  "@types/xml2js": "^0.4.14",
87
+ "@typescript-eslint/eslint-plugin": "^7.16.0",
88
+ "@typescript-eslint/parser": "^7.16.0",
89
+ "esbuild": "^0.24.2",
90
+ "eslint": "^8.57.0",
91
  "tsx": "^4.20.6",
92
  "typescript": "~5.8.2"
93
  }
94
+ }
apps/backend/src/mcp/cognitive/UnifiedMemorySystem.ts CHANGED
@@ -3,7 +3,9 @@
3
  // Integrates existing repositories (CMA, SRAG, PAL, Evolution, ProjectMemory)
4
 
5
  import { getCognitiveMemory, initCognitiveMemory, CognitiveMemory } from '../memory/CognitiveMemory.js';
6
- import { getDatabase } from '../../database/index.js';
 
 
7
  import { MemoryRepository } from '../../services/memory/memoryRepository.js';
8
  import { SragRepository } from '../../services/srag/sragRepository.js';
9
  import { PalRepository } from '../../services/pal/palRepository.js';
@@ -67,8 +69,15 @@ export class UnifiedMemorySystem {
67
 
68
  // New init method to be called after DB is ready
69
  public init() {
70
- const db = getDatabase();
71
- initCognitiveMemory(db);
 
 
 
 
 
 
 
72
  this.cognitive = getCognitiveMemory();
73
  this.proceduralMemory = new ProductionRuleEngine(this.cognitive);
74
  }
@@ -79,8 +88,8 @@ export class UnifiedMemorySystem {
79
  if (!this.workingMemory.has(key)) {
80
  const events = projectMemory.getLifecycleEvents(20);
81
  const features = projectMemory.getFeatures();
82
- this.workingMemory.set(key, {
83
- recentEvents: events,
84
  recentFeatures: features,
85
  widgetStates: {},
86
  userMood: { sentiment: 'neutral', arousal: 0.5, lastUpdated: Date.now() }
@@ -93,10 +102,10 @@ export class UnifiedMemorySystem {
93
  async updateWidgetState(ctx: McpContext, widgetId: string, state: any): Promise<void> {
94
  const wm = await this.getWorkingMemory(ctx);
95
  wm.widgetStates[widgetId] = { ...state, lastUpdated: Date.now() };
96
-
97
  // Trigger holographic analysis when state changes
98
  const patterns = await this.findHolographicPatterns(ctx);
99
-
100
  // Opdater adaptivt layout baseret på mønstre
101
  this.updateAdaptiveLayout(wm, patterns);
102
  }
@@ -107,7 +116,7 @@ export class UnifiedMemorySystem {
107
  const state = this.workingMemory.get(key);
108
  if (state) {
109
  state.recentEvents = [...(state.recentEvents || []), result];
110
-
111
  // Simuleret humør-analyse baseret på interaktion
112
  // Hvis resultatet er en fejl -> stress op
113
  if (result?.error) {
@@ -117,7 +126,7 @@ export class UnifiedMemorySystem {
117
  // Reset langsomt mod neutral
118
  state.userMood.arousal = Math.max(0.2, state.userMood.arousal - 0.05);
119
  }
120
-
121
  this.workingMemory.set(key, state);
122
  }
123
  }
@@ -125,14 +134,14 @@ export class UnifiedMemorySystem {
125
  /** Enrich an incoming MCPMessage with memory context */
126
  async enrichMCPRequest(message: any, ctx: McpContext): Promise<any> {
127
  const wm = await this.getWorkingMemory(ctx);
128
- return {
129
- ...message,
130
- memoryContext: {
131
- recentEvents: wm.recentEvents,
132
  recentFeatures: wm.recentFeatures,
133
  activeWidgets: wm.widgetStates,
134
  systemSuggestion: wm.suggestedLayout
135
- }
136
  };
137
  }
138
 
@@ -154,7 +163,7 @@ export class UnifiedMemorySystem {
154
  /** Opdater layout forslag baseret på mønstre og humør */
155
  private updateAdaptiveLayout(wm: WorkingMemoryState, patterns: any[]) {
156
  // 1. Tjek for kritiske mønstre (Sikkerhed)
157
- const securityPattern = patterns.find(p =>
158
  ['threat', 'attack', 'breach', 'password', 'alert'].includes(p.keyword) && p.frequency > 2
159
  );
160
 
@@ -265,7 +274,7 @@ export class UnifiedMemorySystem {
265
  private async componentHealth(component: string): Promise<ComponentHealth> {
266
  try {
267
  if (!this.cognitive || !this.cognitive.getSourceHealth) {
268
- return {
269
  name: component,
270
  healthScore: 0.8, // Default optimistic
271
  latency: 0,
 
3
  // Integrates existing repositories (CMA, SRAG, PAL, Evolution, ProjectMemory)
4
 
5
  import { getCognitiveMemory, initCognitiveMemory, CognitiveMemory } from '../memory/CognitiveMemory.js';
6
+ import { getDatabase, getSqlJsDatabase } from '../../database/index.js';
7
+ import { getDatabaseAdapter } from '../../platform/db/PrismaDatabaseAdapter.js';
8
+ import { PostgresStorageAdapter } from '../memory/StorageAdapter.js';
9
  import { MemoryRepository } from '../../services/memory/memoryRepository.js';
10
  import { SragRepository } from '../../services/srag/sragRepository.js';
11
  import { PalRepository } from '../../services/pal/palRepository.js';
 
69
 
70
  // New init method to be called after DB is ready
71
  public init() {
72
+ const dbAdapter = getDatabaseAdapter();
73
+ if (dbAdapter.isAvailable()) {
74
+ initCognitiveMemory(new PostgresStorageAdapter(dbAdapter));
75
+ } else {
76
+ const db = getSqlJsDatabase();
77
+ // Note: getSqlJsDatabase returns the raw sql.js instance needed for .exec()
78
+ // If it returns null, CognitiveMemory handles it (memory-only mode)
79
+ initCognitiveMemory(db);
80
+ }
81
  this.cognitive = getCognitiveMemory();
82
  this.proceduralMemory = new ProductionRuleEngine(this.cognitive);
83
  }
 
88
  if (!this.workingMemory.has(key)) {
89
  const events = projectMemory.getLifecycleEvents(20);
90
  const features = projectMemory.getFeatures();
91
+ this.workingMemory.set(key, {
92
+ recentEvents: events,
93
  recentFeatures: features,
94
  widgetStates: {},
95
  userMood: { sentiment: 'neutral', arousal: 0.5, lastUpdated: Date.now() }
 
102
  async updateWidgetState(ctx: McpContext, widgetId: string, state: any): Promise<void> {
103
  const wm = await this.getWorkingMemory(ctx);
104
  wm.widgetStates[widgetId] = { ...state, lastUpdated: Date.now() };
105
+
106
  // Trigger holographic analysis when state changes
107
  const patterns = await this.findHolographicPatterns(ctx);
108
+
109
  // Opdater adaptivt layout baseret på mønstre
110
  this.updateAdaptiveLayout(wm, patterns);
111
  }
 
116
  const state = this.workingMemory.get(key);
117
  if (state) {
118
  state.recentEvents = [...(state.recentEvents || []), result];
119
+
120
  // Simuleret humør-analyse baseret på interaktion
121
  // Hvis resultatet er en fejl -> stress op
122
  if (result?.error) {
 
126
  // Reset langsomt mod neutral
127
  state.userMood.arousal = Math.max(0.2, state.userMood.arousal - 0.05);
128
  }
129
+
130
  this.workingMemory.set(key, state);
131
  }
132
  }
 
134
  /** Enrich an incoming MCPMessage with memory context */
135
  async enrichMCPRequest(message: any, ctx: McpContext): Promise<any> {
136
  const wm = await this.getWorkingMemory(ctx);
137
+ return {
138
+ ...message,
139
+ memoryContext: {
140
+ recentEvents: wm.recentEvents,
141
  recentFeatures: wm.recentFeatures,
142
  activeWidgets: wm.widgetStates,
143
  systemSuggestion: wm.suggestedLayout
144
+ }
145
  };
146
  }
147
 
 
163
  /** Opdater layout forslag baseret på mønstre og humør */
164
  private updateAdaptiveLayout(wm: WorkingMemoryState, patterns: any[]) {
165
  // 1. Tjek for kritiske mønstre (Sikkerhed)
166
+ const securityPattern = patterns.find(p =>
167
  ['threat', 'attack', 'breach', 'password', 'alert'].includes(p.keyword) && p.frequency > 2
168
  );
169
 
 
274
  private async componentHealth(component: string): Promise<ComponentHealth> {
275
  try {
276
  if (!this.cognitive || !this.cognitive.getSourceHealth) {
277
+ return {
278
  name: component,
279
  healthScore: 0.8, // Default optimistic
280
  latency: 0,
apps/backend/src/mcp/memory/CognitiveMemory.ts CHANGED
@@ -15,10 +15,11 @@
15
  * ╚══════════════════════════════════════════════════════════════════════════════╝
16
  */
17
 
 
 
18
  import type { Database } from 'sql.js';
19
  import { PatternMemory, UsagePattern, PatternStatistics } from './PatternMemory.js';
20
  import { FailureMemory, Failure, RecoveryPath, FailureStatistics, SourceHealthSummary } from './FailureMemory.js';
21
- import { queryAll, execute, queryOne } from './SqlJsCompat.js';
22
  import { logger } from '../../utils/logger.js';
23
  import { eventBus, type EventType } from '../EventBus.js';
24
 
@@ -63,7 +64,7 @@ export interface CognitiveStats {
63
  failures: FailureStatistics;
64
  healthRecords: number;
65
  initialized: boolean;
66
- persistenceMode: 'sqlite' | 'memory';
67
  }
68
 
69
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -74,19 +75,28 @@ export class CognitiveMemory {
74
  public readonly patternMemory: PatternMemory;
75
  public readonly failureMemory: FailureMemory;
76
 
77
- private db: Database | null = null;
78
  private healthHistory: Map<string, HealthMetrics[]> = new Map();
79
  private readonly MAX_HEALTH_RECORDS = 1000;
80
  private initialized: boolean = false;
81
- private persistenceMode: 'sqlite' | 'memory' = 'memory';
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- constructor(database?: Database) {
84
- this.patternMemory = new PatternMemory(database);
85
- this.failureMemory = new FailureMemory(database);
86
 
87
- if (database) {
88
- this.db = database;
89
- this.persistenceMode = 'sqlite';
90
  this.initializeHealthTable();
91
  }
92
 
@@ -98,11 +108,14 @@ export class CognitiveMemory {
98
  /**
99
  * Initialize health metrics table for persistence
100
  */
101
- private initializeHealthTable(): void {
102
- if (!this.db) return;
103
 
104
  try {
105
- this.db.exec(`
 
 
 
106
  CREATE TABLE IF NOT EXISTS mcp_source_health (
107
  id TEXT PRIMARY KEY,
108
  source_name TEXT NOT NULL,
@@ -113,11 +126,11 @@ export class CognitiveMemory {
113
  success_rate REAL NOT NULL,
114
  request_count INTEGER NOT NULL,
115
  error_count INTEGER NOT NULL,
116
- timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
117
  )
118
  `);
119
 
120
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_source_health_source
121
  ON mcp_source_health(source_name, timestamp DESC)`);
122
 
123
  logger.debug('📊 Health metrics table initialized');
@@ -303,10 +316,10 @@ export class CognitiveMemory {
303
  }
304
 
305
  // Persist to database
306
- if (this.db) {
307
  try {
308
  const id = `${sourceName}-${Date.now()}`;
309
- execute(this.db, `
310
  INSERT INTO mcp_source_health
311
  (id, source_name, health_score, latency_p50, latency_p95, latency_p99,
312
  success_rate, request_count, error_count, timestamp)
@@ -340,9 +353,9 @@ export class CognitiveMemory {
340
  limit: number = 100
341
  ): Promise<HealthMetrics[]> {
342
  // Try database first
343
- if (this.db) {
344
  try {
345
- const rows = queryAll<{
346
  source_name: string;
347
  health_score: number;
348
  latency_p50: number;
@@ -352,7 +365,7 @@ export class CognitiveMemory {
352
  request_count: number;
353
  error_count: number;
354
  timestamp: string;
355
- }>(this.db, `
356
  SELECT * FROM mcp_source_health
357
  WHERE source_name = ?
358
  ORDER BY timestamp DESC
@@ -513,7 +526,7 @@ export class CognitiveMemory {
513
  * Clean old data (maintenance)
514
  */
515
  async cleanup(retentionDays: number = 30): Promise<void> {
516
- if (!this.db) {
517
  logger.debug('🧹 Cognitive memory cleanup (in-memory - automatic via limits)');
518
  return;
519
  }
@@ -522,12 +535,10 @@ export class CognitiveMemory {
522
  const cutoffDate = new Date();
523
  cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
524
 
525
- const stmt = this.db.prepare(`
526
  DELETE FROM mcp_source_health
527
  WHERE timestamp < ?
528
- `);
529
- stmt.run([cutoffDate.toISOString()]);
530
- stmt.free();
531
 
532
  logger.info(`🧹 Cleaned health records older than ${retentionDays} days`);
533
  } catch (error) {
@@ -542,12 +553,12 @@ export class CognitiveMemory {
542
 
543
  let instance: CognitiveMemory | null = null;
544
 
545
- export function initCognitiveMemory(database?: Database): CognitiveMemory {
546
  if (!instance) {
547
- instance = new CognitiveMemory(database);
548
- } else if (database && !instance['db']) {
549
- // Upgrade to database-backed if provided later
550
- instance = new CognitiveMemory(database);
551
  }
552
  return instance;
553
  }
 
15
  * ╚══════════════════════════════════════════════════════════════════════════════╝
16
  */
17
 
18
+ import type { StorageAdapter } from './StorageAdapter.js';
19
+ import { SqlJsStorageAdapter } from './StorageAdapter.js';
20
  import type { Database } from 'sql.js';
21
  import { PatternMemory, UsagePattern, PatternStatistics } from './PatternMemory.js';
22
  import { FailureMemory, Failure, RecoveryPath, FailureStatistics, SourceHealthSummary } from './FailureMemory.js';
 
23
  import { logger } from '../../utils/logger.js';
24
  import { eventBus, type EventType } from '../EventBus.js';
25
 
 
64
  failures: FailureStatistics;
65
  healthRecords: number;
66
  initialized: boolean;
67
+ persistenceMode: 'sqlite' | 'postgres' | 'memory';
68
  }
69
 
70
  // ═══════════════════════════════════════════════════════════════════════════════
 
75
  public readonly patternMemory: PatternMemory;
76
  public readonly failureMemory: FailureMemory;
77
 
78
+ private storage: StorageAdapter | null = null;
79
  private healthHistory: Map<string, HealthMetrics[]> = new Map();
80
  private readonly MAX_HEALTH_RECORDS = 1000;
81
  private initialized: boolean = false;
82
+ private persistenceMode: 'sqlite' | 'postgres' | 'memory' = 'memory';
83
+
84
+ constructor(storageOrDb?: StorageAdapter | Database) {
85
+ if (storageOrDb) {
86
+ if ('exec' in storageOrDb || 'run' in storageOrDb) {
87
+ // It's a Database
88
+ this.storage = new SqlJsStorageAdapter(storageOrDb as Database);
89
+ } else {
90
+ // It's a StorageAdapter
91
+ this.storage = storageOrDb as StorageAdapter;
92
+ }
93
+ this.persistenceMode = this.storage.mode;
94
+ }
95
 
96
+ this.patternMemory = new PatternMemory(this.storage || undefined);
97
+ this.failureMemory = new FailureMemory(this.storage || undefined);
 
98
 
99
+ if (this.storage) {
 
 
100
  this.initializeHealthTable();
101
  }
102
 
 
108
  /**
109
  * Initialize health metrics table for persistence
110
  */
111
+ private async initializeHealthTable(): Promise<void> {
112
+ if (!this.storage) return;
113
 
114
  try {
115
+ const isPostgres = this.storage.mode === 'postgres';
116
+ const timestampType = isPostgres ? 'TIMESTAMP WITH TIME ZONE' : 'DATETIME';
117
+
118
+ await this.storage.execute(`
119
  CREATE TABLE IF NOT EXISTS mcp_source_health (
120
  id TEXT PRIMARY KEY,
121
  source_name TEXT NOT NULL,
 
126
  success_rate REAL NOT NULL,
127
  request_count INTEGER NOT NULL,
128
  error_count INTEGER NOT NULL,
129
+ timestamp ${timestampType} NOT NULL DEFAULT CURRENT_TIMESTAMP
130
  )
131
  `);
132
 
133
+ await this.storage.execute(`CREATE INDEX IF NOT EXISTS idx_source_health_source
134
  ON mcp_source_health(source_name, timestamp DESC)`);
135
 
136
  logger.debug('📊 Health metrics table initialized');
 
316
  }
317
 
318
  // Persist to database
319
+ if (this.storage) {
320
  try {
321
  const id = `${sourceName}-${Date.now()}`;
322
+ await this.storage.execute(`
323
  INSERT INTO mcp_source_health
324
  (id, source_name, health_score, latency_p50, latency_p95, latency_p99,
325
  success_rate, request_count, error_count, timestamp)
 
353
  limit: number = 100
354
  ): Promise<HealthMetrics[]> {
355
  // Try database first
356
+ if (this.storage) {
357
  try {
358
+ const rows = await this.storage.queryAll<{
359
  source_name: string;
360
  health_score: number;
361
  latency_p50: number;
 
365
  request_count: number;
366
  error_count: number;
367
  timestamp: string;
368
+ }>(`
369
  SELECT * FROM mcp_source_health
370
  WHERE source_name = ?
371
  ORDER BY timestamp DESC
 
526
  * Clean old data (maintenance)
527
  */
528
  async cleanup(retentionDays: number = 30): Promise<void> {
529
+ if (!this.storage) {
530
  logger.debug('🧹 Cognitive memory cleanup (in-memory - automatic via limits)');
531
  return;
532
  }
 
535
  const cutoffDate = new Date();
536
  cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
537
 
538
+ await this.storage.execute(`
539
  DELETE FROM mcp_source_health
540
  WHERE timestamp < ?
541
+ `, [cutoffDate.toISOString()]);
 
 
542
 
543
  logger.info(`🧹 Cleaned health records older than ${retentionDays} days`);
544
  } catch (error) {
 
553
 
554
  let instance: CognitiveMemory | null = null;
555
 
556
+ export function initCognitiveMemory(storageOrDb?: StorageAdapter | Database): CognitiveMemory {
557
  if (!instance) {
558
+ instance = new CognitiveMemory(storageOrDb);
559
+ } else if (storageOrDb && !instance['storage']) {
560
+ // Upgrade to persistent storage if provided later
561
+ instance = new CognitiveMemory(storageOrDb);
562
  }
563
  return instance;
564
  }
apps/backend/src/mcp/memory/FailureMemory.ts CHANGED
@@ -14,10 +14,9 @@
14
  */
15
 
16
  import { v4 as uuidv4 } from 'uuid';
17
- import type { Database } from 'sql.js';
18
  import { logger } from '../../utils/logger.js';
19
  import { eventBus, type EventType } from '../EventBus.js';
20
- import { queryAll, queryOne, queryScalar, execute, batchInsert } from './SqlJsCompat.js';
21
 
22
  // ═══════════════════════════════════════════════════════════════════════════════
23
  // INTERFACES
@@ -73,7 +72,7 @@ export interface SourceHealthSummary {
73
  // ═══════════════════════════════════════════════════════════════════════════════
74
 
75
  export class FailureMemory {
76
- private db: Database | null = null;
77
  private initialized: boolean = false;
78
  private writeQueue: Failure[] = [];
79
  private flushInterval: ReturnType<typeof setInterval> | null = null;
@@ -84,30 +83,33 @@ export class FailureMemory {
84
  private readonly FLUSH_INTERVAL_MS = 3000; // Faster flush for failures
85
  private readonly MAX_DB_FAILURES = 50000;
86
 
87
- constructor(database?: Database) {
88
- if (database) {
89
- this.db = database;
90
  this.initialize();
91
  }
92
  }
93
 
94
  /**
95
- * Initialize with database connection
96
  */
97
- public setDatabase(database: Database): void {
98
- this.db = database;
99
  this.initialize();
100
  }
101
 
102
  /**
103
  * Initialize the service
104
  */
105
- private initialize(): void {
106
- if (this.initialized || !this.db) return;
107
 
108
  try {
 
 
 
109
  // Ensure table exists - use exec for DDL statements (more compatible)
110
- this.db.exec(`
111
  CREATE TABLE IF NOT EXISTS mcp_failure_memory (
112
  id TEXT PRIMARY KEY,
113
  source_name TEXT NOT NULL,
@@ -118,26 +120,26 @@ export class FailureMemory {
118
  recovery_action TEXT,
119
  recovery_success BOOLEAN,
120
  recovery_time_ms INTEGER,
121
- occurred_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
122
  )
123
  `);
124
 
125
  // Create indexes
126
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_failure_memory_source
127
  ON mcp_failure_memory(source_name, occurred_at DESC)`);
128
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_failure_memory_error
129
  ON mcp_failure_memory(error_type, occurred_at DESC)`);
130
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_failure_memory_recovery
131
  ON mcp_failure_memory(recovery_action, recovery_success)`);
132
 
133
  // Load recent failures into cache
134
- this.loadCacheFromDb();
135
 
136
  // Start background flush
137
  this.flushInterval = setInterval(() => this.flushWriteQueue(), this.FLUSH_INTERVAL_MS);
138
 
139
  this.initialized = true;
140
- logger.info('🛡️ FailureMemory initialized with SQLite persistence');
141
  } catch (error) {
142
  logger.error('❌ FailureMemory initialization failed:', error);
143
  this.initialized = true;
@@ -147,11 +149,11 @@ export class FailureMemory {
147
  /**
148
  * Load recent failures from database into cache
149
  */
150
- private loadCacheFromDb(): void {
151
- if (!this.db) return;
152
 
153
  try {
154
- const rows = queryAll(this.db, `
155
  SELECT * FROM mcp_failure_memory
156
  ORDER BY occurred_at DESC
157
  LIMIT ?
@@ -263,9 +265,10 @@ export class FailureMemory {
263
  }
264
 
265
  // Update database
266
- if (this.db) {
 
267
  try {
268
- execute(this.db, `
269
  UPDATE mcp_failure_memory
270
  SET recovery_action = ?, recovery_success = ?, recovery_time_ms = ?
271
  WHERE id = ?
@@ -286,8 +289,8 @@ export class FailureMemory {
286
  /**
287
  * Flush write queue to database
288
  */
289
- private flushWriteQueue(): void {
290
- if (!this.db || this.writeQueue.length === 0) return;
291
 
292
  const failures = [...this.writeQueue];
293
  this.writeQueue = [];
@@ -306,16 +309,16 @@ export class FailureMemory {
306
  JSON.stringify(f.errorContext),
307
  JSON.stringify(f.queryContext),
308
  f.recoveryAction || null,
309
- f.recoverySuccess != null ? (f.recoverySuccess ? 1 : 0) : null,
310
  f.recoveryTimeMs || null,
311
  f.occurredAt.toISOString()
312
  ]);
313
 
314
- batchInsert(this.db, 'mcp_failure_memory', columns, rows);
315
 
316
  // Cleanup old failures periodically
317
  if (Math.random() < 0.02) { // 2% chance per flush
318
- this.cleanupOldFailures();
319
  }
320
 
321
  } catch (error) {
@@ -327,14 +330,14 @@ export class FailureMemory {
327
  /**
328
  * Cleanup old failures
329
  */
330
- private cleanupOldFailures(): void {
331
- if (!this.db) return;
332
 
333
  try {
334
  const ninetyDaysAgo = new Date();
335
  ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
336
 
337
- execute(this.db, `
338
  DELETE FROM mcp_failure_memory
339
  WHERE occurred_at < ?
340
  AND id NOT IN (
@@ -357,9 +360,9 @@ export class FailureMemory {
357
  sourceName: string,
358
  limit: number = 50
359
  ): Promise<Failure[]> {
360
- if (this.db) {
361
  try {
362
- const rows = queryAll(this.db, `
363
  SELECT * FROM mcp_failure_memory
364
  WHERE source_name = ?
365
  ORDER BY occurred_at DESC
@@ -387,15 +390,15 @@ export class FailureMemory {
387
  sourceName: string,
388
  errorType: string
389
  ): Promise<RecoveryPath[]> {
390
- if (this.db) {
391
  try {
392
- const rows = queryAll<{
393
  recovery_action: string;
394
  total: number;
395
  successes: number;
396
  avg_time: number;
397
  last_success: string | null;
398
- }>(this.db, `
399
  SELECT
400
  recovery_action,
401
  COUNT(*) as total,
@@ -487,9 +490,9 @@ export class FailureMemory {
487
  const cutoff = new Date();
488
  cutoff.setMinutes(cutoff.getMinutes() - withinMinutes);
489
 
490
- if (this.db) {
491
  try {
492
- const count = queryScalar<number>(this.db, `
493
  SELECT COUNT(*)
494
  FROM mcp_failure_memory
495
  WHERE source_name = ? AND error_type = ? AND occurred_at > ?
@@ -519,9 +522,9 @@ export class FailureMemory {
519
 
520
  let failures: Failure[] = [];
521
 
522
- if (this.db) {
523
  try {
524
- const rows = queryAll(this.db, `
525
  SELECT * FROM mcp_failure_memory
526
  WHERE source_name = ? AND occurred_at > ?
527
  ORDER BY occurred_at DESC
@@ -599,36 +602,36 @@ export class FailureMemory {
599
  const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
600
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
601
 
602
- if (this.db) {
603
  try {
604
- const total = queryScalar<number>(this.db, 'SELECT COUNT(*) FROM mcp_failure_memory') || 0;
605
- const uniqueErrors = queryScalar<number>(this.db, 'SELECT COUNT(DISTINCT error_type) FROM mcp_failure_memory') || 0;
606
- const uniqueSources = queryScalar<number>(this.db, 'SELECT COUNT(DISTINCT source_name) FROM mcp_failure_memory') || 0;
607
 
608
- const recoveryRate = queryScalar<number>(this.db, `
609
  SELECT AVG(CAST(recovery_success AS FLOAT))
610
  FROM mcp_failure_memory
611
  WHERE recovery_action IS NOT NULL
612
  `) || 0;
613
 
614
- const avgRecoveryTime = queryScalar<number>(this.db, `
615
  SELECT AVG(recovery_time_ms)
616
  FROM mcp_failure_memory
617
  WHERE recovery_success = 1
618
  `) || 0;
619
 
620
- const last24h = queryScalar<number>(this.db,
621
  'SELECT COUNT(*) FROM mcp_failure_memory WHERE occurred_at > ?',
622
  [oneDayAgo.toISOString()]
623
  ) || 0;
624
 
625
- const last7d = queryScalar<number>(this.db,
626
  'SELECT COUNT(*) FROM mcp_failure_memory WHERE occurred_at > ?',
627
  [sevenDaysAgo.toISOString()]
628
  ) || 0;
629
 
630
  // Top error types
631
- const topErrorRows = queryAll<{ error_type: string; count: number; recovery_rate: number }>(this.db, `
632
  SELECT
633
  error_type,
634
  COUNT(*) as count,
@@ -645,7 +648,7 @@ export class FailureMemory {
645
  }));
646
 
647
  // Top failing sources
648
- const topSourceRows = queryAll<{ source_name: string; count: number }>(this.db, `
649
  SELECT source_name, COUNT(*) as count
650
  FROM mcp_failure_memory
651
  GROUP BY source_name
@@ -658,12 +661,12 @@ export class FailureMemory {
658
  }));
659
 
660
  // Recent recoveries
661
- const recentRecoveryRows = queryAll<{
662
  recovery_action: string;
663
  recovery_success: number;
664
  recovery_time_ms: number;
665
  occurred_at: string;
666
- }>(this.db, `
667
  SELECT recovery_action, recovery_success, recovery_time_ms, occurred_at
668
  FROM mcp_failure_memory
669
  WHERE recovery_action IS NOT NULL
@@ -713,7 +716,7 @@ export class FailureMemory {
713
  * Force flush all pending writes
714
  */
715
  async flush(): Promise<void> {
716
- this.flushWriteQueue();
717
  }
718
 
719
  /**
 
14
  */
15
 
16
  import { v4 as uuidv4 } from 'uuid';
17
+ import type { StorageAdapter } from './StorageAdapter.js';
18
  import { logger } from '../../utils/logger.js';
19
  import { eventBus, type EventType } from '../EventBus.js';
 
20
 
21
  // ═══════════════════════════════════════════════════════════════════════════════
22
  // INTERFACES
 
72
  // ═══════════════════════════════════════════════════════════════════════════════
73
 
74
  export class FailureMemory {
75
+ private storage: StorageAdapter | null = null;
76
  private initialized: boolean = false;
77
  private writeQueue: Failure[] = [];
78
  private flushInterval: ReturnType<typeof setInterval> | null = null;
 
83
  private readonly FLUSH_INTERVAL_MS = 3000; // Faster flush for failures
84
  private readonly MAX_DB_FAILURES = 50000;
85
 
86
+ constructor(storage?: StorageAdapter) {
87
+ if (storage) {
88
+ this.storage = storage;
89
  this.initialize();
90
  }
91
  }
92
 
93
  /**
94
+ * Initialize with storage adapter
95
  */
96
+ public setStorage(storage: StorageAdapter): void {
97
+ this.storage = storage;
98
  this.initialize();
99
  }
100
 
101
  /**
102
  * Initialize the service
103
  */
104
+ private async initialize(): Promise<void> {
105
+ if (this.initialized || !this.storage) return;
106
 
107
  try {
108
+ const isPostgres = this.storage.mode === 'postgres';
109
+ const timestampType = isPostgres ? 'TIMESTAMP WITH TIME ZONE' : 'DATETIME';
110
+
111
  // Ensure table exists - use exec for DDL statements (more compatible)
112
+ await this.storage.execute(`
113
  CREATE TABLE IF NOT EXISTS mcp_failure_memory (
114
  id TEXT PRIMARY KEY,
115
  source_name TEXT NOT NULL,
 
120
  recovery_action TEXT,
121
  recovery_success BOOLEAN,
122
  recovery_time_ms INTEGER,
123
+ occurred_at ${timestampType} NOT NULL DEFAULT CURRENT_TIMESTAMP
124
  )
125
  `);
126
 
127
  // Create indexes
128
+ await this.storage.execute(`CREATE INDEX IF NOT EXISTS idx_failure_memory_source
129
  ON mcp_failure_memory(source_name, occurred_at DESC)`);
130
+ await this.storage.execute(`CREATE INDEX IF NOT EXISTS idx_failure_memory_error
131
  ON mcp_failure_memory(error_type, occurred_at DESC)`);
132
+ await this.storage.execute(`CREATE INDEX IF NOT EXISTS idx_failure_memory_recovery
133
  ON mcp_failure_memory(recovery_action, recovery_success)`);
134
 
135
  // Load recent failures into cache
136
+ await this.loadCacheFromDb();
137
 
138
  // Start background flush
139
  this.flushInterval = setInterval(() => this.flushWriteQueue(), this.FLUSH_INTERVAL_MS);
140
 
141
  this.initialized = true;
142
+ logger.info(`🛡️ FailureMemory initialized with ${this.storage.mode} persistence`);
143
  } catch (error) {
144
  logger.error('❌ FailureMemory initialization failed:', error);
145
  this.initialized = true;
 
149
  /**
150
  * Load recent failures from database into cache
151
  */
152
+ private async loadCacheFromDb(): Promise<void> {
153
+ if (!this.storage) return;
154
 
155
  try {
156
+ const rows = await this.storage.queryAll(`
157
  SELECT * FROM mcp_failure_memory
158
  ORDER BY occurred_at DESC
159
  LIMIT ?
 
265
  }
266
 
267
  // Update database
268
+ // Update database
269
+ if (this.storage) {
270
  try {
271
+ await this.storage.execute(`
272
  UPDATE mcp_failure_memory
273
  SET recovery_action = ?, recovery_success = ?, recovery_time_ms = ?
274
  WHERE id = ?
 
289
  /**
290
  * Flush write queue to database
291
  */
292
+ private async flushWriteQueue(): Promise<void> {
293
+ if (!this.storage || this.writeQueue.length === 0) return;
294
 
295
  const failures = [...this.writeQueue];
296
  this.writeQueue = [];
 
309
  JSON.stringify(f.errorContext),
310
  JSON.stringify(f.queryContext),
311
  f.recoveryAction || null,
312
+ f.recoverySuccess != null ? f.recoverySuccess : null, // Use boolean directly
313
  f.recoveryTimeMs || null,
314
  f.occurredAt.toISOString()
315
  ]);
316
 
317
+ await this.storage.batchInsert('mcp_failure_memory', columns, rows);
318
 
319
  // Cleanup old failures periodically
320
  if (Math.random() < 0.02) { // 2% chance per flush
321
+ this.cleanupOldFailures(); // Background
322
  }
323
 
324
  } catch (error) {
 
330
  /**
331
  * Cleanup old failures
332
  */
333
+ private async cleanupOldFailures(): Promise<void> {
334
+ if (!this.storage) return;
335
 
336
  try {
337
  const ninetyDaysAgo = new Date();
338
  ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
339
 
340
+ await this.storage.execute(`
341
  DELETE FROM mcp_failure_memory
342
  WHERE occurred_at < ?
343
  AND id NOT IN (
 
360
  sourceName: string,
361
  limit: number = 50
362
  ): Promise<Failure[]> {
363
+ if (this.storage) {
364
  try {
365
+ const rows = await this.storage.queryAll(`
366
  SELECT * FROM mcp_failure_memory
367
  WHERE source_name = ?
368
  ORDER BY occurred_at DESC
 
390
  sourceName: string,
391
  errorType: string
392
  ): Promise<RecoveryPath[]> {
393
+ if (this.storage) {
394
  try {
395
+ const rows = await this.storage.queryAll<{
396
  recovery_action: string;
397
  total: number;
398
  successes: number;
399
  avg_time: number;
400
  last_success: string | null;
401
+ }>(`
402
  SELECT
403
  recovery_action,
404
  COUNT(*) as total,
 
490
  const cutoff = new Date();
491
  cutoff.setMinutes(cutoff.getMinutes() - withinMinutes);
492
 
493
+ if (this.storage) {
494
  try {
495
+ const count = await this.storage.queryScalar<number>(`
496
  SELECT COUNT(*)
497
  FROM mcp_failure_memory
498
  WHERE source_name = ? AND error_type = ? AND occurred_at > ?
 
522
 
523
  let failures: Failure[] = [];
524
 
525
+ if (this.storage) {
526
  try {
527
+ const rows = await this.storage.queryAll(`
528
  SELECT * FROM mcp_failure_memory
529
  WHERE source_name = ? AND occurred_at > ?
530
  ORDER BY occurred_at DESC
 
602
  const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
603
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
604
 
605
+ if (this.storage) {
606
  try {
607
+ const total = await this.storage.queryScalar<number>('SELECT COUNT(*) FROM mcp_failure_memory') || 0;
608
+ const uniqueErrors = await this.storage.queryScalar<number>('SELECT COUNT(DISTINCT error_type) FROM mcp_failure_memory') || 0;
609
+ const uniqueSources = await this.storage.queryScalar<number>('SELECT COUNT(DISTINCT source_name) FROM mcp_failure_memory') || 0;
610
 
611
+ const recoveryRate = await this.storage.queryScalar<number>(`
612
  SELECT AVG(CAST(recovery_success AS FLOAT))
613
  FROM mcp_failure_memory
614
  WHERE recovery_action IS NOT NULL
615
  `) || 0;
616
 
617
+ const avgRecoveryTime = await this.storage.queryScalar<number>(`
618
  SELECT AVG(recovery_time_ms)
619
  FROM mcp_failure_memory
620
  WHERE recovery_success = 1
621
  `) || 0;
622
 
623
+ const last24h = await this.storage.queryScalar<number>(
624
  'SELECT COUNT(*) FROM mcp_failure_memory WHERE occurred_at > ?',
625
  [oneDayAgo.toISOString()]
626
  ) || 0;
627
 
628
+ const last7d = await this.storage.queryScalar<number>(
629
  'SELECT COUNT(*) FROM mcp_failure_memory WHERE occurred_at > ?',
630
  [sevenDaysAgo.toISOString()]
631
  ) || 0;
632
 
633
  // Top error types
634
+ const topErrorRows = await this.storage.queryAll<{ error_type: string; count: number; recovery_rate: number }>(`
635
  SELECT
636
  error_type,
637
  COUNT(*) as count,
 
648
  }));
649
 
650
  // Top failing sources
651
+ const topSourceRows = await this.storage.queryAll<{ source_name: string; count: number }>(`
652
  SELECT source_name, COUNT(*) as count
653
  FROM mcp_failure_memory
654
  GROUP BY source_name
 
661
  }));
662
 
663
  // Recent recoveries
664
+ const recentRecoveryRows = await this.storage.queryAll<{
665
  recovery_action: string;
666
  recovery_success: number;
667
  recovery_time_ms: number;
668
  occurred_at: string;
669
+ }>(`
670
  SELECT recovery_action, recovery_success, recovery_time_ms, occurred_at
671
  FROM mcp_failure_memory
672
  WHERE recovery_action IS NOT NULL
 
716
  * Force flush all pending writes
717
  */
718
  async flush(): Promise<void> {
719
+ await this.flushWriteQueue();
720
  }
721
 
722
  /**
apps/backend/src/mcp/memory/PatternMemory.ts CHANGED
@@ -15,10 +15,9 @@
15
 
16
  import { v4 as uuidv4 } from 'uuid';
17
  import crypto from 'crypto';
18
- import type { Database } from 'sql.js';
19
  import { logger } from '../../utils/logger.js';
20
  import { eventBus, type EventType } from '../EventBus.js';
21
- import { queryAll, queryOne, queryScalar, execute, batchInsert } from './SqlJsCompat.js';
22
 
23
  // ═══════════════════════════════════════════════════════════════════════════════
24
  // INTERFACES
@@ -76,7 +75,7 @@ export interface PatternStatistics {
76
  // ═══════════════════════════════════════════════════════════════════════════════
77
 
78
  export class PatternMemory {
79
- private db: Database | null = null;
80
  private initialized: boolean = false;
81
  private writeQueue: QueryPattern[] = [];
82
  private flushInterval: ReturnType<typeof setInterval> | null = null;
@@ -87,30 +86,34 @@ export class PatternMemory {
87
  private readonly FLUSH_INTERVAL_MS = 5000; // Batch writes every 5 seconds
88
  private readonly MAX_DB_PATTERNS = 100000;
89
 
90
- constructor(database?: Database) {
91
- if (database) {
92
- this.db = database;
93
  this.initialize();
94
  }
95
  }
96
 
97
  /**
98
- * Initialize with database connection
99
  */
100
- public setDatabase(database: Database): void {
101
- this.db = database;
102
  this.initialize();
103
  }
104
 
105
  /**
106
  * Initialize the service
107
  */
108
- private initialize(): void {
109
- if (this.initialized || !this.db) return;
110
 
111
  try {
112
- // Ensure table exists - use exec for DDL statements (more compatible)
113
- this.db.exec(`
 
 
 
 
114
  CREATE TABLE IF NOT EXISTS mcp_query_patterns (
115
  id TEXT PRIMARY KEY,
116
  widget_id TEXT NOT NULL,
@@ -121,26 +124,26 @@ export class PatternMemory {
121
  result_size INTEGER,
122
  success BOOLEAN NOT NULL,
123
  user_context TEXT,
124
- timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
125
  )
126
  `);
127
 
128
  // Create indexes for fast queries
129
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_query_patterns_widget
130
  ON mcp_query_patterns(widget_id, timestamp DESC)`);
131
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_query_patterns_signature
132
  ON mcp_query_patterns(query_signature)`);
133
- this.db.exec(`CREATE INDEX IF NOT EXISTS idx_query_patterns_source
134
  ON mcp_query_patterns(source_used, timestamp DESC)`);
135
 
136
  // Load recent patterns into cache
137
- this.loadCacheFromDb();
138
 
139
  // Start background flush
140
  this.flushInterval = setInterval(() => this.flushWriteQueue(), this.FLUSH_INTERVAL_MS);
141
 
142
  this.initialized = true;
143
- logger.info('🧠 PatternMemory initialized with SQLite persistence');
144
  } catch (error) {
145
  logger.error('❌ PatternMemory initialization failed:', error);
146
  // Fall back to in-memory only
@@ -151,11 +154,12 @@ export class PatternMemory {
151
  /**
152
  * Load recent patterns from database into cache
153
  */
154
- private loadCacheFromDb(): void {
155
- if (!this.db) return;
 
156
 
157
  try {
158
- const rows = queryAll(this.db, `
159
  SELECT * FROM mcp_query_patterns
160
  ORDER BY timestamp DESC
161
  LIMIT ?
@@ -245,8 +249,8 @@ export class PatternMemory {
245
  /**
246
  * Flush write queue to database (batched writes)
247
  */
248
- private flushWriteQueue(): void {
249
- if (!this.db || this.writeQueue.length === 0) return;
250
 
251
  const patterns = [...this.writeQueue];
252
  this.writeQueue = [];
@@ -264,17 +268,17 @@ export class PatternMemory {
264
  p.querySignature,
265
  p.sourceUsed,
266
  p.latencyMs,
267
- p.resultSize || null,
268
- p.success ? 1 : 0,
269
  p.userContext ? JSON.stringify(p.userContext) : null,
270
  p.timestamp.toISOString()
271
  ]);
272
 
273
- batchInsert(this.db, 'mcp_query_patterns', columns, rows);
274
 
275
  // Cleanup old patterns periodically
276
  if (Math.random() < 0.01) { // 1% chance per flush
277
- this.cleanupOldPatterns();
278
  }
279
 
280
  } catch (error) {
@@ -287,14 +291,17 @@ export class PatternMemory {
287
  /**
288
  * Cleanup old patterns to prevent database bloat
289
  */
290
- private cleanupOldPatterns(): void {
291
- if (!this.db) return;
292
 
293
  try {
294
  const thirtyDaysAgo = new Date();
295
  thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
296
 
297
- execute(this.db, `
 
 
 
298
  DELETE FROM mcp_query_patterns
299
  WHERE timestamp < ?
300
  AND id NOT IN (
@@ -332,10 +339,10 @@ export class PatternMemory {
332
  }
333
 
334
  // Query database for more
335
- if (this.db) {
336
  try {
337
  const remaining = limit - results.length;
338
- const rows = queryAll(this.db, `
339
  SELECT * FROM mcp_query_patterns
340
  WHERE query_signature = ?
341
  AND id NOT IN (${results.map(() => '?').join(',') || '\'\''})
@@ -367,9 +374,9 @@ export class PatternMemory {
367
  let patterns: QueryPattern[] = [];
368
 
369
  // Try database first
370
- if (this.db) {
371
  try {
372
- const rows = queryAll(this.db, `
373
  SELECT * FROM mcp_query_patterns
374
  WHERE widget_id = ? AND timestamp > ?
375
  ORDER BY timestamp DESC
@@ -435,12 +442,12 @@ export class PatternMemory {
435
  * Get average latency for a source
436
  */
437
  async getAverageLatency(sourceName: string): Promise<number> {
438
- if (this.db) {
439
  try {
440
  const oneDayAgo = new Date();
441
  oneDayAgo.setDate(oneDayAgo.getDate() - 1);
442
 
443
- const result = queryOne<{ avg_latency: number }>(this.db, `
444
  SELECT AVG(latency_ms) as avg_latency
445
  FROM mcp_query_patterns
446
  WHERE source_used = ? AND success = 1 AND timestamp > ?
@@ -471,12 +478,12 @@ export class PatternMemory {
471
  * Get success rate for a source and query type
472
  */
473
  async getSuccessRate(sourceName: string, queryType: string): Promise<number> {
474
- if (this.db) {
475
  try {
476
  const sevenDaysAgo = new Date();
477
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
478
 
479
- const result = queryOne<{ total: number; successes: number }>(this.db, `
480
  SELECT
481
  COUNT(*) as total,
482
  SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successes
@@ -515,37 +522,37 @@ export class PatternMemory {
515
  const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
516
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
517
 
518
- if (this.db) {
519
  try {
520
  // Total patterns
521
- const total = queryScalar<number>(this.db, 'SELECT COUNT(*) FROM mcp_query_patterns') || 0;
522
 
523
  // Unique widgets
524
- const uniqueWidgets = queryScalar<number>(this.db, 'SELECT COUNT(DISTINCT widget_id) FROM mcp_query_patterns') || 0;
525
 
526
  // Unique sources
527
- const uniqueSources = queryScalar<number>(this.db, 'SELECT COUNT(DISTINCT source_used) FROM mcp_query_patterns') || 0;
528
 
529
  // Average latency
530
- const avgLatency = queryScalar<number>(this.db, 'SELECT AVG(latency_ms) FROM mcp_query_patterns WHERE success = 1') || 0;
531
 
532
  // Success rate
533
- const successRate = queryScalar<number>(this.db, 'SELECT AVG(CAST(success AS FLOAT)) FROM mcp_query_patterns') || 0;
534
 
535
  // Queries last 24h
536
- const last24h = queryScalar<number>(this.db,
537
  'SELECT COUNT(*) FROM mcp_query_patterns WHERE timestamp > ?',
538
  [oneDayAgo.toISOString()]
539
  ) || 0;
540
 
541
  // Queries last 7d
542
- const last7d = queryScalar<number>(this.db,
543
  'SELECT COUNT(*) FROM mcp_query_patterns WHERE timestamp > ?',
544
  [sevenDaysAgo.toISOString()]
545
  ) || 0;
546
 
547
  // Top sources
548
- const topSourcesRows = queryAll<{ source_used: string; count: number; avg_latency: number }>(this.db, `
549
  SELECT source_used, COUNT(*) as count, AVG(latency_ms) as avg_latency
550
  FROM mcp_query_patterns
551
  GROUP BY source_used
@@ -559,7 +566,7 @@ export class PatternMemory {
559
  }));
560
 
561
  // Top widgets
562
- const topWidgetsRows = queryAll<{ widget_id: string; count: number }>(this.db, `
563
  SELECT widget_id, COUNT(*) as count
564
  FROM mcp_query_patterns
565
  GROUP BY widget_id
@@ -575,6 +582,7 @@ export class PatternMemory {
575
  totalPatterns: total,
576
  uniqueWidgets,
577
  uniqueSources,
 
578
  avgLatencyMs: avgLatency,
579
  successRate,
580
  queriesLast24h: last24h,
 
15
 
16
  import { v4 as uuidv4 } from 'uuid';
17
  import crypto from 'crypto';
18
+ import type { StorageAdapter } from './StorageAdapter.js';
19
  import { logger } from '../../utils/logger.js';
20
  import { eventBus, type EventType } from '../EventBus.js';
 
21
 
22
  // ═══════════════════════════════════════════════════════════════════════════════
23
  // INTERFACES
 
75
  // ═══════════════════════════════════════════════════════════════════════════════
76
 
77
  export class PatternMemory {
78
+ private storage: StorageAdapter | null = null;
79
  private initialized: boolean = false;
80
  private writeQueue: QueryPattern[] = [];
81
  private flushInterval: ReturnType<typeof setInterval> | null = null;
 
86
  private readonly FLUSH_INTERVAL_MS = 5000; // Batch writes every 5 seconds
87
  private readonly MAX_DB_PATTERNS = 100000;
88
 
89
+ constructor(storage?: StorageAdapter) {
90
+ if (storage) {
91
+ this.storage = storage;
92
  this.initialize();
93
  }
94
  }
95
 
96
  /**
97
+ * Initialize with storage adapter
98
  */
99
+ public setStorage(storage: StorageAdapter): void {
100
+ this.storage = storage;
101
  this.initialize();
102
  }
103
 
104
  /**
105
  * Initialize the service
106
  */
107
+ private async initialize(): Promise<void> {
108
+ if (this.initialized || !this.storage) return;
109
 
110
  try {
111
+ // Postgres uses different syntax for some things, but basic CREATE TABLE is mostly compatible
112
+ // except for DATETIME DEFAULT CURRENT_TIMESTAMP vs TIMESTAMP WITH TIME ZONE
113
+ const isPostgres = this.storage.mode === 'postgres';
114
+ const timestampType = isPostgres ? 'TIMESTAMP WITH TIME ZONE' : 'DATETIME';
115
+
116
+ await this.storage.execute(`
117
  CREATE TABLE IF NOT EXISTS mcp_query_patterns (
118
  id TEXT PRIMARY KEY,
119
  widget_id TEXT NOT NULL,
 
124
  result_size INTEGER,
125
  success BOOLEAN NOT NULL,
126
  user_context TEXT,
127
+ timestamp ${timestampType} NOT NULL DEFAULT CURRENT_TIMESTAMP
128
  )
129
  `);
130
 
131
  // Create indexes for fast queries
132
+ await this.storage.execute(`CREATE INDEX IF NOT EXISTS idx_query_patterns_widget
133
  ON mcp_query_patterns(widget_id, timestamp DESC)`);
134
+ await this.storage.execute(`CREATE INDEX IF NOT EXISTS idx_query_patterns_signature
135
  ON mcp_query_patterns(query_signature)`);
136
+ await this.storage.execute(`CREATE INDEX IF NOT EXISTS idx_query_patterns_source
137
  ON mcp_query_patterns(source_used, timestamp DESC)`);
138
 
139
  // Load recent patterns into cache
140
+ await this.loadCacheFromDb();
141
 
142
  // Start background flush
143
  this.flushInterval = setInterval(() => this.flushWriteQueue(), this.FLUSH_INTERVAL_MS);
144
 
145
  this.initialized = true;
146
+ logger.info(`🧠 PatternMemory initialized with ${this.storage.mode} storage`);
147
  } catch (error) {
148
  logger.error('❌ PatternMemory initialization failed:', error);
149
  // Fall back to in-memory only
 
154
  /**
155
  * Load recent patterns from database into cache
156
  */
157
+
158
+ private async loadCacheFromDb(): Promise<void> {
159
+ if (!this.storage) return;
160
 
161
  try {
162
+ const rows = await this.storage.queryAll(`
163
  SELECT * FROM mcp_query_patterns
164
  ORDER BY timestamp DESC
165
  LIMIT ?
 
249
  /**
250
  * Flush write queue to database (batched writes)
251
  */
252
+ private async flushWriteQueue(): Promise<void> {
253
+ if (!this.storage || this.writeQueue.length === 0) return;
254
 
255
  const patterns = [...this.writeQueue];
256
  this.writeQueue = [];
 
268
  p.querySignature,
269
  p.sourceUsed,
270
  p.latencyMs,
271
+ p.resultSize ?? null,
272
+ p.success, // Use boolean directly for Postgres compatibility
273
  p.userContext ? JSON.stringify(p.userContext) : null,
274
  p.timestamp.toISOString()
275
  ]);
276
 
277
+ await this.storage.batchInsert('mcp_query_patterns', columns, rows);
278
 
279
  // Cleanup old patterns periodically
280
  if (Math.random() < 0.01) { // 1% chance per flush
281
+ this.cleanupOldPatterns(); // No await to keep it background
282
  }
283
 
284
  } catch (error) {
 
291
  /**
292
  * Cleanup old patterns to prevent database bloat
293
  */
294
+ private async cleanupOldPatterns(): Promise<void> {
295
+ if (!this.storage) return;
296
 
297
  try {
298
  const thirtyDaysAgo = new Date();
299
  thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
300
 
301
+ // Need to fix this query for Postgres compatibility if nested subquery limit issues arise
302
+ // But standard SQL roughly supports DELETE...
303
+ // Postgres supports DELETE FROM ... WHERE id NOT IN (...)
304
+ await this.storage.execute(`
305
  DELETE FROM mcp_query_patterns
306
  WHERE timestamp < ?
307
  AND id NOT IN (
 
339
  }
340
 
341
  // Query database for more
342
+ if (this.storage) {
343
  try {
344
  const remaining = limit - results.length;
345
+ const rows = await this.storage.queryAll(`
346
  SELECT * FROM mcp_query_patterns
347
  WHERE query_signature = ?
348
  AND id NOT IN (${results.map(() => '?').join(',') || '\'\''})
 
374
  let patterns: QueryPattern[] = [];
375
 
376
  // Try database first
377
+ if (this.storage) {
378
  try {
379
+ const rows = await this.storage.queryAll(`
380
  SELECT * FROM mcp_query_patterns
381
  WHERE widget_id = ? AND timestamp > ?
382
  ORDER BY timestamp DESC
 
442
  * Get average latency for a source
443
  */
444
  async getAverageLatency(sourceName: string): Promise<number> {
445
+ if (this.storage) {
446
  try {
447
  const oneDayAgo = new Date();
448
  oneDayAgo.setDate(oneDayAgo.getDate() - 1);
449
 
450
+ const result = await this.storage.queryOne<{ avg_latency: number }>(`
451
  SELECT AVG(latency_ms) as avg_latency
452
  FROM mcp_query_patterns
453
  WHERE source_used = ? AND success = 1 AND timestamp > ?
 
478
  * Get success rate for a source and query type
479
  */
480
  async getSuccessRate(sourceName: string, queryType: string): Promise<number> {
481
+ if (this.storage) {
482
  try {
483
  const sevenDaysAgo = new Date();
484
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
485
 
486
+ const result = await this.storage.queryOne<{ total: number; successes: number }>(`
487
  SELECT
488
  COUNT(*) as total,
489
  SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successes
 
522
  const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
523
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
524
 
525
+ if (this.storage) {
526
  try {
527
  // Total patterns
528
+ const total = await this.storage.queryScalar<number>('SELECT COUNT(*) FROM mcp_query_patterns') || 0;
529
 
530
  // Unique widgets
531
+ const uniqueWidgets = await this.storage.queryScalar<number>('SELECT COUNT(DISTINCT widget_id) FROM mcp_query_patterns') || 0;
532
 
533
  // Unique sources
534
+ const uniqueSources = await this.storage.queryScalar<number>('SELECT COUNT(DISTINCT source_used) FROM mcp_query_patterns') || 0;
535
 
536
  // Average latency
537
+ const avgLatency = await this.storage.queryScalar<number>('SELECT AVG(latency_ms) FROM mcp_query_patterns WHERE success = 1') || 0;
538
 
539
  // Success rate
540
+ const successRate = await this.storage.queryScalar<number>('SELECT AVG(CAST(success AS FLOAT)) FROM mcp_query_patterns') || 0;
541
 
542
  // Queries last 24h
543
+ const last24h = await this.storage.queryScalar<number>(
544
  'SELECT COUNT(*) FROM mcp_query_patterns WHERE timestamp > ?',
545
  [oneDayAgo.toISOString()]
546
  ) || 0;
547
 
548
  // Queries last 7d
549
+ const last7d = await this.storage.queryScalar<number>(
550
  'SELECT COUNT(*) FROM mcp_query_patterns WHERE timestamp > ?',
551
  [sevenDaysAgo.toISOString()]
552
  ) || 0;
553
 
554
  // Top sources
555
+ const topSourcesRows = await this.storage.queryAll<{ source_used: string; count: number; avg_latency: number }>(`
556
  SELECT source_used, COUNT(*) as count, AVG(latency_ms) as avg_latency
557
  FROM mcp_query_patterns
558
  GROUP BY source_used
 
566
  }));
567
 
568
  // Top widgets
569
+ const topWidgetsRows = await this.storage.queryAll<{ widget_id: string; count: number }>(`
570
  SELECT widget_id, COUNT(*) as count
571
  FROM mcp_query_patterns
572
  GROUP BY widget_id
 
582
  totalPatterns: total,
583
  uniqueWidgets,
584
  uniqueSources,
585
+
586
  avgLatencyMs: avgLatency,
587
  successRate,
588
  queriesLast24h: last24h,
apps/backend/src/mcp/memory/StorageAdapter.ts ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import type { Database } from 'sql.js';
3
+ import { logger } from '../../utils/logger.js';
4
+ import { queryAll, queryOne, execute, batchInsert, queryScalar } from './SqlJsCompat.js';
5
+ import { DatabaseAdapter } from '../../platform/db/PrismaDatabaseAdapter.js';
6
+
7
+ export interface StorageAdapter {
8
+ queryAll<T = any>(sql: string, params?: any[]): Promise<T[]>;
9
+ queryOne<T = any>(sql: string, params?: any[]): Promise<T | null>;
10
+ queryScalar<T = any>(sql: string, params?: any[]): Promise<T | null>;
11
+ execute(sql: string, params?: any[]): Promise<number>;
12
+ batchInsert(tableName: string, columns: string[], rows: any[][]): Promise<number>;
13
+ isAvailable(): boolean;
14
+ mode: 'sqlite' | 'postgres';
15
+ }
16
+
17
+ export class SqlJsStorageAdapter implements StorageAdapter {
18
+ constructor(private db: Database | null) { }
19
+
20
+ mode: 'sqlite' | 'postgres' = 'sqlite';
21
+
22
+ isAvailable(): boolean {
23
+ return !!this.db;
24
+ }
25
+
26
+ async queryAll<T = any>(sql: string, params: any[] = []): Promise<T[]> {
27
+ if (!this.db) return [];
28
+ // SqlJsCompat functions are synchronous for sql.js, but we wrap in Promise for common interface
29
+ return Promise.resolve(queryAll<T>(this.db, sql, params));
30
+ }
31
+
32
+ async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
33
+ if (!this.db) return null;
34
+ return Promise.resolve(queryOne<T>(this.db, sql, params));
35
+ }
36
+
37
+ async queryScalar<T = any>(sql: string, params: any[] = []): Promise<T | null> {
38
+ if (!this.db) return null;
39
+ return Promise.resolve(queryScalar<T>(this.db, sql, params));
40
+ }
41
+
42
+ async execute(sql: string, params: any[] = []): Promise<number> {
43
+ if (!this.db) return 0;
44
+ return Promise.resolve(execute(this.db, sql, params));
45
+ }
46
+
47
+ async batchInsert(tableName: string, columns: string[], rows: any[][]): Promise<number> {
48
+ if (!this.db) return 0;
49
+ return Promise.resolve(batchInsert(this.db, tableName, columns, rows));
50
+ }
51
+ }
52
+
53
+ export class PostgresStorageAdapter implements StorageAdapter {
54
+ constructor(private prisma: DatabaseAdapter) { }
55
+
56
+ mode: 'sqlite' | 'postgres' = 'postgres';
57
+
58
+ isAvailable(): boolean {
59
+ return this.prisma.isAvailable();
60
+ }
61
+
62
+ async queryAll<T = any>(sql: string, params: any[] = []): Promise<T[]> {
63
+ // Postgres uses $1, $2, etc. instead of ?
64
+ const { sql: pgSql, params: pgParams } = this.convertSql(sql, params);
65
+ return await this.prisma.query(pgSql, pgParams);
66
+ }
67
+
68
+ async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
69
+ const rows = await this.queryAll<T>(sql, params);
70
+ return rows.length > 0 ? rows[0] : null;
71
+ }
72
+
73
+ async queryScalar<T = any>(sql: string, params: any[] = []): Promise<T | null> {
74
+ const rows = await this.queryAll(sql, params);
75
+ if (rows.length === 0) return null;
76
+ const firstValue = Object.values(rows[0])[0];
77
+ return firstValue as T;
78
+ }
79
+
80
+ async execute(sql: string, params: any[] = []): Promise<number> {
81
+ const { sql: pgSql, params: pgParams } = this.convertSql(sql, params);
82
+ return await this.prisma.execute(pgSql, pgParams);
83
+ }
84
+
85
+ async batchInsert(tableName: string, columns: string[], rows: any[][]): Promise<number> {
86
+ if (rows.length === 0) return 0;
87
+
88
+ // Construct standard VALUES clause but with $1, $2...
89
+ // This is tricky with variable params.
90
+ // A simple loop implementation for now
91
+ let count = 0;
92
+ for (const row of rows) {
93
+ const placeholders = row.map((_, i) => `$${i + 1}`).join(', ');
94
+ const sql = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;
95
+ await this.prisma.query(sql, row);
96
+ count++;
97
+ }
98
+ return count;
99
+ }
100
+
101
+ private convertSql(sql: string, params: any[]): { sql: string, params: any[] } {
102
+ // Convert ? to $1, $2, etc.
103
+ let paramIndex = 1;
104
+ const pgSql = sql.replace(/\?/g, () => `$${paramIndex++}`);
105
+ return { sql: pgSql, params };
106
+ }
107
+ }
apps/backend/src/platform/db/PrismaDatabaseAdapter.ts CHANGED
@@ -24,6 +24,7 @@ export interface DatabaseAdapter {
24
  initialize(): Promise<void>;
25
  disconnect(): Promise<void>;
26
  query(sql: string, params?: any[]): Promise<any>;
 
27
  transaction<T>(fn: (tx: any) => Promise<T>): Promise<T>;
28
  isAvailable(): boolean;
29
  }
@@ -56,6 +57,10 @@ class StubDatabaseAdapter implements DatabaseAdapter {
56
  throw new Error(`PostgreSQL not available: ${this.reason}. Use SQLite for local operations.`);
57
  }
58
 
 
 
 
 
59
  isAvailable(): boolean {
60
  return false;
61
  }
@@ -131,6 +136,13 @@ class PrismaDatabaseAdapterImpl implements DatabaseAdapter {
131
  return this.prisma.$queryRawUnsafe(sql, ...(params || []));
132
  }
133
 
 
 
 
 
 
 
 
134
  async transaction<T>(fn: (tx: any) => Promise<T>): Promise<T> {
135
  if (!this.prisma || !this.isInitialized) {
136
  throw new Error('PostgreSQL not connected. Use SQLite for local operations.');
 
24
  initialize(): Promise<void>;
25
  disconnect(): Promise<void>;
26
  query(sql: string, params?: any[]): Promise<any>;
27
+ execute(sql: string, params?: any[]): Promise<number>;
28
  transaction<T>(fn: (tx: any) => Promise<T>): Promise<T>;
29
  isAvailable(): boolean;
30
  }
 
57
  throw new Error(`PostgreSQL not available: ${this.reason}. Use SQLite for local operations.`);
58
  }
59
 
60
+ async execute(_sql: string, _params?: any[]): Promise<number> {
61
+ throw new Error(`PostgreSQL not available: ${this.reason}. Use SQLite for local operations.`);
62
+ }
63
+
64
  isAvailable(): boolean {
65
  return false;
66
  }
 
136
  return this.prisma.$queryRawUnsafe(sql, ...(params || []));
137
  }
138
 
139
+ async execute(sql: string, params?: any[]): Promise<number> {
140
+ if (!this.prisma || !this.isInitialized) {
141
+ throw new Error('PostgreSQL not connected. Use SQLite for local operations.');
142
+ }
143
+ return this.prisma.$executeRawUnsafe(sql, ...(params || []));
144
+ }
145
+
146
  async transaction<T>(fn: (tx: any) => Promise<T>): Promise<T> {
147
  if (!this.prisma || !this.isInitialized) {
148
  throw new Error('PostgreSQL not connected. Use SQLite for local operations.');
apps/backend/src/services/NeuralChat/NeuralCortex.ts CHANGED
@@ -67,7 +67,7 @@ export interface DiscoveredPattern {
67
 
68
  class NeuralCortex {
69
  private static instance: NeuralCortex;
70
-
71
  public static getInstance(): NeuralCortex {
72
  if (!NeuralCortex.instance) {
73
  NeuralCortex.instance = new NeuralCortex();
@@ -86,7 +86,7 @@ class NeuralCortex {
86
  }> {
87
  const entities = this.extractEntities(message.body);
88
  const concepts = this.extractConcepts(message.body);
89
-
90
  // 1. Create message node in GRAPH (Neo4j)
91
  await neo4jAdapter.runQuery(`
92
  CREATE (m:Message {
@@ -158,23 +158,23 @@ class NeuralCortex {
158
  */
159
  private extractEntities(text: string): string[] {
160
  const entities: string[] = [];
161
-
162
  // @mentions
163
  const mentions = text.match(/@(\w+)/g);
164
  if (mentions) entities.push(...mentions.map(m => m.slice(1)));
165
-
166
  // File paths
167
  const files = text.match(/[\w-]+\.(ts|js|tsx|jsx|json|md|py|yaml|yml|sql)/gi);
168
  if (files) entities.push(...files);
169
-
170
  // Component/Class names (PascalCase)
171
  const components = text.match(/\b[A-Z][a-zA-Z]+(?:Widget|Service|Controller|Adapter|Component|Provider|Handler)\b/g);
172
  if (components) entities.push(...components);
173
-
174
  // URLs
175
  const urls = text.match(/https?:\/\/[^\s]+/g);
176
  if (urls) entities.push(...urls);
177
-
178
  return [...new Set(entities)];
179
  }
180
 
@@ -184,19 +184,19 @@ class NeuralCortex {
184
  private extractConcepts(text: string): string[] {
185
  const concepts: string[] = [];
186
  const textLower = text.toLowerCase();
187
-
188
  // Technologies
189
- const techs = ['neo4j', 'react', 'typescript', 'docker', 'kubernetes', 'api', 'websocket', 'mcp', 'graphql', 'rest', 'postgresql', 'redis', 'vector', 'rag'];
190
  techs.forEach(t => { if (textLower.includes(t)) concepts.push(t); });
191
-
192
  // Actions
193
  const actions = ['deploy', 'review', 'test', 'refactor', 'implement', 'fix', 'create', 'delete', 'update', 'analyze', 'research', 'architect'];
194
  actions.forEach(a => { if (textLower.includes(a)) concepts.push(a); });
195
-
196
  // Domains
197
  const domains = ['security', 'performance', 'architecture', 'authentication', 'authorization', 'database', 'frontend', 'backend', 'infrastructure', 'ai', 'agents'];
198
  domains.forEach(d => { if (textLower.includes(d)) concepts.push(d); });
199
-
200
  return [...new Set(concepts)];
201
  }
202
 
@@ -211,7 +211,7 @@ class NeuralCortex {
211
  MERGE (m)-[:MENTIONS]->(e)
212
  RETURN e.name as linked
213
  `, { messageId, entity });
214
-
215
  return result[0]?.linked || null;
216
  } catch {
217
  return null;
@@ -241,27 +241,27 @@ class NeuralCortex {
241
  */
242
  async query(input: CortexQuery): Promise<CortexResult[]> {
243
  const results: CortexResult[] = [];
244
-
245
  switch (input.type) {
246
  case 'search':
247
  return await this.hybridSearch(input.query, input.context);
248
-
249
  case 'pattern':
250
  return await this.findPatterns(input.query, input.context);
251
-
252
  case 'insight':
253
  return await this.generateInsights(input.query, input.context);
254
-
255
  case 'history':
256
  return await this.getDecisionHistory(input.query, input.context);
257
-
258
  case 'chat':
259
  default:
260
  // 1. Search Chat History (Vector + Keyword)
261
  const chatResults = await this.searchMessages(input.query);
262
  // 2. Search Knowledge Graph (Keyword/Hybrid)
263
  const graphResults = await this.hybridSearch(input.query, input.context);
264
-
265
  return [...chatResults, ...graphResults].sort((a, b) => b.relevance - a.relevance);
266
  }
267
  }
@@ -313,7 +313,7 @@ class NeuralCortex {
313
 
314
  for (const r of graphResults) {
315
  if (!existingIds.has(r.m.properties.id)) {
316
- results.push({
317
  type: 'message' as const,
318
  data: {
319
  id: r.m.properties.id,
@@ -380,13 +380,13 @@ class NeuralCortex {
380
  LIMIT 20
381
  `, { query, nodeTypes });
382
 
383
- // Merge results (simple dedup by name)
384
- const existingNames = new Set(results.map(r => r.data.name));
385
-
386
- for (const r of graphResults) {
387
- const name = r.n.properties.name || r.n.properties.id;
388
- if (!existingNames.has(name)) {
389
- results.push({
390
  type: 'node' as const,
391
  data: {
392
  name: name,
@@ -397,8 +397,8 @@ class NeuralCortex {
397
  source: 'knowledge_graph',
398
  connections: r.connections.filter((c: any) => c.target)
399
  });
400
- }
401
- }
402
 
403
  return results;
404
  } catch {
@@ -567,7 +567,7 @@ class NeuralCortex {
567
  return [];
568
  }
569
  }
570
-
571
  /**
572
  * Calculate relevance score (0-1) for a result
573
  */
@@ -575,12 +575,12 @@ class NeuralCortex {
575
  if (!text) return 0;
576
  const queryTerms = query.toLowerCase().split(' ');
577
  const textLower = text.toLowerCase();
578
-
579
  let matches = 0;
580
  for (const term of queryTerms) {
581
  if (textLower.includes(term)) matches++;
582
  }
583
-
584
  return matches / queryTerms.length;
585
  }
586
 
@@ -589,7 +589,7 @@ class NeuralCortex {
589
  */
590
  private async getDecisionHistory(query: string, context?: CortexQuery['context']): Promise<CortexResult[]> {
591
  try {
592
- const history = await neo4jAdapter.runQuery(`
593
  MATCH (m:Message)
594
  WHERE any(word IN ['decision', 'approved', 'rejected', 'selected', 'chose']
595
  WHERE toLower(m.body) CONTAINS word)
@@ -599,17 +599,17 @@ class NeuralCortex {
599
  LIMIT 20
600
  `, { query });
601
 
602
- return history.map((h: any) => ({
603
- type: 'message',
604
- data: {
605
- id: h.m.properties.id,
606
- body: h.m.properties.body,
607
- agent: h.agent,
608
- timestamp: h.m.properties.timestamp
609
- },
610
- relevance: 1,
611
- source: 'history'
612
- }));
613
  } catch {
614
  return [];
615
  }
 
67
 
68
  class NeuralCortex {
69
  private static instance: NeuralCortex;
70
+
71
  public static getInstance(): NeuralCortex {
72
  if (!NeuralCortex.instance) {
73
  NeuralCortex.instance = new NeuralCortex();
 
86
  }> {
87
  const entities = this.extractEntities(message.body);
88
  const concepts = this.extractConcepts(message.body);
89
+
90
  // 1. Create message node in GRAPH (Neo4j)
91
  await neo4jAdapter.runQuery(`
92
  CREATE (m:Message {
 
158
  */
159
  private extractEntities(text: string): string[] {
160
  const entities: string[] = [];
161
+
162
  // @mentions
163
  const mentions = text.match(/@(\w+)/g);
164
  if (mentions) entities.push(...mentions.map(m => m.slice(1)));
165
+
166
  // File paths
167
  const files = text.match(/[\w-]+\.(ts|js|tsx|jsx|json|md|py|yaml|yml|sql)/gi);
168
  if (files) entities.push(...files);
169
+
170
  // Component/Class names (PascalCase)
171
  const components = text.match(/\b[A-Z][a-zA-Z]+(?:Widget|Service|Controller|Adapter|Component|Provider|Handler)\b/g);
172
  if (components) entities.push(...components);
173
+
174
  // URLs
175
  const urls = text.match(/https?:\/\/[^\s]+/g);
176
  if (urls) entities.push(...urls);
177
+
178
  return [...new Set(entities)];
179
  }
180
 
 
184
  private extractConcepts(text: string): string[] {
185
  const concepts: string[] = [];
186
  const textLower = text.toLowerCase();
187
+
188
  // Technologies
189
+ const techs = ['neo4j', 'react', 'typescript', 'docker', 'kubernetes', 'api', 'websocket', 'mcp', 'graphql', 'rest', 'postgresql', 'redis', 'vector', 'pgvector', 'rag'];
190
  techs.forEach(t => { if (textLower.includes(t)) concepts.push(t); });
191
+
192
  // Actions
193
  const actions = ['deploy', 'review', 'test', 'refactor', 'implement', 'fix', 'create', 'delete', 'update', 'analyze', 'research', 'architect'];
194
  actions.forEach(a => { if (textLower.includes(a)) concepts.push(a); });
195
+
196
  // Domains
197
  const domains = ['security', 'performance', 'architecture', 'authentication', 'authorization', 'database', 'frontend', 'backend', 'infrastructure', 'ai', 'agents'];
198
  domains.forEach(d => { if (textLower.includes(d)) concepts.push(d); });
199
+
200
  return [...new Set(concepts)];
201
  }
202
 
 
211
  MERGE (m)-[:MENTIONS]->(e)
212
  RETURN e.name as linked
213
  `, { messageId, entity });
214
+
215
  return result[0]?.linked || null;
216
  } catch {
217
  return null;
 
241
  */
242
  async query(input: CortexQuery): Promise<CortexResult[]> {
243
  const results: CortexResult[] = [];
244
+
245
  switch (input.type) {
246
  case 'search':
247
  return await this.hybridSearch(input.query, input.context);
248
+
249
  case 'pattern':
250
  return await this.findPatterns(input.query, input.context);
251
+
252
  case 'insight':
253
  return await this.generateInsights(input.query, input.context);
254
+
255
  case 'history':
256
  return await this.getDecisionHistory(input.query, input.context);
257
+
258
  case 'chat':
259
  default:
260
  // 1. Search Chat History (Vector + Keyword)
261
  const chatResults = await this.searchMessages(input.query);
262
  // 2. Search Knowledge Graph (Keyword/Hybrid)
263
  const graphResults = await this.hybridSearch(input.query, input.context);
264
+
265
  return [...chatResults, ...graphResults].sort((a, b) => b.relevance - a.relevance);
266
  }
267
  }
 
313
 
314
  for (const r of graphResults) {
315
  if (!existingIds.has(r.m.properties.id)) {
316
+ results.push({
317
  type: 'message' as const,
318
  data: {
319
  id: r.m.properties.id,
 
380
  LIMIT 20
381
  `, { query, nodeTypes });
382
 
383
+ // Merge results (simple dedup by name)
384
+ const existingNames = new Set(results.map(r => r.data.name));
385
+
386
+ for (const r of graphResults) {
387
+ const name = r.n.properties.name || r.n.properties.id;
388
+ if (!existingNames.has(name)) {
389
+ results.push({
390
  type: 'node' as const,
391
  data: {
392
  name: name,
 
397
  source: 'knowledge_graph',
398
  connections: r.connections.filter((c: any) => c.target)
399
  });
400
+ }
401
+ }
402
 
403
  return results;
404
  } catch {
 
567
  return [];
568
  }
569
  }
570
+
571
  /**
572
  * Calculate relevance score (0-1) for a result
573
  */
 
575
  if (!text) return 0;
576
  const queryTerms = query.toLowerCase().split(' ');
577
  const textLower = text.toLowerCase();
578
+
579
  let matches = 0;
580
  for (const term of queryTerms) {
581
  if (textLower.includes(term)) matches++;
582
  }
583
+
584
  return matches / queryTerms.length;
585
  }
586
 
 
589
  */
590
  private async getDecisionHistory(query: string, context?: CortexQuery['context']): Promise<CortexResult[]> {
591
  try {
592
+ const history = await neo4jAdapter.runQuery(`
593
  MATCH (m:Message)
594
  WHERE any(word IN ['decision', 'approved', 'rejected', 'selected', 'chose']
595
  WHERE toLower(m.body) CONTAINS word)
 
599
  LIMIT 20
600
  `, { query });
601
 
602
+ return history.map((h: any) => ({
603
+ type: 'message',
604
+ data: {
605
+ id: h.m.properties.id,
606
+ body: h.m.properties.body,
607
+ agent: h.agent,
608
+ timestamp: h.m.properties.timestamp
609
+ },
610
+ relevance: 1,
611
+ source: 'history'
612
+ }));
613
  } catch {
614
  return [];
615
  }
apps/backend/src/services/SelfHealingAdapter.ts CHANGED
@@ -12,7 +12,7 @@ interface HealingResult {
12
  latency: number;
13
  }
14
 
15
- class SelfHealingAdapter {
16
  private static instance: SelfHealingAdapter;
17
  private retryLimits: Map<ErrorSignature, number> = new Map();
18
  private circuitBreakers: Map<string, boolean> = new Map(); // True = OPEN (Broken)
@@ -29,6 +29,25 @@ class SelfHealingAdapter {
29
  return SelfHealingAdapter.instance;
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  /**
33
  * 🛡️ THE OMEGA SHIELD: Main entry point for all system errors.
34
  * Wraps any operation in a Lazarus Loop.
@@ -158,7 +177,7 @@ class SelfHealingAdapter {
158
  * Used by KnowledgeCompiler.
159
  */
160
  public getPredictiveAlerts(): any[] {
161
- // In a real implementation, this would analyze error frequencies from metricsService.
162
  // For now, we return basic alerts if circuit breakers are open.
163
  const alerts: any[] = [];
164
  this.circuitBreakers.forEach((isOpen, name) => {
 
12
  latency: number;
13
  }
14
 
15
+ export class SelfHealingAdapter {
16
  private static instance: SelfHealingAdapter;
17
  private retryLimits: Map<ErrorSignature, number> = new Map();
18
  private circuitBreakers: Map<string, boolean> = new Map(); // True = OPEN (Broken)
 
29
  return SelfHealingAdapter.instance;
30
  }
31
 
32
+ // Wrapper compatibility for CortexController
33
+ public async handleError(error: any, source: string): Promise<void> {
34
+ await this.heal(error instanceof Error ? error : new Error(String(error)), source, {});
35
+ }
36
+
37
+ public async runStartupValidation(_fullCheck: boolean = false): Promise<{ success: boolean; services: any[] }> {
38
+ // Basic validation implementation
39
+ return {
40
+ success: true,
41
+ services: this.getSystemStatus().services
42
+ };
43
+ }
44
+
45
+ public async learnFromError(error: any, context: any, _solution?: any): Promise<void> {
46
+ // Integration with Knowledge Graph to learn from failures
47
+ console.log(`🧠 [LAZARUS] Analyzing failure pattern: ${error instanceof Error ? error.message : String(error)} in ${JSON.stringify(context)}`);
48
+ // Logic to store into FailureMemory would go here
49
+ }
50
+
51
  /**
52
  * 🛡️ THE OMEGA SHIELD: Main entry point for all system errors.
53
  * Wraps any operation in a Lazarus Loop.
 
177
  * Used by KnowledgeCompiler.
178
  */
179
  public getPredictiveAlerts(): any[] {
180
+ // In a real implementations, this would analyze error frequencies from metricsService.
181
  // For now, we return basic alerts if circuit breakers are open.
182
  const alerts: any[] = [];
183
  this.circuitBreakers.forEach((isOpen, name) => {
apps/backend/src/services/embeddings/LocalGPUEmbeddings.ts CHANGED
@@ -3,10 +3,14 @@ import path from 'path';
3
  import { EmbeddingProvider } from './EmbeddingService.js';
4
  import { logger } from '../../utils/logger.js';
5
 
6
- // ESM/CJS compatible __dirname
7
- const __dirname = typeof import.meta !== 'undefined' && import.meta.url
8
- ? path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1'))
9
- : __dirname || process.cwd();
 
 
 
 
10
 
11
  export class LocalGPUEmbeddingsProvider implements EmbeddingProvider {
12
  name = 'local-gpu';
@@ -16,14 +20,14 @@ export class LocalGPUEmbeddingsProvider implements EmbeddingProvider {
16
  private isReady: boolean = false;
17
  private pendingRequests: Map<number, { resolve: (val: any) => void; reject: (err: any) => void }> = new Map();
18
  private requestCounter: number = 0;
19
- private cleanup: () => void = () => {}; // Dynamic cleanup callback
20
 
21
  async initialize(): Promise<void> {
22
  if (this.isReady) return;
23
 
24
  return new Promise((resolve, reject) => {
25
  try {
26
- const scriptPath = path.join(__dirname, 'gpu_bridge.py');
27
  logger.info(`🔌 Starting GPU Bridge: python3 ${scriptPath}`);
28
 
29
  this.process = spawn('python3', [scriptPath]);
@@ -33,10 +37,10 @@ export class LocalGPUEmbeddingsProvider implements EmbeddingProvider {
33
  const lines = data.toString().split('\n');
34
  for (const line of lines) {
35
  if (!line.trim()) continue;
36
-
37
  try {
38
  const response = JSON.parse(line);
39
-
40
  // Initial Ready Signal
41
  if (response.status === 'ready') {
42
  logger.info(`🚀 GPU Bridge Ready on device: ${response.device}`);
@@ -49,10 +53,10 @@ export class LocalGPUEmbeddingsProvider implements EmbeddingProvider {
49
  // For a more robust solution with concurrency, we'd need Request IDs
50
  // But for this implementation, we assume one request at a time via the Service lock
51
  // or we rely on the strictly ordered stdio.
52
-
53
  // NOTE: This simplified implementation assumes sequential processing (awaiting each call)
54
  // which matches the current usage in EmbeddingService.
55
-
56
  } catch (e) {
57
  logger.error(`GPU Bridge parse error: ${e} [Line: ${line}]`);
58
  }
@@ -76,11 +80,11 @@ export class LocalGPUEmbeddingsProvider implements EmbeddingProvider {
76
 
77
  // Timeout backup
78
  setTimeout(() => {
79
- if (!this.isReady) {
80
- // If it takes too long, we might just be downloading the model (can take time)
81
- // So we don't reject immediately, but warn.
82
- logger.warn('⏳ GPU Bridge taking longer than expected (model download?)...');
83
- }
84
  }, 10000);
85
 
86
  } catch (error) {
@@ -95,43 +99,43 @@ export class LocalGPUEmbeddingsProvider implements EmbeddingProvider {
95
  // We just need to ensure we don't interleave writes before reads are done.
96
  private async execute<T>(payload: any): Promise<T> {
97
  if (!this.process || !this.isReady) {
98
- await this.initialize();
99
  }
100
 
101
  return new Promise((resolve, reject) => {
102
- // Simple one-off listener for the next line of output
103
- // This assumes strictly sequential execution provided by the await in the caller
104
- const listener = (data: Buffer) => {
105
- const lines = data.toString().split('\n').filter(l => l.trim());
106
- for (const line of lines) {
107
- try {
108
- const response = JSON.parse(line);
109
- // Ignore status messages if they appear late
110
- if (response.status) continue;
111
-
112
- if (response.error) {
113
- this.cleanup();
114
- reject(new Error(response.error));
115
- } else {
116
- this.cleanup();
117
- resolve(response);
118
- }
119
- return; // Handled
120
- } catch (e) {
121
- // partial line? wait for next chunk
122
- }
123
  }
124
- };
125
-
126
- const cleanup = () => {
127
- this.process?.stdout?.off('data', listener);
128
- this.cleanup = () => {}; // prevent double call
129
- };
130
- // Store cleanup on this scope so the listener callback can call it
131
- (this as any).cleanup = cleanup;
132
-
133
- this.process!.stdout!.on('data', listener);
134
- this.process!.stdin!.write(JSON.stringify(payload) + '\n');
 
 
 
 
 
135
  });
136
  }
137
 
 
3
  import { EmbeddingProvider } from './EmbeddingService.js';
4
  import { logger } from '../../utils/logger.js';
5
 
6
+ import { fileURLToPath } from 'url';
7
+
8
+ // ESM/CJS compatible directory logic
9
+ const currentDir = typeof import.meta !== 'undefined' && import.meta.url
10
+ ? path.dirname(fileURLToPath(import.meta.url))
11
+ : process.cwd(); // Fallback for CJS if specific __dirname is tricky, usually path.dirname(__filename) but __filename is also issue.
12
+ // Better: assume ESM for this modern app or just use process.cwd() relative path if needed but finding built script is safer.
13
+
14
 
15
  export class LocalGPUEmbeddingsProvider implements EmbeddingProvider {
16
  name = 'local-gpu';
 
20
  private isReady: boolean = false;
21
  private pendingRequests: Map<number, { resolve: (val: any) => void; reject: (err: any) => void }> = new Map();
22
  private requestCounter: number = 0;
23
+ private cleanup: () => void = () => { }; // Dynamic cleanup callback
24
 
25
  async initialize(): Promise<void> {
26
  if (this.isReady) return;
27
 
28
  return new Promise((resolve, reject) => {
29
  try {
30
+ const scriptPath = path.join(currentDir, 'gpu_bridge.py');
31
  logger.info(`🔌 Starting GPU Bridge: python3 ${scriptPath}`);
32
 
33
  this.process = spawn('python3', [scriptPath]);
 
37
  const lines = data.toString().split('\n');
38
  for (const line of lines) {
39
  if (!line.trim()) continue;
40
+
41
  try {
42
  const response = JSON.parse(line);
43
+
44
  // Initial Ready Signal
45
  if (response.status === 'ready') {
46
  logger.info(`🚀 GPU Bridge Ready on device: ${response.device}`);
 
53
  // For a more robust solution with concurrency, we'd need Request IDs
54
  // But for this implementation, we assume one request at a time via the Service lock
55
  // or we rely on the strictly ordered stdio.
56
+
57
  // NOTE: This simplified implementation assumes sequential processing (awaiting each call)
58
  // which matches the current usage in EmbeddingService.
59
+
60
  } catch (e) {
61
  logger.error(`GPU Bridge parse error: ${e} [Line: ${line}]`);
62
  }
 
80
 
81
  // Timeout backup
82
  setTimeout(() => {
83
+ if (!this.isReady) {
84
+ // If it takes too long, we might just be downloading the model (can take time)
85
+ // So we don't reject immediately, but warn.
86
+ logger.warn('⏳ GPU Bridge taking longer than expected (model download?)...');
87
+ }
88
  }, 10000);
89
 
90
  } catch (error) {
 
99
  // We just need to ensure we don't interleave writes before reads are done.
100
  private async execute<T>(payload: any): Promise<T> {
101
  if (!this.process || !this.isReady) {
102
+ await this.initialize();
103
  }
104
 
105
  return new Promise((resolve, reject) => {
106
+ // Simple one-off listener for the next line of output
107
+ // This assumes strictly sequential execution provided by the await in the caller
108
+ const listener = (data: Buffer) => {
109
+ const lines = data.toString().split('\n').filter(l => l.trim());
110
+ for (const line of lines) {
111
+ try {
112
+ const response = JSON.parse(line);
113
+ // Ignore status messages if they appear late
114
+ if (response.status) continue;
115
+
116
+ if (response.error) {
117
+ this.cleanup();
118
+ reject(new Error(response.error));
119
+ } else {
120
+ this.cleanup();
121
+ resolve(response);
 
 
 
 
 
122
  }
123
+ return; // Handled
124
+ } catch (e) {
125
+ // partial line? wait for next chunk
126
+ }
127
+ }
128
+ };
129
+
130
+ const cleanup = () => {
131
+ this.process?.stdout?.off('data', listener);
132
+ this.cleanup = () => { }; // prevent double call
133
+ };
134
+ // Store cleanup on this scope so the listener callback can call it
135
+ (this as any).cleanup = cleanup;
136
+
137
+ this.process!.stdout!.on('data', listener);
138
+ this.process!.stdin!.write(JSON.stringify(payload) + '\n');
139
  });
140
  }
141
 
apps/backend/src/tests/autonomous.integration.test.ts CHANGED
@@ -15,6 +15,7 @@
15
 
16
  import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest';
17
  import { initializeDatabase, getDatabase, getSqlJsDatabase } from '../database/index.js';
 
18
 
19
  // Mock Embeddings to avoid ONNX/Float32Array issues in tests
20
  vi.mock('../platform/embeddings/TransformersEmbeddings', () => ({
@@ -85,7 +86,7 @@ describe('PatternMemory - Enterprise Persistence', () => {
85
 
86
  beforeEach(async () => {
87
  const { PatternMemory } = await import('../mcp/memory/PatternMemory.js');
88
- patternMemory = new PatternMemory(db);
89
  });
90
 
91
  it('should record query patterns', async () => {
@@ -212,7 +213,7 @@ describe('FailureMemory - Self-Healing Intelligence', () => {
212
 
213
  beforeEach(async () => {
214
  const { FailureMemory } = await import('../mcp/memory/FailureMemory.js');
215
- failureMemory = new FailureMemory(db);
216
  });
217
 
218
  it('should record failures with context', async () => {
 
15
 
16
  import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest';
17
  import { initializeDatabase, getDatabase, getSqlJsDatabase } from '../database/index.js';
18
+ import { SqlJsStorageAdapter } from '../mcp/memory/StorageAdapter.js';
19
 
20
  // Mock Embeddings to avoid ONNX/Float32Array issues in tests
21
  vi.mock('../platform/embeddings/TransformersEmbeddings', () => ({
 
86
 
87
  beforeEach(async () => {
88
  const { PatternMemory } = await import('../mcp/memory/PatternMemory.js');
89
+ patternMemory = new PatternMemory(new SqlJsStorageAdapter(db));
90
  });
91
 
92
  it('should record query patterns', async () => {
 
213
 
214
  beforeEach(async () => {
215
  const { FailureMemory } = await import('../mcp/memory/FailureMemory.js');
216
+ failureMemory = new FailureMemory(new SqlJsStorageAdapter(db));
217
  });
218
 
219
  it('should record failures with context', async () => {
package-lock.json CHANGED
@@ -115,11 +115,48 @@
115
  "@types/uuid": "^9.0.7",
116
  "@types/ws": "^8.5.10",
117
  "@types/xml2js": "^0.4.14",
 
 
118
  "esbuild": "^0.24.2",
 
119
  "tsx": "^4.20.6",
120
  "typescript": "~5.8.2"
121
  }
122
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  "apps/backend/node_modules/@napi-rs/canvas": {
124
  "version": "0.1.84",
125
  "license": "MIT",
@@ -151,6 +188,473 @@
151
  "undici-types": "~6.21.0"
152
  }
153
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  "apps/backend/node_modules/pdfjs-dist": {
155
  "version": "5.4.449",
156
  "license": "Apache-2.0",
@@ -161,6 +665,36 @@
161
  "@napi-rs/canvas": "^0.1.81"
162
  }
163
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  "apps/backend/packages/domain-types": {
165
  "name": "@widget-tdc/domain-types",
166
  "version": "1.0.0",
@@ -3325,6 +3859,46 @@
3325
  "node": ">=18.18.0"
3326
  }
3327
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3328
  "node_modules/@humanwhocodes/module-importer": {
3329
  "version": "1.0.1",
3330
  "dev": true,
@@ -3337,6 +3911,14 @@
3337
  "url": "https://github.com/sponsors/nzakas"
3338
  }
3339
  },
 
 
 
 
 
 
 
 
3340
  "node_modules/@humanwhocodes/retry": {
3341
  "version": "0.4.3",
3342
  "dev": true,
@@ -7500,6 +8082,16 @@
7500
  "url": "https://github.com/sponsors/ljharb"
7501
  }
7502
  },
 
 
 
 
 
 
 
 
 
 
7503
  "node_modules/array.prototype.findlast": {
7504
  "version": "1.2.5",
7505
  "dev": true,
@@ -9638,6 +10230,19 @@
9638
  "node": ">=0.3.1"
9639
  }
9640
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
9641
  "node_modules/dlv": {
9642
  "version": "1.1.3",
9643
  "license": "MIT"
@@ -11885,6 +12490,37 @@
11885
  "url": "https://github.com/sponsors/ljharb"
11886
  }
11887
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11888
  "node_modules/gopd": {
11889
  "version": "1.2.0",
11890
  "license": "MIT",
@@ -12655,6 +13291,16 @@
12655
  "node": ">=0.10.0"
12656
  }
12657
  },
 
 
 
 
 
 
 
 
 
 
12658
  "node_modules/is-plain-obj": {
12659
  "version": "4.1.0",
12660
  "license": "MIT",
@@ -17154,6 +17800,16 @@
17154
  "node": ">=18"
17155
  }
17156
  },
 
 
 
 
 
 
 
 
 
 
17157
  "node_modules/smart-buffer": {
17158
  "version": "4.2.0",
17159
  "license": "MIT",
@@ -17999,6 +18655,13 @@
17999
  "version": "1.0.0",
18000
  "license": "MIT"
18001
  },
 
 
 
 
 
 
 
18002
  "node_modules/thenify": {
18003
  "version": "3.3.1",
18004
  "license": "MIT",
 
115
  "@types/uuid": "^9.0.7",
116
  "@types/ws": "^8.5.10",
117
  "@types/xml2js": "^0.4.14",
118
+ "@typescript-eslint/eslint-plugin": "^7.16.0",
119
+ "@typescript-eslint/parser": "^7.16.0",
120
  "esbuild": "^0.24.2",
121
+ "eslint": "^8.57.0",
122
  "tsx": "^4.20.6",
123
  "typescript": "~5.8.2"
124
  }
125
  },
126
+ "apps/backend/node_modules/@eslint/eslintrc": {
127
+ "version": "2.1.4",
128
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
129
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
130
+ "dev": true,
131
+ "license": "MIT",
132
+ "dependencies": {
133
+ "ajv": "^6.12.4",
134
+ "debug": "^4.3.2",
135
+ "espree": "^9.6.0",
136
+ "globals": "^13.19.0",
137
+ "ignore": "^5.2.0",
138
+ "import-fresh": "^3.2.1",
139
+ "js-yaml": "^4.1.0",
140
+ "minimatch": "^3.1.2",
141
+ "strip-json-comments": "^3.1.1"
142
+ },
143
+ "engines": {
144
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
145
+ },
146
+ "funding": {
147
+ "url": "https://opencollective.com/eslint"
148
+ }
149
+ },
150
+ "apps/backend/node_modules/@eslint/js": {
151
+ "version": "8.57.0",
152
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
153
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
154
+ "dev": true,
155
+ "license": "MIT",
156
+ "engines": {
157
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
158
+ }
159
+ },
160
  "apps/backend/node_modules/@napi-rs/canvas": {
161
  "version": "0.1.84",
162
  "license": "MIT",
 
188
  "undici-types": "~6.21.0"
189
  }
190
  },
191
+ "apps/backend/node_modules/@typescript-eslint/eslint-plugin": {
192
+ "version": "7.16.0",
193
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz",
194
+ "integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==",
195
+ "dev": true,
196
+ "license": "MIT",
197
+ "dependencies": {
198
+ "@eslint-community/regexpp": "^4.10.0",
199
+ "@typescript-eslint/scope-manager": "7.16.0",
200
+ "@typescript-eslint/type-utils": "7.16.0",
201
+ "@typescript-eslint/utils": "7.16.0",
202
+ "@typescript-eslint/visitor-keys": "7.16.0",
203
+ "graphemer": "^1.4.0",
204
+ "ignore": "^5.3.1",
205
+ "natural-compare": "^1.4.0",
206
+ "ts-api-utils": "^1.3.0"
207
+ },
208
+ "engines": {
209
+ "node": "^18.18.0 || >=20.0.0"
210
+ },
211
+ "funding": {
212
+ "type": "opencollective",
213
+ "url": "https://opencollective.com/typescript-eslint"
214
+ },
215
+ "peerDependencies": {
216
+ "@typescript-eslint/parser": "^7.0.0",
217
+ "eslint": "^8.56.0"
218
+ },
219
+ "peerDependenciesMeta": {
220
+ "typescript": {
221
+ "optional": true
222
+ }
223
+ }
224
+ },
225
+ "apps/backend/node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": {
226
+ "version": "1.4.3",
227
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
228
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
229
+ "dev": true,
230
+ "license": "MIT",
231
+ "engines": {
232
+ "node": ">=16"
233
+ },
234
+ "peerDependencies": {
235
+ "typescript": ">=4.2.0"
236
+ }
237
+ },
238
+ "apps/backend/node_modules/@typescript-eslint/parser": {
239
+ "version": "7.16.0",
240
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz",
241
+ "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==",
242
+ "dev": true,
243
+ "license": "BSD-2-Clause",
244
+ "peer": true,
245
+ "dependencies": {
246
+ "@typescript-eslint/scope-manager": "7.16.0",
247
+ "@typescript-eslint/types": "7.16.0",
248
+ "@typescript-eslint/typescript-estree": "7.16.0",
249
+ "@typescript-eslint/visitor-keys": "7.16.0",
250
+ "debug": "^4.3.4"
251
+ },
252
+ "engines": {
253
+ "node": "^18.18.0 || >=20.0.0"
254
+ },
255
+ "funding": {
256
+ "type": "opencollective",
257
+ "url": "https://opencollective.com/typescript-eslint"
258
+ },
259
+ "peerDependencies": {
260
+ "eslint": "^8.56.0"
261
+ },
262
+ "peerDependenciesMeta": {
263
+ "typescript": {
264
+ "optional": true
265
+ }
266
+ }
267
+ },
268
+ "apps/backend/node_modules/@typescript-eslint/scope-manager": {
269
+ "version": "7.16.0",
270
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz",
271
+ "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==",
272
+ "dev": true,
273
+ "license": "MIT",
274
+ "dependencies": {
275
+ "@typescript-eslint/types": "7.16.0",
276
+ "@typescript-eslint/visitor-keys": "7.16.0"
277
+ },
278
+ "engines": {
279
+ "node": "^18.18.0 || >=20.0.0"
280
+ },
281
+ "funding": {
282
+ "type": "opencollective",
283
+ "url": "https://opencollective.com/typescript-eslint"
284
+ }
285
+ },
286
+ "apps/backend/node_modules/@typescript-eslint/type-utils": {
287
+ "version": "7.16.0",
288
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz",
289
+ "integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==",
290
+ "dev": true,
291
+ "license": "MIT",
292
+ "dependencies": {
293
+ "@typescript-eslint/typescript-estree": "7.16.0",
294
+ "@typescript-eslint/utils": "7.16.0",
295
+ "debug": "^4.3.4",
296
+ "ts-api-utils": "^1.3.0"
297
+ },
298
+ "engines": {
299
+ "node": "^18.18.0 || >=20.0.0"
300
+ },
301
+ "funding": {
302
+ "type": "opencollective",
303
+ "url": "https://opencollective.com/typescript-eslint"
304
+ },
305
+ "peerDependencies": {
306
+ "eslint": "^8.56.0"
307
+ },
308
+ "peerDependenciesMeta": {
309
+ "typescript": {
310
+ "optional": true
311
+ }
312
+ }
313
+ },
314
+ "apps/backend/node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": {
315
+ "version": "1.4.3",
316
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
317
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
318
+ "dev": true,
319
+ "license": "MIT",
320
+ "engines": {
321
+ "node": ">=16"
322
+ },
323
+ "peerDependencies": {
324
+ "typescript": ">=4.2.0"
325
+ }
326
+ },
327
+ "apps/backend/node_modules/@typescript-eslint/types": {
328
+ "version": "7.16.0",
329
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz",
330
+ "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==",
331
+ "dev": true,
332
+ "license": "MIT",
333
+ "engines": {
334
+ "node": "^18.18.0 || >=20.0.0"
335
+ },
336
+ "funding": {
337
+ "type": "opencollective",
338
+ "url": "https://opencollective.com/typescript-eslint"
339
+ }
340
+ },
341
+ "apps/backend/node_modules/@typescript-eslint/typescript-estree": {
342
+ "version": "7.16.0",
343
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz",
344
+ "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==",
345
+ "dev": true,
346
+ "license": "BSD-2-Clause",
347
+ "dependencies": {
348
+ "@typescript-eslint/types": "7.16.0",
349
+ "@typescript-eslint/visitor-keys": "7.16.0",
350
+ "debug": "^4.3.4",
351
+ "globby": "^11.1.0",
352
+ "is-glob": "^4.0.3",
353
+ "minimatch": "^9.0.4",
354
+ "semver": "^7.6.0",
355
+ "ts-api-utils": "^1.3.0"
356
+ },
357
+ "engines": {
358
+ "node": "^18.18.0 || >=20.0.0"
359
+ },
360
+ "funding": {
361
+ "type": "opencollective",
362
+ "url": "https://opencollective.com/typescript-eslint"
363
+ },
364
+ "peerDependenciesMeta": {
365
+ "typescript": {
366
+ "optional": true
367
+ }
368
+ }
369
+ },
370
+ "apps/backend/node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
371
+ "version": "2.0.2",
372
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
373
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
374
+ "dev": true,
375
+ "license": "MIT",
376
+ "dependencies": {
377
+ "balanced-match": "^1.0.0"
378
+ }
379
+ },
380
+ "apps/backend/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
381
+ "version": "9.0.5",
382
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
383
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
384
+ "dev": true,
385
+ "license": "ISC",
386
+ "dependencies": {
387
+ "brace-expansion": "^2.0.1"
388
+ },
389
+ "engines": {
390
+ "node": ">=16 || 14 >=14.17"
391
+ },
392
+ "funding": {
393
+ "url": "https://github.com/sponsors/isaacs"
394
+ }
395
+ },
396
+ "apps/backend/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": {
397
+ "version": "1.4.3",
398
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
399
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
400
+ "dev": true,
401
+ "license": "MIT",
402
+ "engines": {
403
+ "node": ">=16"
404
+ },
405
+ "peerDependencies": {
406
+ "typescript": ">=4.2.0"
407
+ }
408
+ },
409
+ "apps/backend/node_modules/@typescript-eslint/utils": {
410
+ "version": "7.16.0",
411
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz",
412
+ "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==",
413
+ "dev": true,
414
+ "license": "MIT",
415
+ "dependencies": {
416
+ "@eslint-community/eslint-utils": "^4.4.0",
417
+ "@typescript-eslint/scope-manager": "7.16.0",
418
+ "@typescript-eslint/types": "7.16.0",
419
+ "@typescript-eslint/typescript-estree": "7.16.0"
420
+ },
421
+ "engines": {
422
+ "node": "^18.18.0 || >=20.0.0"
423
+ },
424
+ "funding": {
425
+ "type": "opencollective",
426
+ "url": "https://opencollective.com/typescript-eslint"
427
+ },
428
+ "peerDependencies": {
429
+ "eslint": "^8.56.0"
430
+ }
431
+ },
432
+ "apps/backend/node_modules/@typescript-eslint/visitor-keys": {
433
+ "version": "7.16.0",
434
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz",
435
+ "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==",
436
+ "dev": true,
437
+ "license": "MIT",
438
+ "dependencies": {
439
+ "@typescript-eslint/types": "7.16.0",
440
+ "eslint-visitor-keys": "^3.4.3"
441
+ },
442
+ "engines": {
443
+ "node": "^18.18.0 || >=20.0.0"
444
+ },
445
+ "funding": {
446
+ "type": "opencollective",
447
+ "url": "https://opencollective.com/typescript-eslint"
448
+ }
449
+ },
450
+ "apps/backend/node_modules/ajv": {
451
+ "version": "6.12.6",
452
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
453
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
454
+ "dev": true,
455
+ "license": "MIT",
456
+ "dependencies": {
457
+ "fast-deep-equal": "^3.1.1",
458
+ "fast-json-stable-stringify": "^2.0.0",
459
+ "json-schema-traverse": "^0.4.1",
460
+ "uri-js": "^4.2.2"
461
+ },
462
+ "funding": {
463
+ "type": "github",
464
+ "url": "https://github.com/sponsors/epoberezkin"
465
+ }
466
+ },
467
+ "apps/backend/node_modules/brace-expansion": {
468
+ "version": "1.1.12",
469
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
470
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
471
+ "dev": true,
472
+ "license": "MIT",
473
+ "dependencies": {
474
+ "balanced-match": "^1.0.0",
475
+ "concat-map": "0.0.1"
476
+ }
477
+ },
478
+ "apps/backend/node_modules/doctrine": {
479
+ "version": "3.0.0",
480
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
481
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
482
+ "dev": true,
483
+ "license": "Apache-2.0",
484
+ "dependencies": {
485
+ "esutils": "^2.0.2"
486
+ },
487
+ "engines": {
488
+ "node": ">=6.0.0"
489
+ }
490
+ },
491
+ "apps/backend/node_modules/eslint": {
492
+ "version": "8.57.0",
493
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
494
+ "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
495
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
496
+ "dev": true,
497
+ "license": "MIT",
498
+ "peer": true,
499
+ "dependencies": {
500
+ "@eslint-community/eslint-utils": "^4.2.0",
501
+ "@eslint-community/regexpp": "^4.6.1",
502
+ "@eslint/eslintrc": "^2.1.4",
503
+ "@eslint/js": "8.57.0",
504
+ "@humanwhocodes/config-array": "^0.11.14",
505
+ "@humanwhocodes/module-importer": "^1.0.1",
506
+ "@nodelib/fs.walk": "^1.2.8",
507
+ "@ungap/structured-clone": "^1.2.0",
508
+ "ajv": "^6.12.4",
509
+ "chalk": "^4.0.0",
510
+ "cross-spawn": "^7.0.2",
511
+ "debug": "^4.3.2",
512
+ "doctrine": "^3.0.0",
513
+ "escape-string-regexp": "^4.0.0",
514
+ "eslint-scope": "^7.2.2",
515
+ "eslint-visitor-keys": "^3.4.3",
516
+ "espree": "^9.6.1",
517
+ "esquery": "^1.4.2",
518
+ "esutils": "^2.0.2",
519
+ "fast-deep-equal": "^3.1.3",
520
+ "file-entry-cache": "^6.0.1",
521
+ "find-up": "^5.0.0",
522
+ "glob-parent": "^6.0.2",
523
+ "globals": "^13.19.0",
524
+ "graphemer": "^1.4.0",
525
+ "ignore": "^5.2.0",
526
+ "imurmurhash": "^0.1.4",
527
+ "is-glob": "^4.0.0",
528
+ "is-path-inside": "^3.0.3",
529
+ "js-yaml": "^4.1.0",
530
+ "json-stable-stringify-without-jsonify": "^1.0.1",
531
+ "levn": "^0.4.1",
532
+ "lodash.merge": "^4.6.2",
533
+ "minimatch": "^3.1.2",
534
+ "natural-compare": "^1.4.0",
535
+ "optionator": "^0.9.3",
536
+ "strip-ansi": "^6.0.1",
537
+ "text-table": "^0.2.0"
538
+ },
539
+ "bin": {
540
+ "eslint": "bin/eslint.js"
541
+ },
542
+ "engines": {
543
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
544
+ },
545
+ "funding": {
546
+ "url": "https://opencollective.com/eslint"
547
+ }
548
+ },
549
+ "apps/backend/node_modules/eslint-scope": {
550
+ "version": "7.2.2",
551
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
552
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
553
+ "dev": true,
554
+ "license": "BSD-2-Clause",
555
+ "dependencies": {
556
+ "esrecurse": "^4.3.0",
557
+ "estraverse": "^5.2.0"
558
+ },
559
+ "engines": {
560
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
561
+ },
562
+ "funding": {
563
+ "url": "https://opencollective.com/eslint"
564
+ }
565
+ },
566
+ "apps/backend/node_modules/espree": {
567
+ "version": "9.6.1",
568
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
569
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
570
+ "dev": true,
571
+ "license": "BSD-2-Clause",
572
+ "dependencies": {
573
+ "acorn": "^8.9.0",
574
+ "acorn-jsx": "^5.3.2",
575
+ "eslint-visitor-keys": "^3.4.1"
576
+ },
577
+ "engines": {
578
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
579
+ },
580
+ "funding": {
581
+ "url": "https://opencollective.com/eslint"
582
+ }
583
+ },
584
+ "apps/backend/node_modules/file-entry-cache": {
585
+ "version": "6.0.1",
586
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
587
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
588
+ "dev": true,
589
+ "license": "MIT",
590
+ "dependencies": {
591
+ "flat-cache": "^3.0.4"
592
+ },
593
+ "engines": {
594
+ "node": "^10.12.0 || >=12.0.0"
595
+ }
596
+ },
597
+ "apps/backend/node_modules/flat-cache": {
598
+ "version": "3.2.0",
599
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
600
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
601
+ "dev": true,
602
+ "license": "MIT",
603
+ "dependencies": {
604
+ "flatted": "^3.2.9",
605
+ "keyv": "^4.5.3",
606
+ "rimraf": "^3.0.2"
607
+ },
608
+ "engines": {
609
+ "node": "^10.12.0 || >=12.0.0"
610
+ }
611
+ },
612
+ "apps/backend/node_modules/globals": {
613
+ "version": "13.24.0",
614
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
615
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
616
+ "dev": true,
617
+ "license": "MIT",
618
+ "dependencies": {
619
+ "type-fest": "^0.20.2"
620
+ },
621
+ "engines": {
622
+ "node": ">=8"
623
+ },
624
+ "funding": {
625
+ "url": "https://github.com/sponsors/sindresorhus"
626
+ }
627
+ },
628
+ "apps/backend/node_modules/ignore": {
629
+ "version": "5.3.2",
630
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
631
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
632
+ "dev": true,
633
+ "license": "MIT",
634
+ "engines": {
635
+ "node": ">= 4"
636
+ }
637
+ },
638
+ "apps/backend/node_modules/json-schema-traverse": {
639
+ "version": "0.4.1",
640
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
641
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
642
+ "dev": true,
643
+ "license": "MIT"
644
+ },
645
+ "apps/backend/node_modules/minimatch": {
646
+ "version": "3.1.2",
647
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
648
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
649
+ "dev": true,
650
+ "license": "ISC",
651
+ "dependencies": {
652
+ "brace-expansion": "^1.1.7"
653
+ },
654
+ "engines": {
655
+ "node": "*"
656
+ }
657
+ },
658
  "apps/backend/node_modules/pdfjs-dist": {
659
  "version": "5.4.449",
660
  "license": "Apache-2.0",
 
665
  "@napi-rs/canvas": "^0.1.81"
666
  }
667
  },
668
+ "apps/backend/node_modules/rimraf": {
669
+ "version": "3.0.2",
670
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
671
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
672
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
673
+ "dev": true,
674
+ "license": "ISC",
675
+ "dependencies": {
676
+ "glob": "^7.1.3"
677
+ },
678
+ "bin": {
679
+ "rimraf": "bin.js"
680
+ },
681
+ "funding": {
682
+ "url": "https://github.com/sponsors/isaacs"
683
+ }
684
+ },
685
+ "apps/backend/node_modules/type-fest": {
686
+ "version": "0.20.2",
687
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
688
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
689
+ "dev": true,
690
+ "license": "(MIT OR CC0-1.0)",
691
+ "engines": {
692
+ "node": ">=10"
693
+ },
694
+ "funding": {
695
+ "url": "https://github.com/sponsors/sindresorhus"
696
+ }
697
+ },
698
  "apps/backend/packages/domain-types": {
699
  "name": "@widget-tdc/domain-types",
700
  "version": "1.0.0",
 
3859
  "node": ">=18.18.0"
3860
  }
3861
  },
3862
+ "node_modules/@humanwhocodes/config-array": {
3863
+ "version": "0.11.14",
3864
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
3865
+ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
3866
+ "deprecated": "Use @eslint/config-array instead",
3867
+ "dev": true,
3868
+ "license": "Apache-2.0",
3869
+ "dependencies": {
3870
+ "@humanwhocodes/object-schema": "^2.0.2",
3871
+ "debug": "^4.3.1",
3872
+ "minimatch": "^3.0.5"
3873
+ },
3874
+ "engines": {
3875
+ "node": ">=10.10.0"
3876
+ }
3877
+ },
3878
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
3879
+ "version": "1.1.12",
3880
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
3881
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
3882
+ "dev": true,
3883
+ "license": "MIT",
3884
+ "dependencies": {
3885
+ "balanced-match": "^1.0.0",
3886
+ "concat-map": "0.0.1"
3887
+ }
3888
+ },
3889
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
3890
+ "version": "3.1.2",
3891
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
3892
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
3893
+ "dev": true,
3894
+ "license": "ISC",
3895
+ "dependencies": {
3896
+ "brace-expansion": "^1.1.7"
3897
+ },
3898
+ "engines": {
3899
+ "node": "*"
3900
+ }
3901
+ },
3902
  "node_modules/@humanwhocodes/module-importer": {
3903
  "version": "1.0.1",
3904
  "dev": true,
 
3911
  "url": "https://github.com/sponsors/nzakas"
3912
  }
3913
  },
3914
+ "node_modules/@humanwhocodes/object-schema": {
3915
+ "version": "2.0.3",
3916
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
3917
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
3918
+ "deprecated": "Use @eslint/object-schema instead",
3919
+ "dev": true,
3920
+ "license": "BSD-3-Clause"
3921
+ },
3922
  "node_modules/@humanwhocodes/retry": {
3923
  "version": "0.4.3",
3924
  "dev": true,
 
8082
  "url": "https://github.com/sponsors/ljharb"
8083
  }
8084
  },
8085
+ "node_modules/array-union": {
8086
+ "version": "2.1.0",
8087
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
8088
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
8089
+ "dev": true,
8090
+ "license": "MIT",
8091
+ "engines": {
8092
+ "node": ">=8"
8093
+ }
8094
+ },
8095
  "node_modules/array.prototype.findlast": {
8096
  "version": "1.2.5",
8097
  "dev": true,
 
10230
  "node": ">=0.3.1"
10231
  }
10232
  },
10233
+ "node_modules/dir-glob": {
10234
+ "version": "3.0.1",
10235
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
10236
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
10237
+ "dev": true,
10238
+ "license": "MIT",
10239
+ "dependencies": {
10240
+ "path-type": "^4.0.0"
10241
+ },
10242
+ "engines": {
10243
+ "node": ">=8"
10244
+ }
10245
+ },
10246
  "node_modules/dlv": {
10247
  "version": "1.1.3",
10248
  "license": "MIT"
 
12490
  "url": "https://github.com/sponsors/ljharb"
12491
  }
12492
  },
12493
+ "node_modules/globby": {
12494
+ "version": "11.1.0",
12495
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
12496
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
12497
+ "dev": true,
12498
+ "license": "MIT",
12499
+ "dependencies": {
12500
+ "array-union": "^2.1.0",
12501
+ "dir-glob": "^3.0.1",
12502
+ "fast-glob": "^3.2.9",
12503
+ "ignore": "^5.2.0",
12504
+ "merge2": "^1.4.1",
12505
+ "slash": "^3.0.0"
12506
+ },
12507
+ "engines": {
12508
+ "node": ">=10"
12509
+ },
12510
+ "funding": {
12511
+ "url": "https://github.com/sponsors/sindresorhus"
12512
+ }
12513
+ },
12514
+ "node_modules/globby/node_modules/ignore": {
12515
+ "version": "5.3.2",
12516
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
12517
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
12518
+ "dev": true,
12519
+ "license": "MIT",
12520
+ "engines": {
12521
+ "node": ">= 4"
12522
+ }
12523
+ },
12524
  "node_modules/gopd": {
12525
  "version": "1.2.0",
12526
  "license": "MIT",
 
13291
  "node": ">=0.10.0"
13292
  }
13293
  },
13294
+ "node_modules/is-path-inside": {
13295
+ "version": "3.0.3",
13296
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
13297
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
13298
+ "dev": true,
13299
+ "license": "MIT",
13300
+ "engines": {
13301
+ "node": ">=8"
13302
+ }
13303
+ },
13304
  "node_modules/is-plain-obj": {
13305
  "version": "4.1.0",
13306
  "license": "MIT",
 
17800
  "node": ">=18"
17801
  }
17802
  },
17803
+ "node_modules/slash": {
17804
+ "version": "3.0.0",
17805
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
17806
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
17807
+ "dev": true,
17808
+ "license": "MIT",
17809
+ "engines": {
17810
+ "node": ">=8"
17811
+ }
17812
+ },
17813
  "node_modules/smart-buffer": {
17814
  "version": "4.2.0",
17815
  "license": "MIT",
 
18655
  "version": "1.0.0",
18656
  "license": "MIT"
18657
  },
18658
+ "node_modules/text-table": {
18659
+ "version": "0.2.0",
18660
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
18661
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
18662
+ "dev": true,
18663
+ "license": "MIT"
18664
+ },
18665
  "node_modules/thenify": {
18666
  "version": "3.3.1",
18667
  "license": "MIT",