UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

380 lines 13.1 kB
/** * Simplified Advanced Memory Management * Removed complex indexing, compression, and overengineered features */ import { EventEmitter } from "node:events"; import { promises as fs } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { generateId } from "../utils/helpers.js"; export class AdvancedMemoryManager extends EventEmitter { storage = new Map(); storageDir; logger; persisted = false; constructor(config = {}) { super(); const __dirname = dirname(fileURLToPath(import.meta.url)); this.storageDir = config.storageDir ?? join(__dirname, "../../../.claude-flow/memory"); this.logger = config.logger; this.persisted = config.persistenceEnabled !== false; } async initialize() { if (this.persisted) { // Create storage directory if it doesn't exist await fs.mkdir(this.storageDir, { recursive: true }); // Load existing data await this.loadFromDisk(); } this.logger?.info("Memory manager initialized", { entriesLoaded: this.storage.size, }); } async store(key, value, options = {}) { const entry = { id: generateId("mem"), key, value, type: options.type ?? "generic", namespace: options.namespace ?? "default", owner: options.owner ?? "system", createdAt: new Date(), updatedAt: new Date(), }; this.storage.set(entry.id, entry); if (this.persisted) { await this.saveToDisk(); } this.emit("stored", entry); return entry.id; } async get(keyOrId) { // Try direct ID lookup first const byId = this.storage.get(keyOrId); if (byId) { return byId.value; } // Then try key lookup for (const entry of this.storage.values()) { if (entry.key === keyOrId) { return entry.value; } } return undefined; } query(options = {}) { let results = Array.from(this.storage.values()); // Apply filters if (options.namespace) { results = results.filter(e => e.namespace === options.namespace); } if (options.type) { results = results.filter(e => e.type === options.type); } if (options.owner) { results = results.filter(e => e.owner === options.owner); } if (options.keyPattern) { const pattern = new RegExp(options.keyPattern); results = results.filter(e => pattern.test(e.key)); } if (options.fullTextSearch) { const search = options.fullTextSearch.toLowerCase(); results = results.filter(e => JSON.stringify(e.value).toLowerCase().includes(search) || e.key.toLowerCase().includes(search)); } // Handle pagination const total = results.length; const offset = options.offset ?? 0; const limit = options.limit ?? results.length; results = results.slice(offset, offset + limit); // Return in expected format for compatibility const queryResult = results; queryResult.total = total; queryResult.entries = () => results.entries(); queryResult.aggregations = {}; return queryResult; } async update(keyOrId, value) { // Try direct ID lookup first let entry = this.storage.get(keyOrId); // Then try key lookup if (!entry) { for (const e of this.storage.values()) { if (e.key === keyOrId) { entry = e; break; } } } if (!entry) { return false; } entry.value = value; entry.updatedAt = new Date(); if (this.persisted) { await this.saveToDisk(); } this.emit("updated", entry); return true; } async delete(keyOrId) { // Try direct ID deletion if (this.storage.delete(keyOrId)) { if (this.persisted) { await this.saveToDisk(); } return true; } // Try key-based deletion for (const [id, entry] of this.storage.entries()) { if (entry.key === keyOrId) { this.storage.delete(id); if (this.persisted) { await this.saveToDisk(); } this.emit("deleted", entry); return true; } } return false; } async clear(namespace) { let deleted = 0; if (!namespace) { deleted = this.storage.size; this.storage.clear(); } else { for (const [id, entry] of this.storage.entries()) { if (entry.namespace === namespace) { this.storage.delete(id); deleted++; } } } if (this.persisted && deleted > 0) { await this.saveToDisk(); } return deleted; } getStatistics() { const namespaces = new Set(); const types = new Set(); for (const entry of this.storage.values()) { namespaces.add(entry.namespace); types.add(entry.type); } const stats = { totalEntries: this.storage.size, namespaces: Array.from(namespaces), types: Array.from(types), }; // Add extended stats for compatibility return { ...stats, overview: stats, distribution: { byNamespace: {}, byType: {}, byOwner: {}, }, temporal: { oldestEntry: null, newestEntry: null, totalDuration: 0, activityPeriods: [], inactivePeriods: [], }, performance: { averageQueryTime: 0, averageWriteTime: 0, cacheHitRate: 1, indexEfficiency: 1, }, health: { status: "healthy", fragmentation: 0, memoryUsage: 0, diskUsage: 0, errors: 0, warnings: 0, }, optimization: { suggestions: [], potentialSavings: 0, recommendedActions: 0, lastOptimized: null, }, }; } // Restore missing methods with minimal implementations export(options = {}) { const entries = Array.from(this.storage.values()); if (options.format === "csv") { // Simple CSV export const headers = "id,key,type,namespace,owner,createdAt,updatedAt"; const rows = entries.map(e => `${e.id},${e.key},${e.type},${e.namespace},${e.owner},${e.createdAt.toISOString()},${e.updatedAt.toISOString()}`); return [headers, ...rows].join("\n"); } // Default to JSON return JSON.stringify(entries, null, options.pretty ? 2 : 0); } async import(data, options = {}) { let entries; try { const parsed = JSON.parse(data); if (!Array.isArray(parsed)) { throw new Error("Data must be an array"); } entries = parsed; } catch { // Try CSV format const lines = data.trim().split("\n"); if (lines.length < 2) throw new Error("Invalid import data"); entries = []; // Skip CSV parsing for simplicity } let imported = 0; let skipped = 0; const conflicts = []; for (const entry of entries) { if (!options.overwrite && this.storage.has(entry.id)) { skipped++; conflicts.push({ entry, reason: "exists" }); continue; } entry.createdAt = new Date(entry.createdAt); entry.updatedAt = new Date(entry.updatedAt); this.storage.set(entry.id, entry); imported++; } if (this.persisted) { await this.saveToDisk(); } return { imported, skipped, conflicts }; } async cleanup(options = {}) { const actions = []; let removed = 0; if (options.dry) { // Dry run - just report what would be done return { removed: 0, actions: [{ type: "dry-run", message: "Would clean up entries" }] }; } // Simple cleanup - remove old entries if maxAge specified if (options.maxAge) { const cutoff = Date.now() - options.maxAge; for (const [id, entry] of this.storage.entries()) { if (entry.updatedAt.getTime() < cutoff) { if (!options.namespace || entry.namespace === options.namespace) { this.storage.delete(id); removed++; actions.push({ type: "remove", id, reason: "age" }); } } } } if (this.persisted && removed > 0) { await this.saveToDisk(); } return { removed, actions }; } // Alias methods for compatibility async retrieve(keyOrId) { const value = await this.get(keyOrId); if (!value) return undefined; // Find the full entry for (const entry of this.storage.values()) { if (entry.id === keyOrId || entry.key === keyOrId) { return entry; } } return undefined; } async deleteEntry(keyOrId) { return this.delete(keyOrId); } listNamespaces() { const namespaces = new Set(); for (const entry of this.storage.values()) { namespaces.add(entry.namespace); } return Array.from(namespaces); } listTypes() { const types = new Set(); for (const entry of this.storage.values()) { types.add(entry.type); } return Array.from(types); } listTags() { // Simple implementation - return empty array since we don't track tags return []; } updateConfiguration(config) { // No-op for simplicity this.logger?.info("Configuration update requested", config); } getConfiguration() { return { storageDir: this.storageDir, persisted: this.persisted, entriesCount: this.storage.size, }; } // Type guard for validating memory entries isValidMemoryEntry(obj) { if (typeof obj !== "object" || obj === null) return false; const candidate = obj; return (typeof candidate.id === "string" && typeof candidate.key === "string" && typeof candidate.type === "string" && typeof candidate.namespace === "string" && typeof candidate.owner === "string" && (typeof candidate.createdAt === "string" || candidate.createdAt instanceof Date) && (typeof candidate.updatedAt === "string" || candidate.updatedAt instanceof Date)); } // Simple persistence methods async loadFromDisk() { try { const dataFile = join(this.storageDir, "memory.json"); const exists = await fs.access(dataFile).then(() => true).catch(() => false); if (exists) { const data = await fs.readFile(dataFile, "utf-8"); const parsed = JSON.parse(data); if (Array.isArray(parsed)) { for (const rawEntry of parsed) { if (this.isValidMemoryEntry(rawEntry)) { // Convert dates back from strings const entry = { ...rawEntry, createdAt: new Date(rawEntry.createdAt), updatedAt: new Date(rawEntry.updatedAt), }; this.storage.set(entry.id, entry); } } } } } catch (error) { this.logger?.error("Failed to load memory from disk", error); } } async saveToDisk() { try { const dataFile = join(this.storageDir, "memory.json"); const entries = Array.from(this.storage.values()); await fs.writeFile(dataFile, JSON.stringify(entries, null, 2)); } catch (error) { this.logger?.error("Failed to save memory to disk", error); } } } //# sourceMappingURL=advanced-memory-manager.js.map