@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
380 lines • 13.1 kB
JavaScript
/**
* 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