UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

358 lines (357 loc) 9.24 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { logger } from "../../../core/monitoring/logger.js"; import * as zlib from "zlib"; import { promisify } from "util"; const gzip = promisify(zlib.gzip); const gunzip = promisify(zlib.gunzip); class PerformanceOptimizer { config; saveBatch = []; batchTimer; cache = /* @__PURE__ */ new Map(); metrics = { iterationTime: 0, contextLoadTime: 0, stateSaveTime: 0, memoryUsage: 0, tokenCount: 0, cacheHitRate: 0 }; cacheHits = 0; cacheMisses = 0; strategies = []; constructor(config) { this.config = { asyncSaves: config?.asyncSaves ?? true, batchSize: config?.batchSize || 10, compressionLevel: config?.compressionLevel || 2, cacheEnabled: config?.cacheEnabled ?? true, parallelOperations: config?.parallelOperations ?? true }; this.initializeStrategies(); this.startMetricsCollection(); } /** * Save frame with optimizations */ async saveFrame(frame) { const startTime = Date.now(); if (this.config.asyncSaves) { this.addToBatch({ type: "frame", data: frame, timestamp: Date.now() }); } else { await this.saveFrameInternal(frame); } this.metrics.stateSaveTime += Date.now() - startTime; } /** * Save iteration with optimizations */ async saveIteration(iteration) { const startTime = Date.now(); const data = this.config.compressionLevel > 0 ? await this.compressData(iteration) : iteration; if (this.config.asyncSaves) { this.addToBatch({ type: "iteration", data, timestamp: Date.now() }); } else { await this.saveIterationInternal(data); } this.metrics.stateSaveTime += Date.now() - startTime; } /** * Load frames with caching */ async loadFrames(query) { const startTime = Date.now(); const cacheKey = this.generateCacheKey("frames", query); if (this.config.cacheEnabled) { const cached = this.getFromCache(cacheKey); if (cached) { this.cacheHits++; this.metrics.contextLoadTime += Date.now() - startTime; return cached; } } this.cacheMisses++; const frames = await this.loadFramesInternal(query); if (this.config.cacheEnabled) { this.setCache(cacheKey, frames, 6e4); } this.metrics.contextLoadTime += Date.now() - startTime; return frames; } /** * Batch save operations */ async flushBatch() { if (this.saveBatch.length === 0) return; const batch = [...this.saveBatch]; this.saveBatch = []; logger.debug("Flushing batch", { size: batch.length }); if (this.config.parallelOperations) { await Promise.all(batch.map((op) => this.executeSaveOperation(op))); } else { for (const op of batch) { await this.executeSaveOperation(op); } } } /** * Compress data based on compression level */ async compressData(data) { if (this.config.compressionLevel === 0) return data; const json = JSON.stringify(data); const compressionOptions = { level: this.config.compressionLevel * 3 // Map 1-3 to zlib levels 3-9 }; const compressed = await gzip(json, compressionOptions); return { compressed: true, data: compressed.toString("base64"), originalSize: json.length, compressedSize: compressed.length }; } /** * Decompress data */ async decompressData(compressed) { if (!compressed.compressed) return compressed; const buffer = Buffer.from(compressed.data, "base64"); const decompressed = await gunzip(buffer); return JSON.parse(decompressed.toString()); } /** * Apply optimization strategies */ async optimize(operation, data) { let optimized = data; for (const strategy of this.strategies) { if (strategy.enabled) { try { optimized = await strategy.apply(optimized); } catch (error) { logger.error("Optimization strategy failed", { strategy: strategy.name, error: error.message }); } } } return optimized; } /** * Get performance metrics */ getMetrics() { const totalCacheAttempts = this.cacheHits + this.cacheMisses; this.metrics.cacheHitRate = totalCacheAttempts > 0 ? this.cacheHits / totalCacheAttempts : 0; this.metrics.memoryUsage = process.memoryUsage().heapUsed; return { ...this.metrics }; } /** * Clear cache */ clearCache() { const size = this.cache.size; this.cache.clear(); logger.debug("Cache cleared", { entries: size }); } /** * Enable/disable strategy */ setStrategyEnabled(strategyName, enabled) { const strategy = this.strategies.find((s) => s.name === strategyName); if (strategy) { strategy.enabled = enabled; logger.debug("Strategy updated", { name: strategyName, enabled }); } } /** * Cleanup resources */ cleanup() { if (this.batchTimer) { clearTimeout(this.batchTimer); } this.clearCache(); this.saveBatch = []; } /** * Initialize optimization strategies */ initializeStrategies() { this.strategies = [ { name: "deduplication", enabled: true, priority: 1, apply: async (data) => this.deduplicateData(data), metrics: () => this.getMetrics() }, { name: "chunking", enabled: true, priority: 2, apply: async (data) => this.chunkLargeData(data), metrics: () => this.getMetrics() }, { name: "lazy-loading", enabled: this.config.cacheEnabled, priority: 3, apply: async (data) => this.createLazyProxy(data), metrics: () => this.getMetrics() } ]; this.strategies.sort((a, b) => a.priority - b.priority); } /** * Start metrics collection */ startMetricsCollection() { setInterval(() => { const metrics = this.getMetrics(); logger.debug("Performance metrics", metrics); }, 3e4); } /** * Add operation to batch */ addToBatch(operation) { this.saveBatch.push(operation); if (!this.batchTimer) { this.batchTimer = setTimeout(() => { this.flushBatch().catch((error) => { logger.error("Batch flush failed", { error: error.message }); }); this.batchTimer = void 0; }, 1e3); } if (this.saveBatch.length >= this.config.batchSize) { if (this.batchTimer) { clearTimeout(this.batchTimer); this.batchTimer = void 0; } this.flushBatch().catch((error) => { logger.error("Batch flush failed", { error: error.message }); }); } } /** * Execute save operation */ async executeSaveOperation(operation) { switch (operation.type) { case "frame": await this.saveFrameInternal(operation.data); break; case "iteration": await this.saveIterationInternal(operation.data); break; default: logger.warn("Unknown operation type", { type: operation.type }); } } /** * Internal frame save */ async saveFrameInternal(frame) { logger.debug("Frame saved", { frameId: frame.frame_id }); } /** * Internal iteration save */ async saveIterationInternal(iteration) { logger.debug("Iteration saved", { iteration: iteration.number }); } /** * Internal frame load */ async loadFramesInternal(_query) { return []; } /** * Generate cache key */ generateCacheKey(type, params) { return `${type}:${JSON.stringify(params)}`; } /** * Get from cache */ getFromCache(key) { const entry = this.cache.get(key); if (!entry) return void 0; if (entry.expiry && entry.expiry < Date.now()) { this.cache.delete(key); return void 0; } return entry.data; } /** * Set cache entry */ setCache(key, data, ttl) { const entry = { data, timestamp: Date.now(), expiry: ttl ? Date.now() + ttl : void 0 }; this.cache.set(key, entry); if (this.cache.size > 100) { const entries = Array.from(this.cache.entries()); entries.sort((a, b) => a[1].timestamp - b[1].timestamp); for (let i = 0; i < 20; i++) { this.cache.delete(entries[i][0]); } } } /** * Deduplicate data */ deduplicateData(data) { if (Array.isArray(data)) { const seen = /* @__PURE__ */ new Set(); return data.filter((item) => { const key = JSON.stringify(item); if (seen.has(key)) { return false; } seen.add(key); return true; }); } return data; } /** * Chunk large data */ chunkLargeData(data) { const json = JSON.stringify(data); if (json.length > 1e5) { logger.debug("Large data detected", { size: json.length }); } return data; } /** * Create lazy-loading proxy */ createLazyProxy(data) { return data; } } export { PerformanceOptimizer };