/** * MemoryManager - Implementation of IElementManager for Memory elements * Handles CRUD operations and lifecycle management for memories implementing IElement * * FIXES IMPLEMENTED: * 1. CRITICAL: Fixed race conditions in file operations by using FileLockManager for atomic reads/writes * 2. HIGH: Fixed unvalidated YAML parsing vulnerability by using SecureYamlParser * 3. MEDIUM: All user inputs are now validated and sanitized * 4. MEDIUM: Audit logging added for security operations * 5. MEDIUM: Path validation prevents directory traversal attacks */ import { Memory, MemoryMetadata } from './Memory.js'; import { ElementValidationResult } from '../../types/elements/IElement.js'; import { ElementType } from '../../portfolio/types.js'; import { BaseElementManager } from '../base/BaseElementManager.js'; import type { IStorageLayer } from '../../storage/IStorageLayer.js'; import { FileLockManager } from '../../security/fileLockManager.js'; import { ValidationRegistry } from '../../services/validation/ValidationRegistry.js'; import { SerializationService } from '../../services/SerializationService.js'; import { MetadataService } from '../../services/MetadataService.js'; import { FileOperationsService } from '../../services/FileOperationsService.js'; import { FileWatchService } from '../../services/FileWatchService.js'; import { PortfolioManager } from '../../portfolio/PortfolioManager.js'; export declare class MemoryManager extends BaseElementManager { private metadataService; private readonly memoriesDir; private contentHashIndex; private contentHashByPath; private triggerValidationService; private validationService; private serializationService; private activeMemoryNames; constructor(portfolioManager: PortfolioManager, fileLockManager: FileLockManager, fileOperationsService: FileOperationsService, validationRegistry: ValidationRegistry, serializationService: SerializationService, metadataService: MetadataService, fileWatchService?: FileWatchService, memoryBudget?: import('../../cache/CacheMemoryBudget.js').CacheMemoryBudget, backupService?: import('../../services/BackupService.js').BackupService); /** * Phase 2: Override factory to use MemoryStorageLayer for multi-directory scanning. */ protected createStorageLayer(fileOperationsService: FileOperationsService): IStorageLayer; protected getElementLabel(): string; protected createBackupBeforeSave(): Promise; protected createBackupBeforeDelete(): Promise; /** * Override clearCache to also clear memory-specific caches. * Phase 2: Removed unbounded memoryCache and dateFoldersCache. */ clearCache(): void; /** * Override dispose to flush MemoryStorageLayer _index.json and clear caches. * Prevents resource leaks and "Jest did not exit" warnings in tests. */ dispose(): void; /** * Resolve memory file path across different storage locations * Searches in this order: system/, adapters/, date folders, root (legacy) * Extracted method to reduce cognitive complexity (PR #7) * @private */ private resolveMemoryPath; /** * Load a memory from file * SECURITY FIX #1: Uses FileLockManager.atomicReadFile() instead of fs.readFile() * to prevent race conditions and ensure atomic file operations * @param filePath Path to the memory file to load * @returns Promise resolving to the loaded Memory instance * @throws {Error} When file cannot be found or path validation fails * @throws {Error} When YAML parsing fails or content is malformed * @throws {Error} When memory validation fails after loading */ load(filePath: string): Promise; /** * Move existing memory file to user backup directory * Issue #49: Prevents duplicate files by backing up instead of versioning */ private moveToUserBackup; /** * Prune excess backups for a specific memory in a date folder. * Keeps only the N most recent backups (by timestamp in filename). * Issue #654: Prevents unbounded backup growth (was 197k+ files). */ private pruneBackupsForMemory; /** * Generate path for memory storage based on memory type * Routes memories to appropriate folders: * - SYSTEM: system/ * - ADAPTER: adapters/ * - USER: YYYY-MM-DD/ (date-based folders) * @param element Memory element to save * @param memoryType Type of memory (defaults to USER) * @param fileName Optional custom filename * @returns Full path to memory file */ private generateMemoryPath; /** * Determine memory type from file path * @param filePath Relative path within memories directory * @returns Memory type based on path */ private getMemoryTypeFromPath; /** * Ensure all required folder structure exists * Creates system/, backups/system/, backups/user/, adapters/ folders */ private ensureFolderStructure; /** * Calculate SHA-256 hash of memory content for deduplication * Implements Issue #994 - Content-based deduplication */ private calculateContentHash; /** * Get all date folders in memories directory. * Phase 2: Removed local cache — MemoryStorageLayer scan cooldown handles the hot path. * This method is now only used by resolveMemoryPath() as a fallback. * @returns Array of date folder names */ private getDateFolders; /** * Save a memory to file * SECURITY FIX #1: Uses FileLockManager.atomicWriteFile() for atomic operations * @param element Memory element to save * @param filePath Optional custom file path, defaults to type-based path * @returns Promise that resolves when save is complete * @throws {Error} When memory validation fails before saving * @throws {Error} When path validation fails or file system errors occur * @throws {Error} When atomic write operation fails */ save(element: Memory, filePath?: string): Promise; /** * Issue #2329: Serialize and run the full save-path validation without writing to disk. * Lets callers reject a mutation immediately (e.g. addEntry) when the memory can no * longer be persisted, instead of the failure surfacing only in a deferred save * where it cannot be reported back to the caller. */ assertPersistable(element: Memory): Promise; /** * Validation applied to serialized memory YAML before any disk write. * Shared by save() and assertPersistable() so the pre-flight check and the * actual write can never disagree. */ private validateSerializedMemoryYaml; /** * Handle memory load failure * FIX (SonarCloud): Extract duplicated error handling to reduce code duplication * @private */ private handleLoadFailure; /** * List all available memories from all locations. * Phase 2: Delegates multi-directory scanning to MemoryStorageLayer. * Storage layer handles: system/, adapters/, date folders, root, backup filtering, cooldown. * * Issue #18 Phase 4: Apply active status to memories that are in the active set. */ list(): Promise; /** * Activate a memory by name or identifier * Issue #18 Phase 4: Context-loading activation strategy * Issue #24 (LOW PRIORITY): Performance optimization using findByName() * Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages * Issue #24 (LOW PRIORITY): Cleanup trigger for memory leak prevention */ activateMemory(identifier: string): Promise<{ success: boolean; message: string; memory?: Memory; }>; /** * Deactivate a memory by name or identifier * Issue #18 Phase 4: Remove from active set * Issue #24 (LOW PRIORITY): Performance optimization using findByName() * Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages */ deactivateMemory(identifier: string): Promise<{ success: boolean; message: string; }>; /** * Get all active memories * Issue #18 Phase 4: Return memories that are in the active set */ getActiveMemories(): Promise; /** * Issue #39: Scan all memories and repair corrupted backup names * This method loads all memories, checks for corrupted names, repairs them, * and saves the corrected files back to disk. * * @param onProgress Optional callback for progress updates during long operations * @returns Object with repair statistics and list of repaired memories */ repairCorruptedNames(onProgress?: (processed: number, total: number, current?: string) => void): Promise<{ scanned: number; repaired: number; errors: number; repairedMemories: Array<{ original: string; repaired: string; path: string; }>; errorDetails: Array<{ name: string; error: string; }>; }>; /** * Issue #39: Clean up excessive backup files in a directory * Removes versioned backup files (e.g., name.backup-...-v2.yaml, -v3.yaml, etc.) * keeping only the most recent backup (without version suffix) * * @param targetDir Directory to clean up (defaults to system/ folder) * @param dryRun If true, only reports what would be deleted without actually deleting * @param onProgress Optional callback for progress updates during long operations * @returns Object with cleanup statistics */ cleanupExcessiveBackups(targetDir?: string, dryRun?: boolean, onProgress?: (processed: number, total: number, current?: string) => void): Promise<{ scanned: number; deleted: number; errors: number; deletedFiles: string[]; keptFiles: string[]; errorDetails: Array<{ file: string; error: string; }>; }>; /** * Check if active set cleanup is needed and perform cleanup if necessary * Issue #24 (LOW PRIORITY): Memory leak prevention * @private */ private checkAndCleanupActiveSet; /** * Clean up stale entries from active memories set * Issue #24 (LOW PRIORITY): Memory leak prevention * @private */ private cleanupStaleActiveMemories; /** * Check root files for load failures * FIX (SonarCloud S3776): Extract to reduce cognitive complexity * @private */ private checkRootFiles; /** * Check date folder files for load failures * FIX (SonarCloud S3776): Extract to reduce cognitive complexity * @private */ private checkDateFolderFiles; /** * Get diagnostic information about memory loading status * FIX (#1206): New method to expose failed loads to users */ getLoadStatus(): Promise<{ total: number; loaded: number; failed: number; failures: Array<{ file: string; error: string; }>; }>; /** * Get memories marked for auto-loading on server initialization. * Phase 2: Queries index for autoLoad entries instead of loading all memories. * Drops from ~70s to <30ms for typical portfolios with 14k memories. * Issue #1430: Auto-load baseline memories feature * * @returns Promise resolving to array of auto-load memories sorted by priority */ getAutoLoadMemories(): Promise; /** * Install seed memories from the seed-elements directory * Issue #1430: Copy baseline memory to user portfolio on first run * * Copies seed files from src/seed-elements/memories/ to the user's portfolio * if they don't already exist. This allows users to have baseline knowledge * available immediately without manual installation. * * @returns Promise resolving when installation is complete */ installSeedMemories(): Promise; /** * Find memories matching a predicate */ find(predicate: (element: Memory) => boolean): Promise; /** * Find multiple memories matching a predicate */ findMany(predicate: (element: Memory) => boolean): Promise; /** * Delete a memory file * SECURITY: Validates path and logs deletion */ delete(filePath: string): Promise; /** * Check if a memory file exists */ exists(filePath: string): Promise; /** * Create a new memory with metadata */ create(metadata: Partial & { content?: string; instructions?: string; }): Promise; /** * Parse YAML data for import * Extracted to reduce cognitive complexity (SonarCloud) * @private */ private parseYamlForImport; /** * Create memory from parsed data * Extracted to reduce cognitive complexity (SonarCloud) * @private */ private createMemoryFromParsed; /** * Import a memory from JSON/YAML string * SECURITY: Full validation of imported content * @param data JSON or YAML string containing memory data * @param format Format of the input data ('json' or 'yaml') * @returns Promise resolving to the imported Memory instance * @throws {Error} When JSON/YAML parsing fails * @throws {Error} When imported data is missing required fields * @throws {Error} When YAML content exceeds maximum allowed size * @throws {Error} When imported memory fails validation */ importElement(data: string, format?: 'json' | 'yaml' | 'markdown'): Promise; /** * Export a memory to YAML string */ exportElement(element: Memory, format?: 'json' | 'yaml' | 'markdown'): Promise; protected parseMetadata(data: any): Promise; protected createElement(metadata: MemoryMetadata, _content: string): Memory; protected serializeElement(element: Memory): Promise; /** * Validate a memory element */ validate(element: Memory): ElementValidationResult; /** * Validate and resolve a file path * SECURITY: Prevents directory traversal attacks */ validatePath(filePath: string): boolean; /** * Get the element type this manager handles */ getElementType(): ElementType; /** * Get the file extension for memory files */ getFileExtension(): string; /** * Validate and resolve a file path to prevent security issues * @param filePath Path to validate and resolve * @returns Promise resolving to the validated full path * @throws {Error} When path contains traversal attempts (../) * @throws {Error} When path is absolute or invalid * @throws {Error} When file extension is not allowed (.md, .yaml, .yml) * @throws {Error} When resolved path would be outside memories directory */ private validateAndResolvePath; private parseMemoryFile; /** * Helper to parse retention days from various formats */ private parseRetentionDays; /** * Estimate tokens in memory content (rough approximation) * Uses 1 token ≈ 0.75 words for English text * @param content Memory content to estimate * @returns Estimated token count */ estimateTokens(content: string): number; /** * Load and activate auto-load memories during server initialization * Issue #1430: Auto-load baseline memories feature * * This method is called by Container.preparePortfolio() during server startup * to automatically load memories marked with autoLoad: true * * Architecture Note: * This implementation follows the DI-aligned pattern where managers own all operations * for their element type. Auto-load logic lives in MemoryManager (not ServerStartup) * because MemoryManager already owns all memory operations (CRUD, activation, etc.) * * @returns Promise resolving to auto-load statistics */ loadAndActivateAutoLoadMemories(): Promise<{ loaded: number; skipped: number; totalTokens: number; errors: string[]; }>; } //# sourceMappingURL=MemoryManager.d.ts.map