UNPKG

@sethdouglasford/claude-flow

Version:

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

223 lines 7.95 kB
/** * Markdown backend implementation for human-readable memory storage */ import { promises as fs } from "fs"; import path from "path"; import { MemoryBackendError } from "../../utils/errors.js"; /** * Markdown-based memory backend */ export class MarkdownBackend { baseDir; logger; entries = new Map(); indexPath; constructor(baseDir, logger) { this.baseDir = baseDir; this.logger = logger; this.indexPath = path.join(this.baseDir, "index.json"); } async initialize() { this.logger.info("Initializing Markdown backend", { baseDir: this.baseDir }); try { // Ensure directories exist await fs.mkdir(this.baseDir, { recursive: true }); await fs.mkdir(path.join(this.baseDir, "agents"), { recursive: true }); await fs.mkdir(path.join(this.baseDir, "sessions"), { recursive: true }); // Load index await this.loadIndex(); this.logger.info("Markdown backend initialized"); } catch (error) { throw new MemoryBackendError("Failed to initialize Markdown backend", { error }); } } async shutdown() { this.logger.info("Shutting down Markdown backend"); // Save index before shutdown await this.saveIndex(); this.entries.clear(); } async store(entry) { try { // Store in memory this.entries.set(entry.id, entry); // Write to markdown file await this.writeEntryToFile(entry); // Update index await this.saveIndex(); } catch (error) { throw new MemoryBackendError("Failed to store entry", { error }); } } async retrieve(id) { return this.entries.get(id); } async update(id, entry) { if (!this.entries.has(id)) { throw new MemoryBackendError(`Entry not found: ${id}`); } await this.store(entry); } async delete(id) { const entry = this.entries.get(id); if (!entry) { return; } try { // Delete from memory this.entries.delete(id); // Delete file const filePath = this.getEntryFilePath(entry); await fs.unlink(filePath); // Update index await this.saveIndex(); } catch (error) { throw new MemoryBackendError("Failed to delete entry", { error }); } } async query(query) { let results = Array.from(this.entries.values()); // Apply filters if (query.agentId) { results = results.filter(e => e.agentId === query.agentId); } if (query.sessionId) { results = results.filter(e => e.sessionId === query.sessionId); } if (query.type) { results = results.filter(e => e.type === query.type); } if (query.tags && query.tags.length > 0) { results = results.filter(e => query.tags.some(tag => e.tags.includes(tag))); } if (query.startTime) { results = results.filter(e => e.timestamp.getTime() >= query.startTime.getTime()); } if (query.endTime) { results = results.filter(e => e.timestamp.getTime() <= query.endTime.getTime()); } if (query.search) { const searchLower = query.search.toLowerCase(); results = results.filter(e => e.content.toLowerCase().includes(searchLower) || e.tags.some(tag => tag.toLowerCase().includes(searchLower))); } // Sort by timestamp (newest first) results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); // Apply pagination const start = query.offset || 0; const limit = query.limit || results.length; results = results.slice(start, start + limit); return results; } async getAllEntries() { return Array.from(this.entries.values()); } async getHealthStatus() { try { // Check if directory is accessible await fs.stat(this.baseDir); const entryCount = this.entries.size; let totalSizeBytes = 0; // Calculate total size for (const entry of this.entries.values()) { const filePath = this.getEntryFilePath(entry); try { const stat = await fs.stat(filePath); totalSizeBytes += stat.size; } catch { // File might not exist yet } } return { healthy: true, metrics: { entryCount, totalSizeBytes, }, }; } catch (error) { return { healthy: false, error: error instanceof Error ? error.message : "Unknown error", }; } } async loadIndex() { try { const content = await fs.readFile(this.indexPath, "utf-8"); const index = JSON.parse(content); // Convert and validate entries for (const [id, entry] of Object.entries(index)) { // Reconstruct dates entry.timestamp = new Date(entry.timestamp); this.entries.set(id, entry); } this.logger.info("Loaded memory index", { entries: this.entries.size }); } catch (error) { if (error.code !== "ENOENT") { this.logger.warn("Failed to load index", { error }); } // Start with empty index if file doesn't exist } } async saveIndex() { const index = {}; for (const [id, entry] of this.entries) { index[id] = entry; } const content = JSON.stringify(index, null, 2); await fs.writeFile(this.indexPath, content, "utf-8"); } async writeEntryToFile(entry) { const filePath = this.getEntryFilePath(entry); const dirPath = path.dirname(filePath); // Ensure directory exists await fs.mkdir(dirPath, { recursive: true }); // Generate markdown content const content = this.entryToMarkdown(entry); // Write file await fs.writeFile(filePath, content, "utf-8"); } getEntryFilePath(entry) { const date = entry.timestamp.toISOString().split("T")[0]; const time = entry.timestamp.toISOString().split("T")[1].replace(/:/g, "-").split(".")[0]; return path.join(this.baseDir, "agents", entry.agentId, date, `${time}_${entry.id}.md`); } entryToMarkdown(entry) { const lines = [ `# Memory Entry: ${entry.id}`, "", `**Agent**: ${entry.agentId}`, `**Session**: ${entry.sessionId}`, `**Type**: ${entry.type}`, `**Timestamp**: ${entry.timestamp.toISOString()}`, `**Version**: ${entry.version}`, "", ]; if (entry.parentId) { lines.push(`**Parent**: ${entry.parentId}`, ""); } if (entry.tags.length > 0) { lines.push(`**Tags**: ${entry.tags.join(", ")}`, ""); } lines.push("## Content", "", entry.content, ""); if (Object.keys(entry.context).length > 0) { lines.push("## Context", "", "```json"); lines.push(JSON.stringify(entry.context, null, 2)); lines.push("```", ""); } if (entry.metadata && Object.keys(entry.metadata).length > 0) { lines.push("## Metadata", "", "```json"); lines.push(JSON.stringify(entry.metadata, null, 2)); lines.push("```", ""); } return lines.join("\n"); } } //# sourceMappingURL=markdown.js.map