@mrxkun/mcfast-mcp 4.1.10 → 4.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "4.1.10",
4
- "description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.10: Suppress console output to prevent JSON-RPC stream corruption.",
3
+ "version": "4.1.11",
4
+ "description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.11: Suppress console output to prevent JSON-RPC stream corruption.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mcfast-mcp": "src/index.js"
package/src/index.js CHANGED
@@ -75,9 +75,87 @@ import { getAuditQueue } from './utils/audit-queue.js';
75
75
  import { getIntelligenceCache } from './utils/intelligence-cache.js';
76
76
  import { getContextPrefetcher } from './utils/context-prefetcher.js';
77
77
  import { parallelMemorySearch } from './utils/parallel-search.js';
78
+ import { execute as projectAnalyzeExecute } from './tools/project_analyze.js';
78
79
 
79
80
  const execAsync = promisify(exec);
80
81
 
82
+ // ============================================================================
83
+ // LOCK FILE MECHANISM - Prevent multiple instances running simultaneously
84
+ // ============================================================================
85
+ const LOCK_FILE_PATH = path.join(process.cwd(), '.mcfast', '.mcp-instance.lock');
86
+ let lockFileHandle = null;
87
+
88
+ async function acquireLock() {
89
+ try {
90
+ const lockDir = path.dirname(LOCK_FILE_PATH);
91
+ await fs.mkdir(lockDir, { recursive: true });
92
+
93
+ lockFileHandle = await fs.open(LOCK_FILE_PATH, 'wx');
94
+
95
+ // Write current PID
96
+ await lockFileHandle.write(`${process.pid}\n${Date.now()}\n`, 0);
97
+ await lockFileHandle.sync();
98
+
99
+ // Cleanup on exit
100
+ process.on('beforeExit', releaseLock);
101
+ process.on('SIGINT', releaseLock);
102
+ process.on('SIGTERM', releaseLock);
103
+ process.on('uncaughtException', releaseLock);
104
+
105
+ console.error(`${colors.cyan}[Lock]${colors.reset} Acquired lock file: ${LOCK_FILE_PATH}`);
106
+ return true;
107
+ } catch (error) {
108
+ if (error.code === 'EEXIST') {
109
+ // Lock file exists, check if it's stale
110
+ try {
111
+ const lockContent = await fs.readFile(LOCK_FILE_PATH, 'utf-8');
112
+ const [pid, timestamp] = lockContent.trim().split('\n');
113
+ const lockAge = Date.now() - parseInt(timestamp);
114
+
115
+ // Check if process is still running (stale lock if > 5 minutes)
116
+ if (lockAge > 5 * 60 * 1000) {
117
+ console.error(`${colors.yellow}[Lock]${colors.reset} Stale lock detected (age: ${Math.round(lockAge / 1000)}s), removing...`);
118
+ await fs.unlink(LOCK_FILE_PATH);
119
+ return acquireLock(); // Retry
120
+ }
121
+
122
+ console.error(`${colors.red}[Lock]${colors.reset} Another instance is already running (PID: ${pid})`);
123
+ console.error(`${colors.yellow}[Lock]${colors.reset} To force start, delete: ${LOCK_FILE_PATH}`);
124
+ return false;
125
+ } catch (readError) {
126
+ console.error(`${colors.red}[Lock]${colors.reset} Failed to read lock file: ${readError.message}`);
127
+ return false;
128
+ }
129
+ }
130
+ console.error(`${colors.red}[Lock]${colors.reset} Failed to acquire lock: ${error.message}`);
131
+ return false;
132
+ }
133
+ }
134
+
135
+ async function releaseLock() {
136
+ if (lockFileHandle) {
137
+ try {
138
+ await lockFileHandle.close();
139
+ lockFileHandle = null;
140
+ } catch (e) {
141
+ // Ignore close errors
142
+ }
143
+ }
144
+
145
+ try {
146
+ await fs.unlink(LOCK_FILE_PATH);
147
+ console.error(`${colors.cyan}[Lock]${colors.reset} Released lock file`);
148
+ } catch (error) {
149
+ if (error.code !== 'ENOENT') {
150
+ console.error(`${colors.yellow}[Lock]${colors.reset} Failed to release lock: ${error.message}`);
151
+ }
152
+ }
153
+ }
154
+
155
+ // ============================================================================
156
+ // END LOCK FILE MECHANISM
157
+ // ============================================================================
158
+
81
159
  const API_URL = "https://mcfast.vercel.app/api/v1";
82
160
  const TOKEN = process.env.MCFAST_TOKEN;
83
161
  const VERBOSE = process.env.MCFAST_VERBOSE !== 'false'; // Default: true
@@ -95,28 +173,49 @@ async function backgroundInitializeMemoryEngine() {
95
173
  if (memoryEngineInitPromise) return memoryEngineInitPromise;
96
174
 
97
175
  memoryEngineInitPromise = (async () => {
98
- try {
99
- memoryEngine = new MemoryEngine({
100
- apiKey: TOKEN,
101
- enableSync: true
102
- });
103
- // Use a shorter timeout for initialization
104
- const initTimeout = new Promise((_, reject) => {
105
- setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
106
- });
107
-
108
- await Promise.race([
109
- memoryEngine.initialize(process.cwd()),
110
- initTimeout
111
- ]);
112
-
113
- memoryEngineReady = true;
114
- console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
115
- } catch (error) {
116
- console.error(`${colors.yellow}[Memory]${colors.reset} Engine initialization failed: ${error.message}`);
117
- // Don't throw - allow partial initialization
118
- memoryEngineReady = false;
176
+ const maxRetries = 2;
177
+ let lastError = null;
178
+
179
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
180
+ try {
181
+ memoryEngine = new MemoryEngine({
182
+ apiKey: TOKEN,
183
+ enableSync: true
184
+ });
185
+
186
+ // Use timeout for initialization
187
+ const initTimeout = new Promise((_, reject) => {
188
+ setTimeout(() => reject(new Error('Memory engine init timeout')), 25000);
189
+ });
190
+
191
+ await Promise.race([
192
+ memoryEngine.initialize(process.cwd()),
193
+ initTimeout
194
+ ]);
195
+
196
+ memoryEngineReady = true;
197
+ console.error(`${colors.cyan}[Memory]${colors.reset} Engine initialized successfully`);
198
+ return memoryEngine;
199
+
200
+ } catch (error) {
201
+ lastError = error;
202
+ console.error(`${colors.yellow}[Memory]${colors.reset} Initialization attempt ${attempt}/${maxRetries} failed: ${error.message}`);
203
+
204
+ if (attempt < maxRetries) {
205
+ // Exponential backoff before retry
206
+ const backoffMs = 1000 * Math.pow(2, attempt - 1);
207
+ console.error(`${colors.yellow}[Memory]${colors.reset} Retrying in ${backoffMs}ms...`);
208
+ await new Promise(resolve => setTimeout(resolve, backoffMs));
209
+ }
210
+ }
119
211
  }
212
+
213
+ // All retries failed - graceful degradation
214
+ console.error(`${colors.yellow}[Memory]${colors.reset} All initialization attempts failed. Running in degraded mode.`);
215
+ console.error(`${colors.yellow}[Memory]${colors.reset} Error: ${lastError?.message || 'Unknown error'}`);
216
+ console.error(`${colors.yellow}[Memory]${colors.reset} Tools requiring memory will be unavailable.`);
217
+ memoryEngineReady = false;
218
+ return null;
120
219
  })();
121
220
 
122
221
  return memoryEngineInitPromise;
@@ -645,6 +744,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
645
744
  },
646
745
  required: ["instruction"]
647
746
  }
747
+ },
748
+ // PROJECT ANALYSIS TOOL
749
+ {
750
+ name: "project_analyze",
751
+ description: "🔍 Analyze project structure using AI (Mercury). Returns: Project Overview, Technologies, API Endpoints, Main Features. Auto-updates MEMORY.md. Requires MCFAST_TOKEN.",
752
+ inputSchema: {
753
+ type: "object",
754
+ properties: {
755
+ force: { type: "boolean", description: "Force re-analysis (ignore cache)" },
756
+ updateMemory: { type: "boolean", description: "Update MEMORY.md with results (default: true)" }
757
+ },
758
+ required: []
759
+ }
648
760
  }
649
761
  ],
650
762
  };
@@ -847,6 +959,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
847
959
  result = await handleSelectStrategy(args);
848
960
  summary = 'Strategy selected';
849
961
  }
962
+ // PROJECT ANALYSIS TOOL
963
+ else if (name === "project_analyze") {
964
+ result = await projectAnalyzeExecute(args);
965
+ summary = 'Project analyzed';
966
+ }
850
967
  else {
851
968
  throw new Error(`Tool not found: ${name}`);
852
969
  }
@@ -3238,12 +3355,50 @@ async function handleSelectStrategy({ instruction, files = [] }) {
3238
3355
  /**
3239
3356
  * Start Server
3240
3357
  */
3358
+
3359
+ // Acquire lock before starting - prevents multiple instances
3360
+ const lockAcquired = await acquireLock();
3361
+ if (!lockAcquired) {
3362
+ console.error(`${colors.red}[ERROR]${colors.reset} Failed to acquire lock. Another mcfast-mcp instance may be running.`);
3363
+ console.error(`${colors.yellow}[HINT]${colors.reset} Delete ${LOCK_FILE_PATH} if you're sure no other instance is running.`);
3364
+ process.exit(1);
3365
+ }
3366
+
3241
3367
  const transport = new StdioServerTransport();
3242
3368
 
3243
3369
  // Pre-initialize memory engine in background
3244
3370
  backgroundInitializeMemoryEngine().catch(err => {
3245
3371
  console.error(`${colors.yellow}[Memory]${colors.reset} Background init error: ${err.message}`);
3372
+ // Don't exit - continue without memory
3246
3373
  });
3247
3374
 
3375
+ // Graceful shutdown handler
3376
+ async function gracefulShutdown(signal) {
3377
+ console.error(`${colors.yellow}[Shutdown]${colors.reset} Received ${signal}, cleaning up...`);
3378
+
3379
+ try {
3380
+ // Stop memory engine
3381
+ if (memoryEngine && memoryEngineReady) {
3382
+ await memoryEngine.cleanup();
3383
+ }
3384
+
3385
+ // Flush audit queue
3386
+ const auditQ = getAuditQueue();
3387
+ await auditQ.destroy();
3388
+
3389
+ // Release lock
3390
+ await releaseLock();
3391
+
3392
+ console.error(`${colors.green}[Shutdown]${colors.reset} Cleanup complete`);
3393
+ } catch (error) {
3394
+ console.error(`${colors.red}[Shutdown]${colors.reset} Error during cleanup: ${error.message}`);
3395
+ }
3396
+
3397
+ process.exit(0);
3398
+ }
3399
+
3400
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
3401
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
3402
+
3248
3403
  await server.connect(transport);
3249
3404
  console.error("mcfast MCP v1.0.0 running on stdio");
@@ -26,6 +26,9 @@ import { CuratedMemory } from './layers/curated-memory.js';
26
26
  // Bootstrap
27
27
  import { AgentsMdBootstrap } from './bootstrap/agents-md.js';
28
28
 
29
+ // Project Analysis (AI-powered)
30
+ import { execute as projectAnalyzeExecute } from '../tools/project_analyze.js';
31
+
29
32
  // Embedders
30
33
  import { UltraEnhancedEmbedder } from './utils/ultra-embedder.js';
31
34
  import { SmartRouter } from './utils/smart-router.js';
@@ -38,6 +41,7 @@ import { PatternDetector, SuggestionEngine, StrategySelector } from '../intellig
38
41
  export class MemoryEngine {
39
42
  static instance = null;
40
43
  static instancePromise = null;
44
+ static instancePid = null; // Track which process owns the instance
41
45
 
42
46
  /**
43
47
  * Get singleton instance of MemoryEngine
@@ -45,8 +49,19 @@ export class MemoryEngine {
45
49
  * @returns {MemoryEngine} Singleton instance
46
50
  */
47
51
  static getInstance(options = {}) {
52
+ const currentPid = process.pid;
53
+
54
+ // Check if instance belongs to current process
55
+ if (MemoryEngine.instancePid !== currentPid) {
56
+ // Different process - reset instance
57
+ MemoryEngine.instance = null;
58
+ MemoryEngine.instancePromise = null;
59
+ MemoryEngine.instancePid = null;
60
+ }
61
+
48
62
  if (!MemoryEngine.instance) {
49
63
  MemoryEngine.instance = new MemoryEngine(options);
64
+ MemoryEngine.instancePid = currentPid;
50
65
  }
51
66
  return MemoryEngine.instance;
52
67
  }
@@ -58,6 +73,16 @@ export class MemoryEngine {
58
73
  * @returns {Promise<MemoryEngine>} Initialized singleton instance
59
74
  */
60
75
  static async getOrCreate(projectPath, options = {}) {
76
+ const currentPid = process.pid;
77
+
78
+ // Check if instance belongs to current process
79
+ if (MemoryEngine.instancePid !== currentPid) {
80
+ // Different process - reset instance
81
+ MemoryEngine.instance = null;
82
+ MemoryEngine.instancePromise = null;
83
+ MemoryEngine.instancePid = null;
84
+ }
85
+
61
86
  if (MemoryEngine.instance && MemoryEngine.instance.isInitialized) {
62
87
  return MemoryEngine.instance;
63
88
  }
@@ -68,6 +93,7 @@ export class MemoryEngine {
68
93
 
69
94
  const engine = new MemoryEngine(options);
70
95
  MemoryEngine.instance = engine;
96
+ MemoryEngine.instancePid = currentPid;
71
97
  MemoryEngine.instancePromise = engine.initialize(projectPath).then(() => engine);
72
98
 
73
99
  return MemoryEngine.instancePromise;
@@ -131,6 +157,9 @@ export class MemoryEngine {
131
157
  this.suggestionEngine = null;
132
158
  this.strategySelector = null;
133
159
 
160
+ // Auto-analyze project on first use
161
+ this.autoAnalyze = options.autoAnalyze !== false;
162
+
134
163
  // Search configuration
135
164
  this.searchConfig = {
136
165
  hybrid: {
@@ -208,7 +237,13 @@ export class MemoryEngine {
208
237
  '**/*.log'
209
238
  ]
210
239
  });
211
- await this.watcher.start();
240
+
241
+ try {
242
+ await this.watcher.start();
243
+ } catch (watcherError) {
244
+ console.error('[MemoryEngine] ⚠️ File watcher failed to start (continuing without file watching):', watcherError.message);
245
+ this.watcher = null;
246
+ }
212
247
 
213
248
  // Perform initial scan
214
249
  await this.performInitialScan();
@@ -223,6 +258,9 @@ export class MemoryEngine {
223
258
  console.error(`[MemoryEngine] Smart Routing: ${this.smartRoutingEnabled ? 'ENABLED' : 'DISABLED'}`);
224
259
  console.error(`[MemoryEngine] Intelligence: ${this.intelligenceEnabled ? 'ENABLED' : 'DISABLED'}`);
225
260
 
261
+ // Auto-analyze project if MCFAST_TOKEN is set
262
+ await this.autoAnalyzeProject();
263
+
226
264
  // Log initialization to daily logs
227
265
  await this.dailyLogs.log('Memory Engine Initialized', `Project: ${projectPath}`, {
228
266
  stats: this.getStats()
@@ -264,6 +302,47 @@ export class MemoryEngine {
264
302
  }
265
303
  }
266
304
 
305
+ /**
306
+ * Auto-analyze project using AI if MCFAST_TOKEN is set
307
+ * Runs in background to not block initialization
308
+ */
309
+ async autoAnalyzeProject() {
310
+ if (!this.autoAnalyze) return;
311
+
312
+ const apiKey = process.env.MCFAST_TOKEN;
313
+ if (!apiKey) {
314
+ console.error('[MemoryEngine] Skipping auto-analysis (no MCFAST_TOKEN)');
315
+ return;
316
+ }
317
+
318
+ // Check if already analyzed (MEMORY.md has Project Context section)
319
+ try {
320
+ const memoryContent = await this.curatedMemory.read();
321
+ if (memoryContent && memoryContent.includes('## Project Context') &&
322
+ !memoryContent.includes('<!-- Add project context here -->')) {
323
+ console.error('[MemoryEngine] Project already analyzed, skipping auto-analysis');
324
+ return;
325
+ }
326
+ } catch (e) {
327
+ // MEMORY.md doesn't exist yet, proceed with analysis
328
+ }
329
+
330
+ // Run analysis in background
331
+ console.error('[MemoryEngine] 🔄 Starting auto-analysis in background...');
332
+
333
+ projectAnalyzeExecute({ force: false, updateMemory: true })
334
+ .then(result => {
335
+ if (result.metadata?.memoryUpdated) {
336
+ console.error('[MemoryEngine] ✅ Auto-analysis complete, MEMORY.md updated');
337
+ } else if (result.isError) {
338
+ console.error('[MemoryEngine] ⚠️ Auto-analysis failed:', result.content?.[0]?.text?.substring(0, 100));
339
+ }
340
+ })
341
+ .catch(err => {
342
+ console.error('[MemoryEngine] ⚠️ Auto-analysis error:', err.message);
343
+ });
344
+ }
345
+
267
346
  async performInitialScan() {
268
347
  if (this.isScanning) return;
269
348
  this.isScanning = true;
@@ -470,24 +549,44 @@ export class MemoryEngine {
470
549
 
471
550
  // ========== Storage Operations ==========
472
551
 
473
- async storeIndexed(indexed) {
474
- this.codebaseDb?.upsertFile?.(indexed.file);
475
- this.codebaseDb?.deleteFactsByFile?.(indexed.file.id);
476
- this.codebaseDb?.deleteChunksByFile?.(indexed.file.id);
552
+ async storeIndexed(indexed, maxRetries = 3) {
553
+ // Retry logic with exponential backoff
554
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
555
+ try {
556
+ this.codebaseDb?.upsertFile?.(indexed.file);
557
+ this.codebaseDb?.deleteFactsByFile?.(indexed.file.id);
558
+ this.codebaseDb?.deleteChunksByFile?.(indexed.file.id);
477
559
 
478
- for (const fact of indexed.facts) {
479
- this.codebaseDb?.insertFact?.(fact);
480
- }
560
+ for (const fact of indexed.facts) {
561
+ this.codebaseDb?.insertFact?.(fact);
562
+ }
481
563
 
482
- for (const chunk of indexed.chunks) {
483
- this.codebaseDb?.insertChunk?.(chunk);
484
- }
564
+ for (const chunk of indexed.chunks) {
565
+ this.codebaseDb?.insertChunk?.(chunk);
566
+ }
567
+
568
+ for (const embedding of indexed.embeddings) {
569
+ this.codebaseDb?.insertEmbedding?.(embedding);
570
+ }
485
571
 
486
- for (const embedding of indexed.embeddings) {
487
- this.codebaseDb?.insertEmbedding?.(embedding);
572
+ return true; // Success
573
+ } catch (error) {
574
+ if (attempt === maxRetries) {
575
+ console.error(`[MemoryEngine] Failed to store indexed data after ${maxRetries} attempts:`, error.message);
576
+ this.codebaseDb?.recordFailedIndex?.(indexed.file.path, error.message);
577
+ return false;
578
+ }
579
+ // Exponential backoff: 100ms, 200ms, 400ms
580
+ await new Promise(resolve => setTimeout(resolve, 100 * Math.pow(2, attempt - 1)));
581
+ }
488
582
  }
489
583
  }
490
584
 
585
+ // Record failed indexing for tracking
586
+ recordFailedIndex(filePath, errorMessage) {
587
+ console.error(`[MemoryEngine] Indexing failed for ${filePath}: ${errorMessage}`);
588
+ }
589
+
491
590
  // ========== Search Operations ==========
492
591
 
493
592
  async searchFacts(query, limit = 20) {
@@ -573,6 +672,80 @@ export class MemoryEngine {
573
672
  }
574
673
 
575
674
  async searchCodebase(query, limit = 20) {
675
+ const startTime = performance.now();
676
+
677
+ // FIX: Get embeddings from both codebaseDb AND memoryDb
678
+ const [codebaseEmbeddings, memoryEmbeddings] = await Promise.all([
679
+ this.codebaseDb?.getAllEmbeddings?.() || [],
680
+ this.memoryDb?.getAllEmbeddings?.() || []
681
+ ]);
682
+
683
+ // Generate query embedding
684
+ const queryResult = this.embedder.embedCode(query);
685
+ const queryEmbedding = queryResult.vector;
686
+
687
+ // Score and tag results from each source
688
+ const scoreFn = (item) => {
689
+ const buf = item.embedding?.buffer || item.embedding;
690
+ const arr = buf ? new Uint8Array(buf) : new Uint8Array(0);
691
+ return this.embedder.cosineSimilarity(queryEmbedding, Array.from(arr));
692
+ };
693
+
694
+ const codebaseResults = (codebaseEmbeddings || []).map(item => ({
695
+ ...item, similarity: scoreFn(item), source: 'codebase', type: 'code'
696
+ }));
697
+
698
+ const memoryResults = (memoryEmbeddings || []).map(item => ({
699
+ ...item, similarity: scoreFn(item), source: 'memory', type: 'memory'
700
+ }));
701
+
702
+ // Combine results
703
+ let combined = [...codebaseResults, ...memoryResults];
704
+
705
+ // Also get FTS results from both databases
706
+ const [codebaseFts, memoryFts] = await Promise.all([
707
+ this.codebaseDb?.searchFTS?.(query, limit * 2) || { results: [] },
708
+ this.memoryDb?.searchFTS?.(query, limit * 2) || { results: [] }
709
+ ]);
710
+
711
+ // Add FTS results
712
+ for (const r of codebaseFts.results || []) {
713
+ combined.push({ ...r, source: 'fts', type: 'code', score: r.score });
714
+ }
715
+ for (const r of memoryFts.results || []) {
716
+ combined.push({ ...r, source: 'fts', type: 'memory', score: r.score });
717
+ }
718
+
719
+ // Deduplicate and sort
720
+ const seen = new Map();
721
+ for (const r of combined) {
722
+ const key = r.chunk_id || r.id;
723
+ if (!seen.has(key) || (r.similarity || r.score || 0) > (seen.get(key).similarity || seen.get(key).score || 0)) {
724
+ seen.set(key, r);
725
+ }
726
+ }
727
+
728
+ const finalResults = Array.from(seen.values())
729
+ .sort((a, b) => (b.similarity || b.score || 0) - (a.similarity || a.score || 0))
730
+ .slice(0, limit);
731
+
732
+ const duration = performance.now() - startTime;
733
+
734
+ return {
735
+ results: finalResults,
736
+ metadata: {
737
+ vectorCandidates: codebaseResults.length + memoryResults.length,
738
+ ftsCandidates: (codebaseFts.results?.length || 0) + (memoryFts.results?.length || 0),
739
+ totalCandidates: combined.length,
740
+ codebaseResults: codebaseResults.length,
741
+ memoryResults: memoryResults.length,
742
+ duration: duration.toFixed(2) + 'ms'
743
+ }
744
+ };
745
+ }
746
+
747
+ // Legacy search - kept for backward compatibility
748
+ async _searchCodebaseLegacy(query, limit = 20) {
576
749
  // Search codebase using existing methods
577
750
  const vectorResults = await this.searchVector(query, limit * 2);
578
751
  const ftsResults = this.codebaseDb?.searchFTS?.(query, limit * 2);
@@ -781,6 +954,33 @@ export class MemoryEngine {
781
954
  this.isInitialized = false;
782
955
  console.error('[MemoryEngine] Shutdown complete');
783
956
  }
957
+
958
+ async cleanup() {
959
+ // Alias for shutdown for consistency
960
+ return this.shutdown();
961
+ }
962
+ }
963
+
964
+ // Auto-register cleanup handlers for graceful shutdown
965
+ if (typeof process !== 'undefined') {
966
+ const cleanupHandler = async (signal) => {
967
+ console.error(`[MemoryEngine] Received ${signal}, performing cleanup...`);
968
+ if (MemoryEngine.instance) {
969
+ try {
970
+ await MemoryEngine.instance.shutdown();
971
+ } catch (error) {
972
+ console.error(`[MemoryEngine] Error during shutdown: ${error.message}`);
973
+ }
974
+ }
975
+ };
976
+
977
+ // Register handlers only once
978
+ if (!process._memoryEngineCleanupRegistered) {
979
+ process.on('beforeExit', () => cleanupHandler('beforeExit'));
980
+ process.on('SIGINT', () => cleanupHandler('SIGINT'));
981
+ process.on('SIGTERM', () => cleanupHandler('SIGTERM'));
982
+ process._memoryEngineCleanupRegistered = true;
983
+ }
784
984
  }
785
985
 
786
986
  export default MemoryEngine;