@danielsimonjr/memoryjs 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../node_modules/tsup/assets/esm_shims.js","../../src/utils/searchCache.ts","../../src/utils/indexes.ts","../../src/utils/errors.ts","../../src/utils/errorSuggestions.ts","../../src/utils/constants.ts","../../src/utils/compressionUtil.ts","../../src/utils/compressedCache.ts","../../src/utils/logger.ts","../../src/utils/searchAlgorithms.ts","../../src/utils/schemas.ts","../../src/utils/formatters.ts","../../src/utils/entityUtils.ts","../../src/utils/parallelUtils.ts","../../src/utils/taskScheduler.ts","../../src/utils/operationUtils.ts","../../src/utils/WorkerPoolManager.ts","../../src/utils/BatchProcessor.ts","../../src/utils/MemoryMonitor.ts","../../src/utils/relationHelpers.ts","../../src/utils/relationValidation.ts","../../src/utils/EntityValidator.ts","../../src/utils/validators.ts","../../src/utils/SchemaValidator.ts","../../src/utils/index.ts","../../src/features/StreamingExporter.ts","../../src/features/IOManager.ts","../../src/core/TransactionManager.ts","../../src/core/GraphEventEmitter.ts","../../src/core/GraphStorage.ts","../../src/core/SQLiteStorage.ts","../../src/core/StorageFactory.ts","../../src/core/EntityManager.ts","../../src/core/RelationManager.ts","../../src/core/ObservationManager.ts","../../src/core/HierarchyManager.ts","../../src/core/GraphTraversal.ts","../../src/search/SearchFilterChain.ts","../../src/search/BasicSearch.ts","../../src/search/TFIDFIndexManager.ts","../../src/search/RankedSearch.ts","../../src/search/BooleanSearch.ts","../../src/search/FuzzySearch.ts","../../src/search/SearchSuggestions.ts","../../src/search/SavedSearchManager.ts","../../src/search/QueryCostEstimator.ts","../../src/search/SearchManager.ts","../../src/search/EmbeddingService.ts","../../src/search/EmbeddingCache.ts","../../src/search/IncrementalIndexer.ts","../../src/search/VectorStore.ts","../../src/search/SemanticSearch.ts","../../src/search/TFIDFEventSync.ts","../../src/search/SymbolicSearch.ts","../../src/search/HybridSearchManager.ts","../../src/search/QueryAnalyzer.ts","../../src/search/QueryPlanner.ts","../../src/search/ReflectionManager.ts","../../src/search/BM25Search.ts","../../src/search/OptimizedInvertedIndex.ts","../../src/search/HybridScorer.ts","../../src/search/ParallelSearchExecutor.ts","../../src/search/EarlyTerminationManager.ts","../../src/search/QueryPlanCache.ts","../../src/search/QuantizedVectorStore.ts","../../src/search/QueryLogger.ts","../../src/search/QueryParser.ts","../../src/search/ProximitySearch.ts","../../src/search/index.ts","../../src/features/TagManager.ts","../../src/features/AnalyticsManager.ts","../../src/features/CompressionManager.ts","../../src/features/ArchiveManager.ts","../../src/agent/AccessTracker.ts","../../src/types/agent-memory.ts","../../src/agent/DecayEngine.ts","../../src/agent/DecayScheduler.ts","../../src/agent/SummarizationService.ts","../../src/agent/SalienceEngine.ts","../../src/agent/ContextWindowManager.ts","../../src/agent/MemoryFormatter.ts","../../src/agent/WorkingMemoryManager.ts","../../src/agent/SessionManager.ts","../../src/agent/EpisodicMemoryManager.ts","../../src/agent/PatternDetector.ts","../../src/agent/RuleEvaluator.ts","../../src/agent/ConsolidationPipeline.ts","../../src/agent/ConflictResolver.ts","../../src/agent/MultiAgentMemoryManager.ts","../../src/agent/AgentMemoryConfig.ts","../../src/agent/AgentMemoryManager.ts","../../src/core/ManagerContext.ts","../../src/cli/interactive.ts","../../src/cli/index.ts","../../src/cli/commands/index.ts","../../src/cli/options.ts","../../src/cli/config.ts","../../src/cli/formatters.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","/**\r\n * Search Result Cache\r\n *\r\n * Simple LRU-style cache for search results with TTL support.\r\n * Improves performance for repeated queries without external dependencies.\r\n *\r\n * @module utils/searchCache\r\n */\r\n\r\nimport type { SearchResult, KnowledgeGraph } from '../types/index.js';\r\n\r\n/**\r\n * Cache entry with expiration.\r\n */\r\ninterface CacheEntry<T> {\r\n value: T;\r\n timestamp: number;\r\n expiresAt: number;\r\n}\r\n\r\n/**\r\n * Cache statistics for monitoring.\r\n */\r\nexport interface CacheStats {\r\n hits: number;\r\n misses: number;\r\n size: number;\r\n hitRate: number;\r\n}\r\n\r\n/**\r\n * Simple LRU cache implementation for search results.\r\n *\r\n * Features:\r\n * - Maximum size limit (LRU eviction when full)\r\n * - TTL-based expiration\r\n * - Cache statistics tracking\r\n * - Hash-based key generation from query parameters\r\n */\r\nexport class SearchCache<T = SearchResult[] | KnowledgeGraph> {\r\n private cache: Map<string, CacheEntry<T>> = new Map();\r\n private accessOrder: string[] = [];\r\n private hits = 0;\r\n private misses = 0;\r\n\r\n constructor(\r\n private maxSize: number = 500,\r\n private ttlMs: number = 5 * 60 * 1000 // 5 minutes default\r\n ) {}\r\n\r\n /**\r\n * Generate cache key from query parameters.\r\n */\r\n private generateKey(params: Record<string, unknown>): string {\r\n // Sort keys for consistent hashing\r\n const sorted = Object.keys(params)\r\n .sort()\r\n .map(key => `${key}:${JSON.stringify(params[key])}`)\r\n .join('|');\r\n return sorted;\r\n }\r\n\r\n /**\r\n * Get value from cache.\r\n *\r\n * @param params - Query parameters to generate cache key\r\n * @returns Cached value or undefined if not found/expired\r\n */\r\n get(params: Record<string, unknown>): T | undefined {\r\n const key = this.generateKey(params);\r\n const entry = this.cache.get(key);\r\n\r\n if (!entry) {\r\n this.misses++;\r\n return undefined;\r\n }\r\n\r\n // Check expiration\r\n if (Date.now() > entry.expiresAt) {\r\n this.cache.delete(key);\r\n this.removeFromAccessOrder(key);\r\n this.misses++;\r\n return undefined;\r\n }\r\n\r\n // Update access order (move to end = most recently used)\r\n this.removeFromAccessOrder(key);\r\n this.accessOrder.push(key);\r\n this.hits++;\r\n\r\n return entry.value;\r\n }\r\n\r\n /**\r\n * Set value in cache.\r\n *\r\n * @param params - Query parameters to generate cache key\r\n * @param value - Value to cache\r\n */\r\n set(params: Record<string, unknown>, value: T): void {\r\n const key = this.generateKey(params);\r\n\r\n // Remove old entry if exists\r\n if (this.cache.has(key)) {\r\n this.removeFromAccessOrder(key);\r\n }\r\n\r\n // Evict least recently used if at capacity\r\n if (this.cache.size >= this.maxSize && !this.cache.has(key)) {\r\n const lruKey = this.accessOrder.shift();\r\n if (lruKey) {\r\n this.cache.delete(lruKey);\r\n }\r\n }\r\n\r\n // Add new entry\r\n this.cache.set(key, {\r\n value,\r\n timestamp: Date.now(),\r\n expiresAt: Date.now() + this.ttlMs,\r\n });\r\n this.accessOrder.push(key);\r\n }\r\n\r\n /**\r\n * Invalidate all cached entries.\r\n */\r\n clear(): void {\r\n this.cache.clear();\r\n this.accessOrder = [];\r\n }\r\n\r\n /**\r\n * Remove specific entry from access order.\r\n */\r\n private removeFromAccessOrder(key: string): void {\r\n const index = this.accessOrder.indexOf(key);\r\n if (index > -1) {\r\n this.accessOrder.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Get cache statistics.\r\n */\r\n getStats(): CacheStats {\r\n const total = this.hits + this.misses;\r\n return {\r\n hits: this.hits,\r\n misses: this.misses,\r\n size: this.cache.size,\r\n hitRate: total > 0 ? this.hits / total : 0,\r\n };\r\n }\r\n\r\n /**\r\n * Reset cache statistics.\r\n */\r\n resetStats(): void {\r\n this.hits = 0;\r\n this.misses = 0;\r\n }\r\n\r\n /**\r\n * Clean up expired entries.\r\n *\r\n * Should be called periodically to prevent memory buildup.\r\n */\r\n cleanupExpired(): void {\r\n const now = Date.now();\r\n const keysToDelete: string[] = [];\r\n\r\n for (const [key, entry] of this.cache.entries()) {\r\n if (now > entry.expiresAt) {\r\n keysToDelete.push(key);\r\n }\r\n }\r\n\r\n for (const key of keysToDelete) {\r\n this.cache.delete(key);\r\n this.removeFromAccessOrder(key);\r\n }\r\n }\r\n\r\n /**\r\n * Get current cache size.\r\n */\r\n get size(): number {\r\n return this.cache.size;\r\n }\r\n\r\n /**\r\n * Check if cache has entry for params.\r\n */\r\n has(params: Record<string, unknown>): boolean {\r\n const key = this.generateKey(params);\r\n const entry = this.cache.get(key);\r\n\r\n if (!entry) return false;\r\n\r\n // Check expiration\r\n if (Date.now() > entry.expiresAt) {\r\n this.cache.delete(key);\r\n this.removeFromAccessOrder(key);\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n}\r\n\r\n/**\r\n * Global search caches for different search types.\r\n */\r\nexport const searchCaches = {\r\n basic: new SearchCache<KnowledgeGraph>(),\r\n ranked: new SearchCache<SearchResult[]>(),\r\n boolean: new SearchCache<KnowledgeGraph>(),\r\n fuzzy: new SearchCache<KnowledgeGraph>(),\r\n};\r\n\r\n/**\r\n * Clear all search caches.\r\n *\r\n * Should be called when graph is modified to ensure cache consistency.\r\n */\r\nexport function clearAllSearchCaches(): void {\r\n searchCaches.basic.clear();\r\n searchCaches.ranked.clear();\r\n searchCaches.boolean.clear();\r\n searchCaches.fuzzy.clear();\r\n}\r\n\r\n/**\r\n * Get combined statistics for all caches.\r\n */\r\nexport function getAllCacheStats(): Record<string, CacheStats> {\r\n return {\r\n basic: searchCaches.basic.getStats(),\r\n ranked: searchCaches.ranked.getStats(),\r\n boolean: searchCaches.boolean.getStats(),\r\n fuzzy: searchCaches.fuzzy.getStats(),\r\n };\r\n}\r\n\r\n/**\r\n * Clean up expired entries in all caches.\r\n */\r\nexport function cleanupAllCaches(): void {\r\n searchCaches.basic.cleanupExpired();\r\n searchCaches.ranked.cleanupExpired();\r\n searchCaches.boolean.cleanupExpired();\r\n searchCaches.fuzzy.cleanupExpired();\r\n}\r\n","/**\r\n * Search Indexes\r\n *\r\n * Provides O(1) lookup structures to avoid repeated linear scans.\r\n * - NameIndex: O(1) entity lookup by name\r\n * - TypeIndex: O(1) entities by type\r\n * - LowercaseCache: Pre-computed lowercase strings to avoid repeated toLowerCase()\r\n * - RelationIndex: O(1) relation lookup by entity name (from/to)\r\n * - ObservationIndex: O(1) observation word lookup by entity\r\n *\r\n * @module utils/indexes\r\n */\r\n\r\nimport type { Entity, LowercaseData, Relation } from '../types/index.js';\r\n\r\n/**\r\n * NameIndex provides O(1) entity lookup by name.\r\n *\r\n * Uses a Map internally for constant-time access.\r\n */\r\nexport class NameIndex {\r\n private index: Map<string, Entity> = new Map();\r\n\r\n /**\r\n * Build the index from an array of entities.\r\n * Clears any existing index data first.\r\n */\r\n build(entities: Entity[]): void {\r\n this.index.clear();\r\n for (const entity of entities) {\r\n this.index.set(entity.name, entity);\r\n }\r\n }\r\n\r\n /**\r\n * Get an entity by name in O(1) time.\r\n */\r\n get(name: string): Entity | undefined {\r\n return this.index.get(name);\r\n }\r\n\r\n /**\r\n * Add a single entity to the index.\r\n */\r\n add(entity: Entity): void {\r\n this.index.set(entity.name, entity);\r\n }\r\n\r\n /**\r\n * Remove an entity from the index by name.\r\n */\r\n remove(name: string): void {\r\n this.index.delete(name);\r\n }\r\n\r\n /**\r\n * Check if an entity exists in the index.\r\n */\r\n has(name: string): boolean {\r\n return this.index.has(name);\r\n }\r\n\r\n /**\r\n * Get the number of entities in the index.\r\n */\r\n get size(): number {\r\n return this.index.size;\r\n }\r\n\r\n /**\r\n * Clear all entries from the index.\r\n */\r\n clear(): void {\r\n this.index.clear();\r\n }\r\n}\r\n\r\n/**\r\n * TypeIndex provides O(1) lookup of entities by type.\r\n *\r\n * Uses a Map<type, Set<entityName>> structure for efficient type queries.\r\n * Type comparisons are case-insensitive.\r\n */\r\nexport class TypeIndex {\r\n private index: Map<string, Set<string>> = new Map();\r\n\r\n /**\r\n * Build the index from an array of entities.\r\n * Clears any existing index data first.\r\n */\r\n build(entities: Entity[]): void {\r\n this.index.clear();\r\n for (const entity of entities) {\r\n this.addToIndex(entity.name, entity.entityType);\r\n }\r\n }\r\n\r\n /**\r\n * Get all entity names of a given type in O(1) time.\r\n * Type comparison is case-insensitive.\r\n */\r\n getNames(entityType: string): Set<string> {\r\n const typeLower = entityType.toLowerCase();\r\n return this.index.get(typeLower) ?? new Set();\r\n }\r\n\r\n /**\r\n * Add an entity to the type index.\r\n */\r\n add(entity: Entity): void {\r\n this.addToIndex(entity.name, entity.entityType);\r\n }\r\n\r\n /**\r\n * Remove an entity from the type index.\r\n * Requires the entity type to know which bucket to remove from.\r\n */\r\n remove(entityName: string, entityType: string): void {\r\n const typeLower = entityType.toLowerCase();\r\n const names = this.index.get(typeLower);\r\n if (names) {\r\n names.delete(entityName);\r\n if (names.size === 0) {\r\n this.index.delete(typeLower);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Update an entity's type in the index.\r\n * Removes from old type and adds to new type.\r\n */\r\n updateType(entityName: string, oldType: string, newType: string): void {\r\n this.remove(entityName, oldType);\r\n this.addToIndex(entityName, newType);\r\n }\r\n\r\n /**\r\n * Get all unique types in the index.\r\n */\r\n getTypes(): string[] {\r\n return Array.from(this.index.keys());\r\n }\r\n\r\n /**\r\n * Clear all entries from the index.\r\n */\r\n clear(): void {\r\n this.index.clear();\r\n }\r\n\r\n private addToIndex(entityName: string, entityType: string): void {\r\n const typeLower = entityType.toLowerCase();\r\n let names = this.index.get(typeLower);\r\n if (!names) {\r\n names = new Set();\r\n this.index.set(typeLower, names);\r\n }\r\n names.add(entityName);\r\n }\r\n}\r\n\r\n/**\r\n * LowercaseCache pre-computes lowercase versions of all searchable fields.\r\n *\r\n * Eliminates the need for repeated toLowerCase() calls during search,\r\n * which is expensive with many entities and observations.\r\n */\r\nexport class LowercaseCache {\r\n private cache: Map<string, LowercaseData> = new Map();\r\n\r\n /**\r\n * Build the cache from an array of entities.\r\n * Clears any existing cache data first.\r\n */\r\n build(entities: Entity[]): void {\r\n this.cache.clear();\r\n for (const entity of entities) {\r\n this.cache.set(entity.name, this.computeLowercase(entity));\r\n }\r\n }\r\n\r\n /**\r\n * Get pre-computed lowercase data for an entity.\r\n */\r\n get(entityName: string): LowercaseData | undefined {\r\n return this.cache.get(entityName);\r\n }\r\n\r\n /**\r\n * Add or update an entity in the cache.\r\n */\r\n set(entity: Entity): void {\r\n this.cache.set(entity.name, this.computeLowercase(entity));\r\n }\r\n\r\n /**\r\n * Remove an entity from the cache.\r\n */\r\n remove(entityName: string): void {\r\n this.cache.delete(entityName);\r\n }\r\n\r\n /**\r\n * Check if an entity exists in the cache.\r\n */\r\n has(entityName: string): boolean {\r\n return this.cache.has(entityName);\r\n }\r\n\r\n /**\r\n * Clear all entries from the cache.\r\n */\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n /**\r\n * Get the number of entries in the cache.\r\n */\r\n get size(): number {\r\n return this.cache.size;\r\n }\r\n\r\n private computeLowercase(entity: Entity): LowercaseData {\r\n return {\r\n name: entity.name.toLowerCase(),\r\n entityType: entity.entityType.toLowerCase(),\r\n observations: entity.observations.map(o => o.toLowerCase()),\r\n tags: entity.tags?.map(t => t.toLowerCase()) ?? [],\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * RelationIndex provides O(1) lookup of relations by entity name.\r\n *\r\n * Maintains two separate indexes for efficient directional queries:\r\n * - fromIndex: Map from source entity name to its outgoing relations\r\n * - toIndex: Map from target entity name to its incoming relations\r\n *\r\n * This eliminates O(n) array scans when looking up relations for an entity.\r\n */\r\nexport class RelationIndex {\r\n /** Index of relations by source (from) entity */\r\n private fromIndex: Map<string, Set<Relation>> = new Map();\r\n\r\n /** Index of relations by target (to) entity */\r\n private toIndex: Map<string, Set<Relation>> = new Map();\r\n\r\n /**\r\n * Build the index from an array of relations.\r\n * Clears any existing index data first.\r\n */\r\n build(relations: Relation[]): void {\r\n this.fromIndex.clear();\r\n this.toIndex.clear();\r\n for (const relation of relations) {\r\n this.addToIndexes(relation);\r\n }\r\n }\r\n\r\n /**\r\n * Get all relations where the entity is the source (outgoing relations).\r\n * Returns empty array if no relations found.\r\n */\r\n getRelationsFrom(entityName: string): Relation[] {\r\n const relations = this.fromIndex.get(entityName);\r\n return relations ? Array.from(relations) : [];\r\n }\r\n\r\n /**\r\n * Get all relations where the entity is the target (incoming relations).\r\n * Returns empty array if no relations found.\r\n */\r\n getRelationsTo(entityName: string): Relation[] {\r\n const relations = this.toIndex.get(entityName);\r\n return relations ? Array.from(relations) : [];\r\n }\r\n\r\n /**\r\n * Get all relations involving the entity (both incoming and outgoing).\r\n * Returns empty array if no relations found.\r\n */\r\n getRelationsFor(entityName: string): Relation[] {\r\n const fromRelations = this.fromIndex.get(entityName);\r\n const toRelations = this.toIndex.get(entityName);\r\n\r\n // Combine sets to handle self-referential relations correctly\r\n const combined = new Set<Relation>();\r\n if (fromRelations) {\r\n for (const r of fromRelations) {\r\n combined.add(r);\r\n }\r\n }\r\n if (toRelations) {\r\n for (const r of toRelations) {\r\n combined.add(r);\r\n }\r\n }\r\n return Array.from(combined);\r\n }\r\n\r\n /**\r\n * Add a single relation to the index.\r\n */\r\n add(relation: Relation): void {\r\n this.addToIndexes(relation);\r\n }\r\n\r\n /**\r\n * Remove a relation from the index.\r\n * Matches by from, to, and relationType.\r\n */\r\n remove(relation: Relation): void {\r\n // Remove from fromIndex\r\n const fromRelations = this.fromIndex.get(relation.from);\r\n if (fromRelations) {\r\n for (const r of fromRelations) {\r\n if (r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType) {\r\n fromRelations.delete(r);\r\n break;\r\n }\r\n }\r\n if (fromRelations.size === 0) {\r\n this.fromIndex.delete(relation.from);\r\n }\r\n }\r\n\r\n // Remove from toIndex\r\n const toRelations = this.toIndex.get(relation.to);\r\n if (toRelations) {\r\n for (const r of toRelations) {\r\n if (r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType) {\r\n toRelations.delete(r);\r\n break;\r\n }\r\n }\r\n if (toRelations.size === 0) {\r\n this.toIndex.delete(relation.to);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Remove all relations involving a specific entity.\r\n * Returns the relations that were removed.\r\n */\r\n removeAllForEntity(entityName: string): Relation[] {\r\n const removed: Relation[] = [];\r\n\r\n // Remove outgoing relations\r\n const fromRelations = this.fromIndex.get(entityName);\r\n if (fromRelations) {\r\n for (const r of fromRelations) {\r\n removed.push(r);\r\n // Also remove from toIndex\r\n const toRels = this.toIndex.get(r.to);\r\n if (toRels) {\r\n toRels.delete(r);\r\n if (toRels.size === 0) {\r\n this.toIndex.delete(r.to);\r\n }\r\n }\r\n }\r\n this.fromIndex.delete(entityName);\r\n }\r\n\r\n // Remove incoming relations\r\n const toRelations = this.toIndex.get(entityName);\r\n if (toRelations) {\r\n for (const r of toRelations) {\r\n // Skip self-referential relations (already handled above)\r\n if (r.from === entityName) continue;\r\n removed.push(r);\r\n // Also remove from fromIndex\r\n const fromRels = this.fromIndex.get(r.from);\r\n if (fromRels) {\r\n fromRels.delete(r);\r\n if (fromRels.size === 0) {\r\n this.fromIndex.delete(r.from);\r\n }\r\n }\r\n }\r\n this.toIndex.delete(entityName);\r\n }\r\n\r\n return removed;\r\n }\r\n\r\n /**\r\n * Check if any relations exist for an entity.\r\n */\r\n hasRelations(entityName: string): boolean {\r\n return this.fromIndex.has(entityName) || this.toIndex.has(entityName);\r\n }\r\n\r\n /**\r\n * Get count of outgoing relations for an entity.\r\n */\r\n getOutgoingCount(entityName: string): number {\r\n return this.fromIndex.get(entityName)?.size ?? 0;\r\n }\r\n\r\n /**\r\n * Get count of incoming relations for an entity.\r\n */\r\n getIncomingCount(entityName: string): number {\r\n return this.toIndex.get(entityName)?.size ?? 0;\r\n }\r\n\r\n /**\r\n * Get total count of unique relations in the index.\r\n */\r\n get size(): number {\r\n // Count all relations in fromIndex (each relation appears exactly once there)\r\n let count = 0;\r\n for (const relations of this.fromIndex.values()) {\r\n count += relations.size;\r\n }\r\n return count;\r\n }\r\n\r\n /**\r\n * Clear all entries from the index.\r\n */\r\n clear(): void {\r\n this.fromIndex.clear();\r\n this.toIndex.clear();\r\n }\r\n\r\n private addToIndexes(relation: Relation): void {\r\n // Add to fromIndex\r\n let fromSet = this.fromIndex.get(relation.from);\r\n if (!fromSet) {\r\n fromSet = new Set();\r\n this.fromIndex.set(relation.from, fromSet);\r\n }\r\n fromSet.add(relation);\r\n\r\n // Add to toIndex\r\n let toSet = this.toIndex.get(relation.to);\r\n if (!toSet) {\r\n toSet = new Set();\r\n this.toIndex.set(relation.to, toSet);\r\n }\r\n toSet.add(relation);\r\n }\r\n}\r\n\r\n/**\r\n * Inverted index mapping observation keywords to entity names.\r\n * Enables O(1) lookup for 'which entities mention word X?' queries.\r\n * Words are normalized to lowercase and split on whitespace/punctuation.\r\n */\r\nexport class ObservationIndex {\r\n private index: Map<string, Set<string>> = new Map();\r\n private entityObservations: Map<string, Set<string>> = new Map();\r\n\r\n /**\r\n * Add an entity's observations to the index.\r\n * Tokenizes observations into words and creates reverse mapping.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param observations - Array of observation strings\r\n */\r\n add(entityName: string, observations: string[]): void {\r\n const entityWords = new Set<string>();\r\n\r\n for (const observation of observations) {\r\n const words = this.tokenize(observation);\r\n for (const word of words) {\r\n entityWords.add(word);\r\n if (!this.index.has(word)) {\r\n this.index.set(word, new Set());\r\n }\r\n this.index.get(word)!.add(entityName);\r\n }\r\n }\r\n\r\n this.entityObservations.set(entityName, entityWords);\r\n }\r\n\r\n /**\r\n * Remove an entity from the index.\r\n * Cleans up all word mappings for this entity.\r\n *\r\n * @param entityName - Name of the entity to remove\r\n */\r\n remove(entityName: string): void {\r\n const words = this.entityObservations.get(entityName);\r\n if (!words) return;\r\n\r\n for (const word of words) {\r\n const entities = this.index.get(word);\r\n if (entities) {\r\n entities.delete(entityName);\r\n if (entities.size === 0) {\r\n this.index.delete(word);\r\n }\r\n }\r\n }\r\n\r\n this.entityObservations.delete(entityName);\r\n }\r\n\r\n /**\r\n * Get all entities that have observations containing the given word.\r\n * Word matching is case-insensitive.\r\n *\r\n * @param word - Word to search for\r\n * @returns Set of entity names containing this word\r\n */\r\n getEntitiesWithWord(word: string): Set<string> {\r\n return this.index.get(word.toLowerCase()) ?? new Set();\r\n }\r\n\r\n /**\r\n * Get all entities that have observations containing ANY of the given words (union).\r\n *\r\n * @param words - Array of words to search for\r\n * @returns Set of entity names containing any of the words\r\n */\r\n getEntitiesWithAnyWord(words: string[]): Set<string> {\r\n const result = new Set<string>();\r\n for (const word of words) {\r\n const entities = this.getEntitiesWithWord(word);\r\n for (const entity of entities) {\r\n result.add(entity);\r\n }\r\n }\r\n return result;\r\n }\r\n\r\n /**\r\n * Get all entities that have observations containing ALL of the given words (intersection).\r\n *\r\n * @param words - Array of words that must all be present\r\n * @returns Set of entity names containing all of the words\r\n */\r\n getEntitiesWithAllWords(words: string[]): Set<string> {\r\n if (words.length === 0) return new Set();\r\n\r\n let result = new Set(this.getEntitiesWithWord(words[0]));\r\n\r\n for (let i = 1; i < words.length && result.size > 0; i++) {\r\n const wordEntities = this.getEntitiesWithWord(words[i]);\r\n result = new Set([...result].filter(e => wordEntities.has(e)));\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Clear all entries from the index.\r\n */\r\n clear(): void {\r\n this.index.clear();\r\n this.entityObservations.clear();\r\n }\r\n\r\n /**\r\n * Get statistics about the index.\r\n *\r\n * @returns Object with wordCount and entityCount\r\n */\r\n getStats(): { wordCount: number; entityCount: number } {\r\n return {\r\n wordCount: this.index.size,\r\n entityCount: this.entityObservations.size,\r\n };\r\n }\r\n\r\n /**\r\n * Tokenize text into searchable words.\r\n * Normalizes to lowercase, splits on non-alphanumeric characters,\r\n * and filters out words less than 2 characters.\r\n *\r\n * @param text - Text to tokenize\r\n * @returns Array of normalized words\r\n */\r\n private tokenize(text: string): string[] {\r\n return text\r\n .toLowerCase()\r\n .split(/[^a-z0-9]+/)\r\n .filter(word => word.length >= 2);\r\n }\r\n}\r\n","/**\r\n * Custom Error Types\r\n *\r\n * Defines custom error classes for better error handling and debugging.\r\n * Phase 1 Sprint 10: Enhanced with ErrorCode enum and suggestions.\r\n *\r\n * @module utils/errors\r\n */\r\n\r\n/**\r\n * Error codes for programmatic handling.\r\n * Phase 1 Sprint 10: Centralized error codes.\r\n */\r\nexport enum ErrorCode {\r\n // Validation errors\r\n VALIDATION_FAILED = 'VALIDATION_ERROR',\r\n REQUIRED_FIELD_MISSING = 'REQUIRED_FIELD_MISSING',\r\n INVALID_FIELD_VALUE = 'INVALID_FIELD_VALUE',\r\n SCHEMA_VALIDATION_FAILED = 'SCHEMA_VALIDATION_FAILED',\r\n\r\n // Storage errors\r\n STORAGE_READ_FAILED = 'STORAGE_READ_FAILED',\r\n STORAGE_WRITE_FAILED = 'STORAGE_WRITE_FAILED',\r\n ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND',\r\n RELATION_NOT_FOUND = 'RELATION_NOT_FOUND',\r\n DUPLICATE_ENTITY = 'DUPLICATE_ENTITY',\r\n STORAGE_CORRUPTED = 'STORAGE_CORRUPTED',\r\n FILE_OPERATION_ERROR = 'FILE_OPERATION_ERROR',\r\n\r\n // Search errors\r\n SEARCH_FAILED = 'SEARCH_FAILED',\r\n INVALID_QUERY = 'INVALID_QUERY',\r\n INDEX_NOT_READY = 'INDEX_NOT_READY',\r\n EMBEDDING_FAILED = 'EMBEDDING_FAILED',\r\n\r\n // Configuration errors\r\n INVALID_CONFIG = 'INVALID_CONFIG',\r\n MISSING_DEPENDENCY = 'MISSING_DEPENDENCY',\r\n UNSUPPORTED_FEATURE = 'UNSUPPORTED_FEATURE',\r\n\r\n // Operation errors\r\n CYCLE_DETECTED = 'CYCLE_DETECTED',\r\n INVALID_IMPORTANCE = 'INVALID_IMPORTANCE',\r\n INSUFFICIENT_ENTITIES = 'INSUFFICIENT_ENTITIES',\r\n OPERATION_CANCELLED = 'OPERATION_CANCELLED',\r\n IMPORT_ERROR = 'IMPORT_ERROR',\r\n EXPORT_ERROR = 'EXPORT_ERROR',\r\n\r\n // Generic\r\n UNKNOWN_ERROR = 'UNKNOWN_ERROR',\r\n}\r\n\r\n/**\r\n * Options for enhanced error construction.\r\n */\r\nexport interface ErrorOptions {\r\n /** Additional context for debugging */\r\n context?: Record<string, unknown>;\r\n /** Recovery suggestions for the user */\r\n suggestions?: string[];\r\n /** Original error that caused this one */\r\n cause?: Error;\r\n}\r\n\r\n/**\r\n * Base error class for all knowledge graph errors.\r\n * Extends the native Error class with additional context.\r\n * Phase 1 Sprint 10: Enhanced with suggestions and context.\r\n */\r\nexport class KnowledgeGraphError extends Error {\r\n /** Error code for programmatic handling */\r\n readonly code: string;\r\n /** Additional context for debugging */\r\n readonly context?: Record<string, unknown>;\r\n /** Recovery suggestions for the user */\r\n readonly suggestions: string[];\r\n\r\n constructor(message: string, code?: string, options?: ErrorOptions) {\r\n super(message);\r\n this.name = 'KnowledgeGraphError';\r\n this.code = code || ErrorCode.UNKNOWN_ERROR;\r\n this.context = options?.context;\r\n this.suggestions = options?.suggestions || [];\r\n\r\n if (options?.cause) {\r\n this.cause = options.cause;\r\n }\r\n\r\n // Maintains proper stack trace in V8 engines\r\n if (Error.captureStackTrace) {\r\n Error.captureStackTrace(this, this.constructor);\r\n }\r\n }\r\n\r\n /**\r\n * Get a formatted error message with suggestions.\r\n */\r\n getDetailedMessage(): string {\r\n let msg = `[${this.code}] ${this.message}`;\r\n\r\n if (this.context && Object.keys(this.context).length > 0) {\r\n msg += `\\nContext: ${JSON.stringify(this.context, null, 2)}`;\r\n }\r\n\r\n if (this.suggestions.length > 0) {\r\n msg += `\\nSuggestions:\\n${this.suggestions.map((s) => ` - ${s}`).join('\\n')}`;\r\n }\r\n\r\n return msg;\r\n }\r\n\r\n /**\r\n * Convert to a plain object for serialization.\r\n */\r\n toJSON(): Record<string, unknown> {\r\n return {\r\n name: this.name,\r\n code: this.code,\r\n message: this.message,\r\n context: this.context,\r\n suggestions: this.suggestions,\r\n stack: this.stack,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when an entity is not found.\r\n */\r\nexport class EntityNotFoundError extends KnowledgeGraphError {\r\n constructor(entityName: string) {\r\n super(`Entity \"${entityName}\" not found`, ErrorCode.ENTITY_NOT_FOUND, {\r\n context: { entityName },\r\n suggestions: [\r\n 'Check that the entity name is spelled correctly',\r\n 'Use searchManager.search() to find similar entities',\r\n 'Verify the entity was created before accessing it',\r\n ],\r\n });\r\n this.name = 'EntityNotFoundError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when a relation is not found.\r\n */\r\nexport class RelationNotFoundError extends KnowledgeGraphError {\r\n constructor(from: string, to: string, relationType?: string) {\r\n const desc = relationType\r\n ? `Relation \"${from}\" --[${relationType}]--> \"${to}\"`\r\n : `Relation from \"${from}\" to \"${to}\"`;\r\n super(`${desc} not found`, ErrorCode.RELATION_NOT_FOUND, {\r\n context: { from, to, relationType },\r\n suggestions: [\r\n 'Verify both entities exist before creating relations',\r\n 'Check the relation type spelling',\r\n 'Use relationManager.getRelations() to list existing relations',\r\n ],\r\n });\r\n this.name = 'RelationNotFoundError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when attempting to create a duplicate entity.\r\n */\r\nexport class DuplicateEntityError extends KnowledgeGraphError {\r\n constructor(entityName: string) {\r\n super(`Entity \"${entityName}\" already exists`, ErrorCode.DUPLICATE_ENTITY, {\r\n context: { entityName },\r\n suggestions: [\r\n 'Use a different entity name',\r\n 'Use updateEntity() to modify the existing entity',\r\n 'Delete the existing entity first if replacement is intended',\r\n ],\r\n });\r\n this.name = 'DuplicateEntityError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when validation fails.\r\n * Note: Maintains backward-compatible signature with errors: string[].\r\n */\r\nexport class ValidationError extends KnowledgeGraphError {\r\n constructor(\r\n message: string,\r\n public readonly errors: string[]\r\n ) {\r\n super(message, ErrorCode.VALIDATION_FAILED, {\r\n context: { validationErrors: errors },\r\n suggestions: [\r\n 'Check the validation errors for specific field issues',\r\n 'Ensure all required fields are provided',\r\n 'Verify field values match expected types and constraints',\r\n ],\r\n });\r\n this.name = 'ValidationError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when a cycle is detected in hierarchies.\r\n */\r\nexport class CycleDetectedError extends KnowledgeGraphError {\r\n constructor(entityName: string, parentName: string) {\r\n super(\r\n `Setting parent \"${parentName}\" for entity \"${entityName}\" would create a cycle`,\r\n ErrorCode.CYCLE_DETECTED,\r\n {\r\n context: { entityName, parentName },\r\n suggestions: [\r\n 'Review the parent-child relationship being created',\r\n 'Check for existing ancestor relationships',\r\n 'Use getAncestors() to verify hierarchy before setting parent',\r\n ],\r\n }\r\n );\r\n this.name = 'CycleDetectedError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when an invalid importance value is provided.\r\n */\r\nexport class InvalidImportanceError extends KnowledgeGraphError {\r\n constructor(value: number, min: number = 0, max: number = 10) {\r\n super(`Importance must be between ${min} and ${max}, got ${value}`, ErrorCode.INVALID_IMPORTANCE, {\r\n context: { value, min, max },\r\n suggestions: [\r\n 'Importance values must be between 0 and 10',\r\n 'Use a decimal value like 7.5 for fine-grained control',\r\n 'Default importance is 5 if not specified',\r\n ],\r\n });\r\n this.name = 'InvalidImportanceError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when a file operation fails.\r\n */\r\nexport class FileOperationError extends KnowledgeGraphError {\r\n constructor(operation: string, filePath: string, cause?: Error) {\r\n super(\r\n `Failed to ${operation} file: ${filePath}${cause ? ` - ${cause.message}` : ''}`,\r\n ErrorCode.FILE_OPERATION_ERROR,\r\n {\r\n context: { operation, filePath },\r\n suggestions: [\r\n 'Check file permissions (read/write)',\r\n 'Verify the file path is valid',\r\n 'Ensure the file is not locked by another process',\r\n ],\r\n cause,\r\n }\r\n );\r\n this.name = 'FileOperationError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when an import operation fails.\r\n */\r\nexport class ImportError extends KnowledgeGraphError {\r\n constructor(format: string, message: string) {\r\n super(`Import failed (${format}): ${message}`, ErrorCode.IMPORT_ERROR, {\r\n context: { format },\r\n suggestions: [\r\n 'Verify the import data format is correct',\r\n 'Check for syntax errors in the import file',\r\n 'Ensure all required fields are present in imported data',\r\n ],\r\n });\r\n this.name = 'ImportError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when an export operation fails.\r\n */\r\nexport class ExportError extends KnowledgeGraphError {\r\n constructor(format: string, message: string) {\r\n super(`Export failed (${format}): ${message}`, ErrorCode.EXPORT_ERROR, {\r\n context: { format },\r\n suggestions: [\r\n 'Check write permissions for the export path',\r\n 'Verify sufficient disk space is available',\r\n 'Try a different export format',\r\n ],\r\n });\r\n this.name = 'ExportError';\r\n }\r\n}\r\n\r\n/**\r\n * Error thrown when insufficient entities are provided for an operation.\r\n */\r\nexport class InsufficientEntitiesError extends KnowledgeGraphError {\r\n constructor(operation: string, required: number, provided: number) {\r\n super(\r\n `${operation} requires at least ${required} entities, got ${provided}`,\r\n ErrorCode.INSUFFICIENT_ENTITIES,\r\n {\r\n context: { operation, required, provided },\r\n suggestions: [\r\n 'Ensure you have created enough entities before the operation',\r\n 'Check the minimum entity requirements for this operation',\r\n 'Create additional entities if needed',\r\n ],\r\n }\r\n );\r\n this.name = 'InsufficientEntitiesError';\r\n }\r\n}\r\n\r\n/**\r\n * Phase 9B: Error thrown when an operation is cancelled via AbortSignal.\r\n *\r\n * @example\r\n * ```typescript\r\n * const controller = new AbortController();\r\n * try {\r\n * await manager.createEntities(entities, { signal: controller.signal });\r\n * } catch (error) {\r\n * if (error instanceof OperationCancelledError) {\r\n * console.log('Operation was cancelled');\r\n * }\r\n * }\r\n * ```\r\n */\r\nexport class OperationCancelledError extends KnowledgeGraphError {\r\n constructor(operation?: string) {\r\n const message = operation\r\n ? `Operation '${operation}' was cancelled`\r\n : 'Operation was cancelled';\r\n super(message, ErrorCode.OPERATION_CANCELLED, {\r\n context: operation ? { operation } : undefined,\r\n suggestions: [\r\n 'The operation was cancelled via AbortSignal',\r\n 'Retry the operation if cancellation was unintended',\r\n 'Check for timeout settings that may have triggered cancellation',\r\n ],\r\n });\r\n this.name = 'OperationCancelledError';\r\n }\r\n}\r\n","/**\n * Error Suggestion Generator\n *\n * Provides context-specific suggestions for error recovery.\n * Phase 1 Sprint 10: Progress Callbacks and Error Improvements.\n *\n * @module utils/errorSuggestions\n */\n\nimport { ErrorCode } from './errors.js';\n\n/**\n * Generate context-specific suggestions based on error code and context.\n */\nexport function generateSuggestions(\n code: ErrorCode | string,\n context?: Record<string, unknown>\n): string[] {\n const suggestions: string[] = [];\n\n switch (code) {\n case ErrorCode.ENTITY_NOT_FOUND:\n suggestions.push(\n 'Check that the entity name is spelled correctly',\n 'Use searchManager.search() to find similar entities',\n 'Verify the entity was created before accessing it'\n );\n if (context?.entityName) {\n suggestions.push(`Searched for: \"${context.entityName}\"`);\n }\n break;\n\n case ErrorCode.RELATION_NOT_FOUND:\n suggestions.push(\n 'Verify both entities exist before creating relations',\n 'Check the relation type spelling',\n 'Use relationManager.getRelations() to list existing relations'\n );\n break;\n\n case ErrorCode.DUPLICATE_ENTITY:\n suggestions.push(\n 'Use a different entity name',\n 'Use updateEntity() to modify the existing entity',\n 'Delete the existing entity first if replacement is intended'\n );\n break;\n\n case ErrorCode.STORAGE_READ_FAILED:\n suggestions.push(\n 'Check that the storage file exists at the specified path',\n 'Verify read permissions on the file and parent directory',\n 'Ensure the file is not locked by another process'\n );\n if (context?.path) {\n suggestions.push(`Path: ${context.path}`);\n }\n break;\n\n case ErrorCode.STORAGE_WRITE_FAILED:\n suggestions.push(\n 'Check write permissions on the storage directory',\n 'Ensure sufficient disk space is available',\n 'Verify the file is not read-only'\n );\n break;\n\n case ErrorCode.STORAGE_CORRUPTED:\n suggestions.push(\n 'Try restoring from a backup file',\n 'Check for file corruption (unexpected characters)',\n 'Consider creating a new storage file and re-importing data'\n );\n break;\n\n case ErrorCode.INVALID_QUERY:\n suggestions.push(\n 'Check query syntax for unmatched quotes or parentheses',\n 'Escape special characters if searching literal text',\n 'Use simpler query terms to isolate the issue'\n );\n if (context?.query) {\n suggestions.push(`Query: \"${context.query}\"`);\n }\n break;\n\n case ErrorCode.SEARCH_FAILED:\n suggestions.push(\n 'Verify the search index is up to date',\n 'Try a simpler search query',\n 'Check for special characters that may need escaping'\n );\n break;\n\n case ErrorCode.INDEX_NOT_READY:\n suggestions.push(\n 'Wait for indexing to complete',\n 'Call initialize() before performing searches',\n 'Check if background indexing is in progress'\n );\n break;\n\n case ErrorCode.EMBEDDING_FAILED:\n suggestions.push(\n 'Verify MEMORY_EMBEDDING_PROVIDER is set correctly',\n 'Check that the API key is valid (MEMORY_OPENAI_API_KEY)',\n 'Ensure network connectivity to the embedding service'\n );\n break;\n\n case ErrorCode.MISSING_DEPENDENCY:\n suggestions.push(\n 'Install the required dependency: npm install <package>',\n 'Check that peer dependencies are installed',\n 'Review the feature documentation for requirements'\n );\n if (context?.dependency) {\n suggestions.push(`Missing: ${context.dependency}`);\n }\n break;\n\n case ErrorCode.UNSUPPORTED_FEATURE:\n suggestions.push(\n 'Check if the feature requires additional configuration',\n 'Verify the feature is available in your version',\n 'Review the documentation for feature requirements'\n );\n break;\n\n case ErrorCode.CYCLE_DETECTED:\n suggestions.push(\n 'Review the parent-child relationship being created',\n 'Check for existing ancestor relationships',\n 'Use getAncestors() to verify hierarchy before setting parent'\n );\n break;\n\n case ErrorCode.INVALID_IMPORTANCE:\n suggestions.push(\n 'Importance values must be between 0 and 10',\n 'Use a decimal value like 7.5 for fine-grained control',\n 'Default importance is 5 if not specified'\n );\n break;\n\n case ErrorCode.INSUFFICIENT_ENTITIES:\n suggestions.push(\n 'Ensure you have created enough entities before the operation',\n 'Check the minimum entity requirements for this operation',\n 'Create additional entities if needed'\n );\n break;\n\n case ErrorCode.VALIDATION_FAILED:\n suggestions.push(\n 'Check the validation errors for specific field issues',\n 'Ensure all required fields are provided',\n 'Verify field values match expected types and constraints'\n );\n break;\n\n case ErrorCode.SCHEMA_VALIDATION_FAILED:\n suggestions.push(\n 'Review the JSON Schema for the entity type',\n 'Check that all required properties are present',\n 'Verify property types match the schema'\n );\n break;\n\n case ErrorCode.OPERATION_CANCELLED:\n suggestions.push(\n 'The operation was cancelled via AbortSignal',\n 'Retry the operation if cancellation was unintended',\n 'Check for timeout settings that may have triggered cancellation'\n );\n break;\n\n case ErrorCode.IMPORT_ERROR:\n suggestions.push(\n 'Verify the import data format is correct',\n 'Check for syntax errors in the import file',\n 'Ensure all required fields are present in imported data'\n );\n if (context?.format) {\n suggestions.push(`Format: ${context.format}`);\n }\n break;\n\n case ErrorCode.EXPORT_ERROR:\n suggestions.push(\n 'Check write permissions for the export path',\n 'Verify sufficient disk space is available',\n 'Try a different export format'\n );\n if (context?.format) {\n suggestions.push(`Format: ${context.format}`);\n }\n break;\n\n case ErrorCode.FILE_OPERATION_ERROR:\n suggestions.push(\n 'Check file permissions (read/write)',\n 'Verify the file path is valid',\n 'Ensure the file is not locked by another process'\n );\n if (context?.operation) {\n suggestions.push(`Operation: ${context.operation}`);\n }\n if (context?.path) {\n suggestions.push(`Path: ${context.path}`);\n }\n break;\n\n case ErrorCode.INVALID_CONFIG:\n suggestions.push(\n 'Review configuration options in the documentation',\n 'Check for typos in configuration keys',\n 'Verify configuration values are of the correct type'\n );\n break;\n\n default:\n suggestions.push(\n 'Check the error message for details',\n 'Review the documentation for the operation',\n 'Report the issue if the error persists'\n );\n }\n\n return suggestions;\n}\n\n/**\n * Get a single-line recovery hint for an error code.\n */\nexport function getQuickHint(code: ErrorCode | string): string {\n switch (code) {\n case ErrorCode.ENTITY_NOT_FOUND:\n return 'Entity does not exist - check spelling or create it first';\n case ErrorCode.RELATION_NOT_FOUND:\n return 'Relation does not exist - verify entities and relation type';\n case ErrorCode.DUPLICATE_ENTITY:\n return 'Entity already exists - use update or choose different name';\n case ErrorCode.VALIDATION_FAILED:\n return 'Invalid data - check required fields and value constraints';\n case ErrorCode.CYCLE_DETECTED:\n return 'Circular reference - cannot create parent-child cycle';\n case ErrorCode.OPERATION_CANCELLED:\n return 'Operation was cancelled - retry if needed';\n default:\n return 'An error occurred - see details above';\n }\n}\n","/**\r\n * Application Constants\r\n *\r\n * Centralized configuration constants for file paths, extensions, and default values.\r\n *\r\n * @module utils/constants\r\n */\r\n\r\n/**\r\n * File extensions used by the memory system.\r\n */\r\nexport const FILE_EXTENSIONS = {\r\n /** JSONL format for line-delimited JSON storage */\r\n JSONL: '.jsonl',\r\n /** Legacy JSON format (backward compatibility) */\r\n JSON: '.json',\r\n} as const;\r\n\r\n/**\r\n * File name suffixes for auxiliary data files.\r\n * These suffixes are appended to the base memory file name.\r\n */\r\nexport const FILE_SUFFIXES = {\r\n /** Suffix for saved searches file */\r\n SAVED_SEARCHES: '-saved-searches',\r\n /** Suffix for tag aliases file */\r\n TAG_ALIASES: '-tag-aliases',\r\n} as const;\r\n\r\n/**\r\n * Default file names used by the memory system.\r\n */\r\nexport const DEFAULT_FILE_NAMES = {\r\n /** Default memory file name */\r\n MEMORY: 'memory',\r\n /** Legacy memory file name (for backward compatibility) */\r\n MEMORY_LEGACY: 'memory',\r\n} as const;\r\n\r\n/**\r\n * Environment variable names used for configuration.\r\n */\r\nexport const ENV_VARS = {\r\n /** Environment variable for custom memory file path */\r\n MEMORY_FILE_PATH: 'MEMORY_FILE_PATH',\r\n} as const;\r\n\r\n/**\r\n * Default base directory relative to the compiled code.\r\n */\r\nexport const DEFAULT_BASE_DIR = '../';\r\n\r\n/**\r\n * Log message prefixes for consistent logging.\r\n */\r\nexport const LOG_PREFIXES = {\r\n /** Informational message prefix */\r\n INFO: '[INFO]',\r\n /** Error message prefix */\r\n ERROR: '[ERROR]',\r\n /** Warning message prefix */\r\n WARN: '[WARN]',\r\n} as const;\r\n\r\n/**\r\n * Similarity scoring weights for duplicate detection.\r\n * These weights determine the relative importance of each factor\r\n * when calculating entity similarity for duplicate detection.\r\n * Total weights must sum to 1.0 (100%).\r\n */\r\nexport const SIMILARITY_WEIGHTS = {\r\n /** Name similarity weight (40%) - Uses Levenshtein distance */\r\n NAME: 0.4,\r\n /** Entity type match weight (20%) - Exact match required */\r\n TYPE: 0.2,\r\n /** Observation overlap weight (30%) - Uses Jaccard similarity */\r\n OBSERVATIONS: 0.3,\r\n /** Tag overlap weight (10%) - Uses Jaccard similarity */\r\n TAGS: 0.1,\r\n} as const;\r\n\r\n/**\r\n * Default threshold for duplicate detection (80% similarity required).\r\n */\r\nexport const DEFAULT_DUPLICATE_THRESHOLD = 0.8;\r\n\r\n/**\r\n * Search result limits to prevent resource exhaustion.\r\n */\r\nexport const SEARCH_LIMITS = {\r\n /** Default number of results to return */\r\n DEFAULT: 50,\r\n /** Maximum number of results allowed */\r\n MAX: 200,\r\n /** Minimum number of results (must be at least 1) */\r\n MIN: 1,\r\n} as const;\r\n\r\n/**\r\n * Entity importance range validation constants.\r\n * Importance is used to prioritize entities (0 = lowest, 10 = highest).\r\n */\r\nexport const IMPORTANCE_RANGE = {\r\n /** Minimum importance value */\r\n MIN: 0,\r\n /** Maximum importance value */\r\n MAX: 10,\r\n} as const;\r\n\r\n/**\r\n * Graph size limits to prevent resource exhaustion and ensure performance.\r\n * These limits help maintain system stability and responsiveness.\r\n */\r\nexport const GRAPH_LIMITS = {\r\n /** Maximum number of entities in the graph */\r\n MAX_ENTITIES: 100000,\r\n /** Maximum number of relations in the graph */\r\n MAX_RELATIONS: 1000000,\r\n /** Maximum graph file size in megabytes */\r\n MAX_FILE_SIZE_MB: 500,\r\n /** Maximum observations per entity */\r\n MAX_OBSERVATIONS_PER_ENTITY: 1000,\r\n /** Maximum tags per entity */\r\n MAX_TAGS_PER_ENTITY: 100,\r\n} as const;\r\n\r\n/**\r\n * Query complexity limits to prevent expensive query operations.\r\n * These limits protect against denial-of-service through complex queries.\r\n */\r\nexport const QUERY_LIMITS = {\r\n /** Maximum nesting depth for boolean queries */\r\n MAX_DEPTH: 10,\r\n /** Maximum number of terms in a single query */\r\n MAX_TERMS: 50,\r\n /** Maximum number of boolean operators (AND/OR/NOT) */\r\n MAX_OPERATORS: 20,\r\n /** Maximum query string length */\r\n MAX_QUERY_LENGTH: 5000,\r\n} as const;\r\n\r\n/**\r\n * Brotli compression configuration constants.\r\n * Brotli is built into Node.js >=11.7.0 via the zlib module.\r\n * No external dependencies required.\r\n *\r\n * Quality levels determine compression ratio vs speed tradeoff:\r\n * - Lower values (0-4): Faster compression, lower ratio\r\n * - Higher values (9-11): Slower compression, higher ratio\r\n */\r\nexport const COMPRESSION_CONFIG = {\r\n // Quality levels (0-11)\r\n /** Fast compression for real-time entity writes (quality 4) */\r\n BROTLI_QUALITY_REALTIME: 4,\r\n /** Balanced compression for exports and imports (quality 6) */\r\n BROTLI_QUALITY_BATCH: 6,\r\n /** Maximum compression for backups and archives (quality 11) */\r\n BROTLI_QUALITY_ARCHIVE: 11,\r\n /** Fast decompress for cache compression (quality 5) */\r\n BROTLI_QUALITY_CACHE: 5,\r\n\r\n // Auto-compression thresholds (in bytes)\r\n /** Auto-compress exports larger than 100KB */\r\n AUTO_COMPRESS_EXPORT_SIZE: 100 * 1024,\r\n /** Auto-compress MCP responses larger than 256KB */\r\n AUTO_COMPRESS_RESPONSE_SIZE: 256 * 1024,\r\n /** Always compress backups by default */\r\n AUTO_COMPRESS_BACKUP: true,\r\n\r\n // File extension for compressed files\r\n /** Brotli compressed file extension */\r\n BROTLI_EXTENSION: '.br',\r\n\r\n // Performance tuning\r\n /** Chunk size for streaming compression (64KB) */\r\n COMPRESSION_CHUNK_SIZE: 65536,\r\n /** Default window size for brotli (lgwin parameter) */\r\n COMPRESSION_WINDOW_SIZE: 22,\r\n} as const;\r\n\r\n/**\r\n * Type representing valid brotli quality levels used in the application.\r\n */\r\nexport type CompressionQuality =\r\n | typeof COMPRESSION_CONFIG.BROTLI_QUALITY_REALTIME\r\n | typeof COMPRESSION_CONFIG.BROTLI_QUALITY_BATCH\r\n | typeof COMPRESSION_CONFIG.BROTLI_QUALITY_ARCHIVE\r\n | typeof COMPRESSION_CONFIG.BROTLI_QUALITY_CACHE;\r\n\r\n// ==================== Semantic Search Configuration (Phase 4 Sprint 10-12) ====================\r\n\r\n/**\r\n * Environment variable names for embedding configuration.\r\n */\r\nexport const EMBEDDING_ENV_VARS = {\r\n /** Embedding provider: 'openai', 'local', or 'none' (default: 'none') */\r\n PROVIDER: 'MEMORY_EMBEDDING_PROVIDER',\r\n /** OpenAI API key (required when provider is 'openai') */\r\n OPENAI_API_KEY: 'MEMORY_OPENAI_API_KEY',\r\n /** Optional model override for the embedding service */\r\n MODEL: 'MEMORY_EMBEDDING_MODEL',\r\n /** Auto-index entities on creation: 'true' or 'false' (default: 'false') */\r\n AUTO_INDEX: 'MEMORY_AUTO_INDEX_EMBEDDINGS',\r\n} as const;\r\n\r\n/**\r\n * Default embedding configuration values.\r\n */\r\nexport const EMBEDDING_DEFAULTS = {\r\n /** Default provider (disabled by default) */\r\n PROVIDER: 'none' as const,\r\n /** Default OpenAI model for embeddings (1536 dimensions) */\r\n OPENAI_MODEL: 'text-embedding-3-small',\r\n /** Default local model for embeddings (384 dimensions) */\r\n LOCAL_MODEL: 'Xenova/all-MiniLM-L6-v2',\r\n /** OpenAI embedding dimensions for text-embedding-3-small */\r\n OPENAI_DIMENSIONS: 1536,\r\n /** Local embedding dimensions for all-MiniLM-L6-v2 */\r\n LOCAL_DIMENSIONS: 384,\r\n /** Maximum texts per batch for OpenAI */\r\n OPENAI_MAX_BATCH_SIZE: 2048,\r\n /** Default batch size for embedding operations */\r\n DEFAULT_BATCH_SIZE: 100,\r\n /** Whether to auto-index entities by default */\r\n AUTO_INDEX: false,\r\n} as const;\r\n\r\n/**\r\n * Semantic search configuration limits.\r\n */\r\nexport const SEMANTIC_SEARCH_LIMITS = {\r\n /** Default number of results for semantic search */\r\n DEFAULT_LIMIT: 10,\r\n /** Maximum number of results for semantic search */\r\n MAX_LIMIT: 100,\r\n /** Minimum similarity score for results (0.0-1.0) */\r\n MIN_SIMILARITY: 0.0,\r\n} as const;\r\n\r\n/**\r\n * OpenAI API configuration.\r\n */\r\nexport const OPENAI_API_CONFIG: {\r\n BASE_URL: string;\r\n EMBEDDINGS_ENDPOINT: string;\r\n MAX_RETRIES: number;\r\n INITIAL_BACKOFF_MS: number;\r\n MAX_BACKOFF_MS: number;\r\n} = {\r\n /** Base URL for OpenAI API */\r\n BASE_URL: 'https://api.openai.com/v1',\r\n /** Embeddings endpoint */\r\n EMBEDDINGS_ENDPOINT: '/embeddings',\r\n /** Maximum retries for rate limiting */\r\n MAX_RETRIES: 3,\r\n /** Initial backoff delay in milliseconds */\r\n INITIAL_BACKOFF_MS: 1000,\r\n /** Maximum backoff delay in milliseconds */\r\n MAX_BACKOFF_MS: 10000,\r\n};\r\n\r\n/**\r\n * Get embedding configuration from environment variables.\r\n *\r\n * @returns EmbeddingConfig object with values from environment or defaults\r\n */\r\nexport function getEmbeddingConfig(): {\r\n provider: 'openai' | 'local' | 'none';\r\n apiKey?: string;\r\n model?: string;\r\n autoIndex: boolean;\r\n} {\r\n const provider = (process.env[EMBEDDING_ENV_VARS.PROVIDER] || EMBEDDING_DEFAULTS.PROVIDER) as 'openai' | 'local' | 'none';\r\n const apiKey = process.env[EMBEDDING_ENV_VARS.OPENAI_API_KEY];\r\n const model = process.env[EMBEDDING_ENV_VARS.MODEL];\r\n const autoIndex = process.env[EMBEDDING_ENV_VARS.AUTO_INDEX] === 'true';\r\n\r\n return { provider, apiKey, model, autoIndex };\r\n}\r\n\r\n// ==================== Streaming Export Configuration (Phase 7 Sprint 1) ====================\r\n\r\n/**\r\n * Streaming export configuration.\r\n *\r\n * Controls when to use streaming mode and buffer sizes for optimal memory usage.\r\n */\r\nexport const STREAMING_CONFIG = {\r\n /** Minimum entity count to trigger streaming mode */\r\n STREAMING_THRESHOLD: 5000,\r\n /** Chunk size for batched streaming operations */\r\n CHUNK_SIZE: 500,\r\n /** High water mark for stream buffers (bytes) */\r\n HIGH_WATER_MARK: 64 * 1024,\r\n /** Flush interval for long-running streams (ms) */\r\n FLUSH_INTERVAL_MS: 100,\r\n} as const;\r\n","/**\r\n * Compression Utility Module\r\n *\r\n * Provides brotli compression and decompression utilities using Node.js\r\n * built-in zlib module. No external dependencies required.\r\n *\r\n * Brotli offers 15-20% better compression than gzip, with 60-75%\r\n * compression typical for JSON data.\r\n *\r\n * @module utils/compressionUtil\r\n */\r\n\r\nimport { brotliCompress, brotliDecompress, constants } from 'zlib';\r\nimport { promisify } from 'util';\r\nimport { promises as fs } from 'fs';\r\nimport { COMPRESSION_CONFIG } from './constants.js';\r\n\r\n// Promisify Node.js zlib functions for async/await usage\r\nconst compressAsync = promisify(brotliCompress);\r\nconst decompressAsync = promisify(brotliDecompress);\r\n\r\n/**\r\n * Options for compression operations.\r\n */\r\nexport interface CompressionOptions {\r\n /**\r\n * Brotli quality level (0-11).\r\n * Higher values = better compression but slower.\r\n * @default 6\r\n */\r\n quality?: number;\r\n\r\n /**\r\n * Window size (10-24) for the LZ77 algorithm.\r\n * Larger windows = better compression for large files.\r\n * @default 22\r\n */\r\n lgwin?: number;\r\n\r\n /**\r\n * Compression mode hint.\r\n * - 'text': Optimized for UTF-8 text\r\n * - 'generic': General-purpose compression\r\n * @default 'generic'\r\n */\r\n mode?: 'text' | 'generic';\r\n}\r\n\r\n/**\r\n * Result of a compression operation.\r\n */\r\nexport interface CompressionResult {\r\n /** The compressed data as a Buffer */\r\n compressed: Buffer;\r\n /** Original size in bytes */\r\n originalSize: number;\r\n /** Compressed size in bytes */\r\n compressedSize: number;\r\n /** Compression ratio (compressedSize / originalSize). Lower is better. */\r\n ratio: number;\r\n}\r\n\r\n/**\r\n * Metadata about a compressed file for storage alongside the compressed data.\r\n */\r\nexport interface CompressionMetadata {\r\n /** Whether the data is compressed */\r\n compressed: boolean;\r\n /** Compression format used */\r\n compressionFormat: 'brotli' | 'none';\r\n /** Original size before compression in bytes */\r\n originalSize: number;\r\n /** Size after compression in bytes */\r\n compressedSize: number;\r\n /** Optional checksum of original data for integrity verification */\r\n originalChecksum?: string;\r\n /** ISO 8601 timestamp when compression was performed */\r\n createdAt: string;\r\n}\r\n\r\n/**\r\n * Check if a file path indicates brotli compression based on extension.\r\n *\r\n * Note: Brotli doesn't have reliable magic bytes for detection.\r\n * Using file extension (.br) is the recommended detection method.\r\n *\r\n * @param filePath - The file path to check\r\n * @returns True if the path ends with .br extension\r\n *\r\n * @example\r\n * ```typescript\r\n * hasBrotliExtension('backup.jsonl.br') // true\r\n * hasBrotliExtension('backup.jsonl') // false\r\n * hasBrotliExtension('data.json') // false\r\n * ```\r\n */\r\nexport function hasBrotliExtension(filePath: string): boolean {\r\n return filePath.endsWith(COMPRESSION_CONFIG.BROTLI_EXTENSION);\r\n}\r\n\r\n/**\r\n * Compress data using brotli algorithm.\r\n *\r\n * @param data - The data to compress (string or Buffer)\r\n * @param options - Compression options\r\n * @returns Compression result with compressed data and statistics\r\n *\r\n * @example\r\n * ```typescript\r\n * const jsonData = JSON.stringify({ entities: [...] });\r\n * const result = await compress(jsonData, { quality: 11 });\r\n * console.log(`Compressed from ${result.originalSize} to ${result.compressedSize} bytes`);\r\n * console.log(`Compression ratio: ${(result.ratio * 100).toFixed(1)}%`);\r\n * ```\r\n */\r\nexport async function compress(\r\n data: Buffer | string,\r\n options: CompressionOptions = {}\r\n): Promise<CompressionResult> {\r\n const input = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf-8');\r\n const quality = options.quality ?? COMPRESSION_CONFIG.BROTLI_QUALITY_BATCH;\r\n\r\n // Validate quality range\r\n if (quality < 0 || quality > 11) {\r\n throw new Error(`Invalid brotli quality level: ${quality}. Must be 0-11.`);\r\n }\r\n\r\n const zlibOptions: { params: Record<number, number> } = {\r\n params: {\r\n [constants.BROTLI_PARAM_QUALITY]: quality,\r\n [constants.BROTLI_PARAM_MODE]:\r\n options.mode === 'text'\r\n ? constants.BROTLI_MODE_TEXT\r\n : constants.BROTLI_MODE_GENERIC,\r\n },\r\n };\r\n\r\n if (options.lgwin !== undefined) {\r\n if (options.lgwin < 10 || options.lgwin > 24) {\r\n throw new Error(\r\n `Invalid brotli window size: ${options.lgwin}. Must be 10-24.`\r\n );\r\n }\r\n zlibOptions.params[constants.BROTLI_PARAM_LGWIN] = options.lgwin;\r\n }\r\n\r\n const compressed = await compressAsync(input, zlibOptions);\r\n\r\n return {\r\n compressed,\r\n originalSize: input.length,\r\n compressedSize: compressed.length,\r\n ratio: input.length > 0 ? compressed.length / input.length : 1,\r\n };\r\n}\r\n\r\n/**\r\n * Decompress brotli-compressed data.\r\n *\r\n * @param data - The compressed data as a Buffer\r\n * @returns The decompressed data as a Buffer\r\n * @throws Error if decompression fails (corrupt or invalid data)\r\n *\r\n * @example\r\n * ```typescript\r\n * const compressed = await fs.readFile('backup.jsonl.br');\r\n * const decompressed = await decompress(compressed);\r\n * const jsonData = decompressed.toString('utf-8');\r\n * ```\r\n */\r\nexport async function decompress(data: Buffer): Promise<Buffer> {\r\n if (!Buffer.isBuffer(data)) {\r\n throw new Error('Input must be a Buffer');\r\n }\r\n\r\n if (data.length === 0) {\r\n return Buffer.alloc(0);\r\n }\r\n\r\n try {\r\n return await decompressAsync(data);\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : 'Unknown error';\r\n throw new Error(`Brotli decompression failed: ${message}`);\r\n }\r\n}\r\n\r\n/**\r\n * Calculate compression ratio.\r\n *\r\n * @param originalSize - Original size in bytes\r\n * @param compressedSize - Compressed size in bytes\r\n * @returns Ratio as a decimal (e.g., 0.25 = 75% compression)\r\n *\r\n * @example\r\n * ```typescript\r\n * const ratio = getCompressionRatio(1000, 250);\r\n * console.log(`Compression ratio: ${(1 - ratio) * 100}%`); // \"Compression ratio: 75%\"\r\n * ```\r\n */\r\nexport function getCompressionRatio(\r\n originalSize: number,\r\n compressedSize: number\r\n): number {\r\n if (originalSize <= 0) return 1;\r\n return compressedSize / originalSize;\r\n}\r\n\r\n/**\r\n * Compress a file and write the result to disk.\r\n *\r\n * @param inputPath - Path to the input file\r\n * @param outputPath - Path for the compressed output file\r\n * @param options - Compression options\r\n * @returns Compression result with statistics\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = await compressFile(\r\n * 'memory.jsonl',\r\n * 'memory.jsonl.br',\r\n * { quality: 11 }\r\n * );\r\n * console.log(`Saved ${result.originalSize - result.compressedSize} bytes`);\r\n * ```\r\n */\r\nexport async function compressFile(\r\n inputPath: string,\r\n outputPath: string,\r\n options?: CompressionOptions\r\n): Promise<CompressionResult> {\r\n const input = await fs.readFile(inputPath);\r\n const result = await compress(input, options);\r\n await fs.writeFile(outputPath, result.compressed);\r\n return result;\r\n}\r\n\r\n/**\r\n * Decompress a file and write the result to disk.\r\n *\r\n * @param inputPath - Path to the compressed input file\r\n * @param outputPath - Path for the decompressed output file\r\n * @throws Error if file not found or decompression fails\r\n *\r\n * @example\r\n * ```typescript\r\n * await decompressFile('backup.jsonl.br', 'restored.jsonl');\r\n * ```\r\n */\r\nexport async function decompressFile(\r\n inputPath: string,\r\n outputPath: string\r\n): Promise<void> {\r\n const input = await fs.readFile(inputPath);\r\n const decompressed = await decompress(input);\r\n await fs.writeFile(outputPath, decompressed);\r\n}\r\n\r\n/**\r\n * Create metadata object for a compression result.\r\n * This metadata should be stored alongside compressed files for\r\n * integrity verification and restoration.\r\n *\r\n * @param result - The compression result\r\n * @param checksum - Optional checksum of the original data\r\n * @returns Compression metadata object\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = await compress(data);\r\n * const metadata = createMetadata(result);\r\n * await fs.writeFile('backup.meta.json', JSON.stringify(metadata, null, 2));\r\n * ```\r\n */\r\nexport function createMetadata(\r\n result: CompressionResult,\r\n checksum?: string\r\n): CompressionMetadata {\r\n return {\r\n compressed: true,\r\n compressionFormat: 'brotli',\r\n originalSize: result.originalSize,\r\n compressedSize: result.compressedSize,\r\n originalChecksum: checksum,\r\n createdAt: new Date().toISOString(),\r\n };\r\n}\r\n\r\n/**\r\n * Create metadata for uncompressed data.\r\n * Useful for consistent metadata format when compression is disabled.\r\n *\r\n * @param size - Size of the data in bytes\r\n * @returns Compression metadata indicating no compression\r\n */\r\nexport function createUncompressedMetadata(size: number): CompressionMetadata {\r\n return {\r\n compressed: false,\r\n compressionFormat: 'none',\r\n originalSize: size,\r\n compressedSize: size,\r\n createdAt: new Date().toISOString(),\r\n };\r\n}\r\n\r\n/**\r\n * Compress a string and return base64-encoded result.\r\n * Useful for embedding compressed data in JSON responses.\r\n *\r\n * @param data - String data to compress\r\n * @param options - Compression options\r\n * @returns Base64-encoded compressed data\r\n *\r\n * @example\r\n * ```typescript\r\n * const encoded = await compressToBase64(jsonString);\r\n * // Send in response: { compressed: true, data: encoded }\r\n * ```\r\n */\r\nexport async function compressToBase64(\r\n data: string,\r\n options?: CompressionOptions\r\n): Promise<string> {\r\n const result = await compress(data, options);\r\n return result.compressed.toString('base64');\r\n}\r\n\r\n/**\r\n * Decompress base64-encoded compressed data.\r\n * Counterpart to compressToBase64.\r\n *\r\n * @param base64Data - Base64-encoded compressed data\r\n * @returns Decompressed string\r\n *\r\n * @example\r\n * ```typescript\r\n * const original = await decompressFromBase64(response.data);\r\n * const parsed = JSON.parse(original);\r\n * ```\r\n */\r\nexport async function decompressFromBase64(base64Data: string): Promise<string> {\r\n const buffer = Buffer.from(base64Data, 'base64');\r\n const decompressed = await decompress(buffer);\r\n return decompressed.toString('utf-8');\r\n}\r\n","/**\r\n * Compressed Cache Utility\r\n *\r\n * Provides an LRU cache with automatic compression of old entries.\r\n * Reduces memory footprint for large knowledge graphs (50k+ entities).\r\n *\r\n * Uses synchronous brotli compression/decompression for cache operations\r\n * to avoid async complexity in hot paths.\r\n *\r\n * Phase 3 Sprint 5: Archive & Cache Compression\r\n *\r\n * @module utils/compressedCache\r\n */\r\n\r\nimport { brotliCompressSync, brotliDecompressSync, constants } from 'zlib';\r\nimport type { Entity } from '../types/index.js';\r\nimport { COMPRESSION_CONFIG } from './constants.js';\r\n\r\n/**\r\n * Internal cache entry structure.\r\n */\r\ninterface CacheEntry {\r\n /** The entity data (null if compressed) */\r\n entity: Entity | null;\r\n /** Whether this entry is compressed */\r\n compressed: boolean;\r\n /** Compressed data buffer (only present if compressed) */\r\n compressedData?: Buffer;\r\n /** Size of original entity JSON in bytes (for stats) */\r\n originalSize: number;\r\n /** Timestamp of last access for LRU eviction */\r\n lastAccessed: number;\r\n}\r\n\r\n/**\r\n * Options for CompressedCache configuration.\r\n */\r\nexport interface CompressedCacheOptions {\r\n /**\r\n * Maximum number of uncompressed (hot) entries to keep.\r\n * Entries beyond this limit may be compressed.\r\n * @default 1000\r\n */\r\n maxUncompressed?: number;\r\n\r\n /**\r\n * Time in milliseconds before an entry is eligible for compression.\r\n * Entries accessed within this time window stay uncompressed.\r\n * @default 300000 (5 minutes)\r\n */\r\n compressionThresholdMs?: number;\r\n\r\n /**\r\n * Whether to enable automatic compression.\r\n * If false, entries are never automatically compressed.\r\n * @default true\r\n */\r\n autoCompress?: boolean;\r\n\r\n /**\r\n * Minimum entry size in bytes before compression is applied.\r\n * Entries smaller than this are not compressed (overhead exceeds benefit).\r\n * Phase 12 Sprint 6: Adaptive compression.\r\n * @default 256\r\n */\r\n minCompressionSize?: number;\r\n\r\n /**\r\n * Minimum compression ratio to keep entry compressed.\r\n * If compression achieves less than this ratio, entry stays uncompressed.\r\n * Phase 12 Sprint 6: Adaptive compression.\r\n * @default 0.7 (30% reduction minimum)\r\n */\r\n minCompressionRatio?: number;\r\n}\r\n\r\n/**\r\n * Statistics about the cache state.\r\n */\r\nexport interface CompressedCacheStats {\r\n /** Total number of entries in the cache */\r\n total: number;\r\n /** Number of compressed entries */\r\n compressed: number;\r\n /** Number of uncompressed (hot) entries */\r\n uncompressed: number;\r\n /** Estimated memory saved by compression in bytes */\r\n memorySaved: number;\r\n /** Total original size of all entries in bytes */\r\n totalOriginalSize: number;\r\n /** Total compressed size in bytes */\r\n totalCompressedSize: number;\r\n /** Cache hit count since creation */\r\n hits: number;\r\n /** Cache miss count since creation */\r\n misses: number;\r\n /** Number of compressions performed */\r\n compressions: number;\r\n /** Number of decompressions performed */\r\n decompressions: number;\r\n /** Phase 12 Sprint 6: Number of entries skipped due to size */\r\n skippedSmallEntries: number;\r\n /** Phase 12 Sprint 6: Number of entries skipped due to poor ratio */\r\n skippedPoorRatio: number;\r\n /** Phase 12 Sprint 6: Average compression ratio (0-1) */\r\n avgCompressionRatio: number;\r\n /** Phase 12 Sprint 6: Estimated memory usage in bytes */\r\n estimatedMemoryBytes: number;\r\n}\r\n\r\n/**\r\n * LRU cache with automatic compression of old entries.\r\n *\r\n * Reduces memory footprint by compressing infrequently accessed entries\r\n * using brotli compression. Hot (recently accessed) entries stay\r\n * uncompressed for fast access.\r\n *\r\n * @example\r\n * ```typescript\r\n * const cache = new CompressedCache({\r\n * maxUncompressed: 500,\r\n * compressionThresholdMs: 60000 // 1 minute\r\n * });\r\n *\r\n * // Store entity\r\n * cache.set('Alice', { name: 'Alice', entityType: 'person', observations: [] });\r\n *\r\n * // Retrieve entity (decompresses if needed)\r\n * const entity = cache.get('Alice');\r\n *\r\n * // Check stats\r\n * const stats = cache.getStats();\r\n * console.log(`Memory saved: ${stats.memorySaved} bytes`);\r\n * ```\r\n */\r\nexport class CompressedCache {\r\n private _entryMap: Map<string, CacheEntry> = new Map();\r\n private readonly maxUncompressed: number;\r\n private readonly compressionThresholdMs: number;\r\n private readonly autoCompress: boolean;\r\n // Phase 12 Sprint 6: Adaptive compression options\r\n private readonly minCompressionSize: number;\r\n private readonly minCompressionRatio: number;\r\n\r\n // Statistics\r\n private hits: number = 0;\r\n private misses: number = 0;\r\n private compressions: number = 0;\r\n private decompressions: number = 0;\r\n // Phase 12 Sprint 6: Adaptive compression stats\r\n private skippedSmallEntries: number = 0;\r\n private skippedPoorRatio: number = 0;\r\n private compressionRatios: number[] = [];\r\n\r\n constructor(options: CompressedCacheOptions = {}) {\r\n this.maxUncompressed = options.maxUncompressed ?? 1000;\r\n this.compressionThresholdMs = options.compressionThresholdMs ?? 5 * 60 * 1000;\r\n this.autoCompress = options.autoCompress ?? true;\r\n // Phase 12 Sprint 6: Adaptive compression defaults\r\n this.minCompressionSize = options.minCompressionSize ?? 256;\r\n this.minCompressionRatio = options.minCompressionRatio ?? 0.7;\r\n }\r\n\r\n /**\r\n * Get an entity from the cache.\r\n *\r\n * If the entity is compressed, it will be decompressed on access.\r\n * The entry is marked as recently accessed to prevent re-compression.\r\n *\r\n * @param name - Entity name to retrieve\r\n * @returns The entity if found, undefined otherwise\r\n */\r\n get(name: string): Entity | undefined {\r\n const entry = this._entryMap.get(name);\r\n\r\n if (!entry) {\r\n this.misses++;\r\n return undefined;\r\n }\r\n\r\n this.hits++;\r\n entry.lastAccessed = Date.now();\r\n\r\n if (entry.compressed && entry.compressedData) {\r\n // Decompress on access\r\n try {\r\n const decompressed = brotliDecompressSync(entry.compressedData);\r\n entry.entity = JSON.parse(decompressed.toString('utf-8'));\r\n entry.compressed = false;\r\n entry.compressedData = undefined;\r\n this.decompressions++;\r\n } catch {\r\n // Decompression failed - remove corrupt entry\r\n this._entryMap.delete(name);\r\n return undefined;\r\n }\r\n }\r\n\r\n return entry.entity ?? undefined;\r\n }\r\n\r\n /**\r\n * Store an entity in the cache.\r\n *\r\n * Entries are stored uncompressed initially. Old entries may be\r\n * compressed automatically based on cache settings.\r\n *\r\n * @param name - Entity name (key)\r\n * @param entity - Entity to store\r\n */\r\n set(name: string, entity: Entity): void {\r\n const jsonStr = JSON.stringify(entity);\r\n const originalSize = Buffer.byteLength(jsonStr, 'utf-8');\r\n\r\n this._entryMap.set(name, {\r\n entity,\r\n compressed: false,\r\n originalSize,\r\n lastAccessed: Date.now(),\r\n });\r\n\r\n if (this.autoCompress) {\r\n this.maybeCompressOldEntries();\r\n }\r\n }\r\n\r\n /**\r\n * Check if an entity exists in the cache.\r\n *\r\n * @param name - Entity name to check\r\n * @returns True if entity exists in cache\r\n */\r\n has(name: string): boolean {\r\n return this._entryMap.has(name);\r\n }\r\n\r\n /**\r\n * Delete an entity from the cache.\r\n *\r\n * @param name - Entity name to delete\r\n * @returns True if entity was deleted, false if not found\r\n */\r\n delete(name: string): boolean {\r\n return this._entryMap.delete(name);\r\n }\r\n\r\n /**\r\n * Clear all entries from the cache.\r\n */\r\n clear(): void {\r\n this._entryMap.clear();\r\n // Don't reset statistics - they track lifetime totals\r\n }\r\n\r\n /**\r\n * Get the number of entries in the cache.\r\n */\r\n get size(): number {\r\n return this._entryMap.size;\r\n }\r\n\r\n /**\r\n * Get all entity names in the cache.\r\n */\r\n keys(): IterableIterator<string> {\r\n return this._entryMap.keys();\r\n }\r\n\r\n /**\r\n * Get comprehensive cache statistics.\r\n *\r\n * @returns Statistics about cache state and performance\r\n */\r\n getStats(): CompressedCacheStats {\r\n let compressed = 0;\r\n let uncompressed = 0;\r\n let memorySaved = 0;\r\n let totalOriginalSize = 0;\r\n let totalCompressedSize = 0;\r\n let estimatedMemoryBytes = 0;\r\n\r\n for (const entry of this._entryMap.values()) {\r\n totalOriginalSize += entry.originalSize;\r\n\r\n if (entry.compressed && entry.compressedData) {\r\n compressed++;\r\n totalCompressedSize += entry.compressedData.length;\r\n estimatedMemoryBytes += entry.compressedData.length;\r\n // Memory saved = original size - compressed size\r\n memorySaved += entry.originalSize - entry.compressedData.length;\r\n } else {\r\n uncompressed++;\r\n estimatedMemoryBytes += entry.originalSize;\r\n }\r\n }\r\n\r\n // Calculate average compression ratio\r\n const avgCompressionRatio = this.compressionRatios.length > 0\r\n ? this.compressionRatios.reduce((a, b) => a + b, 0) / this.compressionRatios.length\r\n : 0;\r\n\r\n return {\r\n total: this._entryMap.size,\r\n compressed,\r\n uncompressed,\r\n memorySaved: Math.max(0, memorySaved),\r\n totalOriginalSize,\r\n totalCompressedSize,\r\n hits: this.hits,\r\n misses: this.misses,\r\n compressions: this.compressions,\r\n decompressions: this.decompressions,\r\n // Phase 12 Sprint 6: Adaptive compression stats\r\n skippedSmallEntries: this.skippedSmallEntries,\r\n skippedPoorRatio: this.skippedPoorRatio,\r\n avgCompressionRatio,\r\n estimatedMemoryBytes,\r\n };\r\n }\r\n\r\n /**\r\n * Force compression of entries older than the threshold.\r\n *\r\n * Called automatically after set() when autoCompress is enabled.\r\n * Can be called manually to trigger compression on demand.\r\n *\r\n * @returns Number of entries compressed\r\n */\r\n compressOldEntries(): number {\r\n return this.maybeCompressOldEntries(true);\r\n }\r\n\r\n /**\r\n * Compress old entries if we're over the uncompressed limit.\r\n *\r\n * @param force - If true, compress regardless of limit\r\n * @returns Number of entries compressed\r\n */\r\n private maybeCompressOldEntries(force: boolean = false): number {\r\n // Count uncompressed entries\r\n let uncompressedCount = 0;\r\n for (const entry of this._entryMap.values()) {\r\n if (!entry.compressed) {\r\n uncompressedCount++;\r\n }\r\n }\r\n\r\n // Only compress if over limit (unless forced)\r\n if (!force && uncompressedCount <= this.maxUncompressed) {\r\n return 0;\r\n }\r\n\r\n const now = Date.now();\r\n let compressedCount = 0;\r\n\r\n // Sort uncompressed entries by last accessed time (oldest first)\r\n const sortedEntries = [...this._entryMap.entries()]\r\n .filter(([, e]) => !e.compressed && e.entity !== null)\r\n .sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);\r\n\r\n // Compress oldest entries until we're under the limit\r\n for (const [, entry] of sortedEntries) {\r\n // Stop if we've compressed enough\r\n if (!force && (uncompressedCount - compressedCount) <= this.maxUncompressed) {\r\n break;\r\n }\r\n\r\n // Only compress if entry is old enough\r\n if (now - entry.lastAccessed < this.compressionThresholdMs) {\r\n continue;\r\n }\r\n\r\n // Compress the entry\r\n if (entry.entity) {\r\n // Phase 12 Sprint 6: Skip small entries (adaptive compression)\r\n if (entry.originalSize < this.minCompressionSize) {\r\n this.skippedSmallEntries++;\r\n continue;\r\n }\r\n\r\n try {\r\n const jsonStr = JSON.stringify(entry.entity);\r\n const compressed = brotliCompressSync(Buffer.from(jsonStr, 'utf-8'), {\r\n params: {\r\n [constants.BROTLI_PARAM_QUALITY]: COMPRESSION_CONFIG.BROTLI_QUALITY_CACHE,\r\n },\r\n });\r\n\r\n // Phase 12 Sprint 6: Check compression ratio\r\n const ratio = compressed.length / entry.originalSize;\r\n if (ratio > this.minCompressionRatio) {\r\n // Compression didn't achieve enough reduction\r\n this.skippedPoorRatio++;\r\n continue;\r\n }\r\n\r\n // Track compression ratio\r\n this.compressionRatios.push(ratio);\r\n\r\n entry.compressedData = compressed;\r\n entry.compressed = true;\r\n entry.entity = null; // Free memory\r\n this.compressions++;\r\n compressedCount++;\r\n } catch {\r\n // Compression failed - leave entry uncompressed\r\n continue;\r\n }\r\n }\r\n }\r\n\r\n return compressedCount;\r\n }\r\n\r\n /**\r\n * Decompress all entries in the cache.\r\n *\r\n * Useful when preparing for bulk operations or export.\r\n *\r\n * @returns Number of entries decompressed\r\n */\r\n decompressAll(): number {\r\n let decompressedCount = 0;\r\n\r\n for (const [name, entry] of this._entryMap) {\r\n if (entry.compressed && entry.compressedData) {\r\n try {\r\n const decompressed = brotliDecompressSync(entry.compressedData);\r\n entry.entity = JSON.parse(decompressed.toString('utf-8'));\r\n entry.compressed = false;\r\n entry.compressedData = undefined;\r\n this.decompressions++;\r\n decompressedCount++;\r\n } catch {\r\n // Decompression failed - remove corrupt entry\r\n this._entryMap.delete(name);\r\n }\r\n }\r\n }\r\n\r\n return decompressedCount;\r\n }\r\n\r\n /**\r\n * Get all entities from the cache (decompressing as needed).\r\n *\r\n * @returns Array of all entities in the cache\r\n */\r\n getAllEntities(): Entity[] {\r\n const entities: Entity[] = [];\r\n\r\n for (const [name] of this._entryMap) {\r\n const entity = this.get(name);\r\n if (entity) {\r\n entities.push(entity);\r\n }\r\n }\r\n\r\n return entities;\r\n }\r\n\r\n /**\r\n * Iterate over all entries with their compression status.\r\n *\r\n * Does not decompress entries - useful for inspection.\r\n *\r\n * @yields Entry information without decompressing\r\n */\r\n *entries(): IterableIterator<{\r\n name: string;\r\n compressed: boolean;\r\n originalSize: number;\r\n lastAccessed: number;\r\n }> {\r\n for (const [name, entry] of this._entryMap) {\r\n yield {\r\n name,\r\n compressed: entry.compressed,\r\n originalSize: entry.originalSize,\r\n lastAccessed: entry.lastAccessed,\r\n };\r\n }\r\n }\r\n}\r\n","/**\r\n * Simple logging utility for the Memory MCP Server\r\n *\r\n * Provides consistent log formatting with levels: debug, info, warn, error\r\n *\r\n * IMPORTANT: All log output goes to stderr to avoid interfering with\r\n * JSON-RPC communication on stdout when running as an MCP server.\r\n */\r\n\r\nexport const logger = {\r\n /**\r\n * Debug level logging (verbose, for development)\r\n * Output: stderr (to avoid interfering with JSON-RPC)\r\n */\r\n debug: (msg: string, ...args: unknown[]): void => {\r\n if (process.env.LOG_LEVEL === 'debug') {\r\n console.error(`[DEBUG] ${msg}`, ...args);\r\n }\r\n },\r\n\r\n /**\r\n * Info level logging (general informational messages)\r\n * Output: stderr (to avoid interfering with JSON-RPC)\r\n */\r\n info: (msg: string, ...args: unknown[]): void => {\r\n console.error(`[INFO] ${msg}`, ...args);\r\n },\r\n\r\n /**\r\n * Warning level logging (warnings that don't prevent operation)\r\n * Output: stderr (native console.warn behavior)\r\n */\r\n warn: (msg: string, ...args: unknown[]): void => {\r\n console.warn(`[WARN] ${msg}`, ...args);\r\n },\r\n\r\n /**\r\n * Error level logging (errors that affect functionality)\r\n * Output: stderr (native console.error behavior)\r\n */\r\n error: (msg: string, ...args: unknown[]): void => {\r\n console.error(`[ERROR] ${msg}`, ...args);\r\n },\r\n};\r\n","/**\r\n * Search Algorithms\r\n *\r\n * Algorithms for search operations: Levenshtein distance for fuzzy matching\r\n * and TF-IDF for relevance scoring.\r\n *\r\n * @module utils/searchAlgorithms\r\n */\r\n\r\n// ==================== Levenshtein Distance ====================\r\n\r\n/**\r\n * Calculate Levenshtein distance between two strings.\r\n *\r\n * Returns the minimum number of single-character edits needed to change\r\n * one word into another.\r\n *\r\n * **Algorithm**: Space-optimized dynamic programming using only two rows.\r\n * Time complexity: O(m*n), Space complexity: O(min(m,n)).\r\n *\r\n * This optimization reduces memory usage from O(m*n) to O(min(m,n)) by\r\n * observing that each row only depends on the previous row.\r\n *\r\n * @param str1 - First string to compare\r\n * @param str2 - Second string to compare\r\n * @returns Minimum number of edits required (0 = identical strings)\r\n *\r\n * @example\r\n * ```typescript\r\n * levenshteinDistance(\"kitten\", \"sitting\"); // Returns 3\r\n * levenshteinDistance(\"hello\", \"hello\"); // Returns 0\r\n * levenshteinDistance(\"abc\", \"\"); // Returns 3\r\n * ```\r\n */\r\nexport function levenshteinDistance(str1: string, str2: string): number {\r\n // Ensure str1 is the shorter string for optimal space usage\r\n if (str1.length > str2.length) {\r\n [str1, str2] = [str2, str1];\r\n }\r\n\r\n const m = str1.length;\r\n const n = str2.length;\r\n\r\n // Use two rows instead of full matrix - O(min(m,n)) space\r\n let prev: number[] = Array.from({ length: m + 1 }, (_, i) => i);\r\n let curr: number[] = new Array(m + 1);\r\n\r\n for (let j = 1; j <= n; j++) {\r\n curr[0] = j; // Distance from empty string\r\n\r\n for (let i = 1; i <= m; i++) {\r\n if (str1[i - 1] === str2[j - 1]) {\r\n // Characters match, no edit needed\r\n curr[i] = prev[i - 1];\r\n } else {\r\n // Take minimum of three operations\r\n curr[i] = 1 + Math.min(\r\n prev[i - 1], // substitution\r\n prev[i], // deletion\r\n curr[i - 1] // insertion\r\n );\r\n }\r\n }\r\n\r\n // Swap rows for next iteration\r\n [prev, curr] = [curr, prev];\r\n }\r\n\r\n return prev[m];\r\n}\r\n\r\n// ==================== TF-IDF ====================\r\n\r\n/**\r\n * Calculate Term Frequency (TF) for a term in a document.\r\n *\r\n * TF = (Number of times term appears in document) / (Total terms in document)\r\n *\r\n * @param term - The search term\r\n * @param document - The document text\r\n * @returns Term frequency (0.0 to 1.0)\r\n */\r\nexport function calculateTF(term: string, document: string): number {\r\n const termLower = term.toLowerCase();\r\n const tokens = tokenize(document);\r\n\r\n if (tokens.length === 0) return 0;\r\n\r\n const termCount = tokens.filter(t => t === termLower).length;\r\n return termCount / tokens.length;\r\n}\r\n\r\n/**\r\n * Calculate Inverse Document Frequency (IDF) for a term across documents.\r\n *\r\n * IDF = log(Total documents / Documents containing term)\r\n *\r\n * Note: For bulk IDF calculation, prefer calculateIDFFromTokenSets which\r\n * avoids re-tokenizing documents for each term.\r\n *\r\n * @param term - The search term\r\n * @param documents - Array of document texts\r\n * @returns Inverse document frequency\r\n */\r\nexport function calculateIDF(term: string, documents: string[]): number {\r\n if (documents.length === 0) return 0;\r\n\r\n const termLower = term.toLowerCase();\r\n const docsWithTerm = documents.filter(doc =>\r\n tokenize(doc).includes(termLower)\r\n ).length;\r\n\r\n if (docsWithTerm === 0) return 0;\r\n\r\n return Math.log(documents.length / docsWithTerm);\r\n}\r\n\r\n/**\r\n * Calculate Inverse Document Frequency (IDF) from pre-tokenized documents.\r\n *\r\n * IDF = log(Total documents / Documents containing term)\r\n *\r\n * **Optimized**: Avoids re-tokenizing documents for each term. Pre-tokenize\r\n * documents once and convert to Sets for O(1) lookup per document.\r\n *\r\n * @param term - The search term (should already be lowercase)\r\n * @param tokenSets - Array of Sets, each containing unique tokens for a document\r\n * @returns Inverse document frequency\r\n *\r\n * @example\r\n * ```typescript\r\n * const docs = [\"hello world\", \"hello there\"];\r\n * const tokenSets = docs.map(d => new Set(tokenize(d)));\r\n * calculateIDFFromTokenSets(\"hello\", tokenSets); // Low IDF (common term)\r\n * calculateIDFFromTokenSets(\"world\", tokenSets); // Higher IDF (less common)\r\n * ```\r\n */\r\nexport function calculateIDFFromTokenSets(term: string, tokenSets: Set<string>[]): number {\r\n if (tokenSets.length === 0) return 0;\r\n\r\n const termLower = term.toLowerCase();\r\n let docsWithTerm = 0;\r\n\r\n for (const tokenSet of tokenSets) {\r\n if (tokenSet.has(termLower)) {\r\n docsWithTerm++;\r\n }\r\n }\r\n\r\n if (docsWithTerm === 0) return 0;\r\n\r\n return Math.log(tokenSets.length / docsWithTerm);\r\n}\r\n\r\n/**\r\n * Calculate TF-IDF score for a term in a document.\r\n *\r\n * TF-IDF = TF * IDF\r\n *\r\n * Higher scores indicate more important/relevant terms.\r\n *\r\n * @param term - The search term\r\n * @param document - The document text\r\n * @param documents - Array of all documents\r\n * @returns TF-IDF score\r\n */\r\nexport function calculateTFIDF(\r\n term: string,\r\n document: string,\r\n documents: string[]\r\n): number {\r\n const tf = calculateTF(term, document);\r\n const idf = calculateIDF(term, documents);\r\n return tf * idf;\r\n}\r\n\r\n/**\r\n * Tokenize text into lowercase words.\r\n *\r\n * Splits on whitespace and removes punctuation.\r\n *\r\n * @param text - Text to tokenize\r\n * @returns Array of lowercase tokens\r\n */\r\nexport function tokenize(text: string): string[] {\r\n return text\r\n .toLowerCase()\r\n .replace(/[^\\w\\s]/g, ' ')\r\n .split(/\\s+/)\r\n .filter(token => token.length > 0);\r\n}\r\n","/**\r\n * Validation Schemas and Helpers\r\n *\r\n * Consolidated module for Zod schemas and validation utilities.\r\n * Provides runtime type safety and data validation.\r\n *\r\n * @module utils/schemas\r\n */\r\n\r\nimport { z, type ZodSchema, type ZodError } from 'zod';\r\nimport { IMPORTANCE_RANGE } from './constants.js';\r\nimport { ValidationError } from './errors.js';\r\n\r\n// ==================== Constants ====================\r\n\r\n/**\r\n * Importance range constants (imported from centralized constants).\r\n */\r\nconst MIN_IMPORTANCE = IMPORTANCE_RANGE.MIN;\r\nconst MAX_IMPORTANCE = IMPORTANCE_RANGE.MAX;\r\n\r\n// ==================== Base Schema Components ====================\r\n\r\n/**\r\n * ISO 8601 date string validation.\r\n * Accepts standard ISO format: YYYY-MM-DDTHH:mm:ss.sssZ\r\n */\r\nconst isoDateSchema = z.string().datetime({ message: 'Must be a valid ISO 8601 date string' });\r\n\r\n/**\r\n * Entity name validation.\r\n * Must be a non-empty string with reasonable length constraints.\r\n */\r\nconst entityNameSchema = z.string()\r\n .min(1, 'Entity name cannot be empty')\r\n .max(500, 'Entity name cannot exceed 500 characters')\r\n .trim();\r\n\r\n/**\r\n * Entity type validation.\r\n * Must be a non-empty string (e.g., \"person\", \"project\", \"concept\").\r\n */\r\nconst entityTypeSchema = z.string()\r\n .min(1, 'Entity type cannot be empty')\r\n .max(100, 'Entity type cannot exceed 100 characters')\r\n .trim();\r\n\r\n/**\r\n * Observation validation.\r\n * Each observation must be a non-empty string.\r\n */\r\nconst observationSchema = z.string()\r\n .min(1, 'Observation cannot be empty')\r\n .max(5000, 'Observation cannot exceed 5000 characters');\r\n\r\n/**\r\n * Tag validation.\r\n * Tags are normalized to lowercase and must be non-empty.\r\n */\r\nconst tagSchema = z.string()\r\n .min(1, 'Tag cannot be empty')\r\n .max(100, 'Tag cannot exceed 100 characters')\r\n .trim()\r\n .toLowerCase();\r\n\r\n/**\r\n * Importance validation.\r\n * Must be a number between MIN_IMPORTANCE and MAX_IMPORTANCE (0-10).\r\n */\r\nconst importanceSchema = z.number()\r\n .int('Importance must be an integer')\r\n .min(MIN_IMPORTANCE, `Importance must be at least ${MIN_IMPORTANCE}`)\r\n .max(MAX_IMPORTANCE, `Importance must be at most ${MAX_IMPORTANCE}`);\r\n\r\n/**\r\n * Relation type validation.\r\n * Should be in snake_case format (e.g., \"works_at\", \"manages\").\r\n */\r\nconst relationTypeSchema = z.string()\r\n .min(1, 'Relation type cannot be empty')\r\n .max(100, 'Relation type cannot exceed 100 characters')\r\n .trim();\r\n\r\n// ==================== Entity Schemas ====================\r\n\r\n/**\r\n * Complete Entity schema with all fields.\r\n * Used for validating full entity objects including timestamps.\r\n */\r\nexport const EntitySchema = z.object({\r\n name: entityNameSchema,\r\n entityType: entityTypeSchema,\r\n observations: z.array(observationSchema),\r\n createdAt: isoDateSchema.optional(),\r\n lastModified: isoDateSchema.optional(),\r\n tags: z.array(tagSchema).optional(),\r\n importance: importanceSchema.optional(),\r\n parentId: entityNameSchema.optional(),\r\n}).strict();\r\n\r\n/**\r\n * Entity creation input schema.\r\n * Used for validating user input when creating new entities.\r\n * Timestamps are optional and will be auto-generated if not provided.\r\n */\r\nexport const CreateEntitySchema = z.object({\r\n name: entityNameSchema,\r\n entityType: entityTypeSchema,\r\n observations: z.array(observationSchema),\r\n tags: z.array(tagSchema).optional(),\r\n importance: importanceSchema.optional(),\r\n parentId: entityNameSchema.optional(),\r\n createdAt: isoDateSchema.optional(),\r\n lastModified: isoDateSchema.optional(),\r\n}).strict();\r\n\r\n/**\r\n * Entity update input schema.\r\n * All fields are optional for partial updates.\r\n * Name cannot be updated (it's the unique identifier).\r\n */\r\nexport const UpdateEntitySchema = z.object({\r\n entityType: entityTypeSchema.optional(),\r\n observations: z.array(observationSchema).optional(),\r\n tags: z.array(tagSchema).optional(),\r\n importance: importanceSchema.optional(),\r\n parentId: entityNameSchema.optional(),\r\n}).strict();\r\n\r\n// ==================== Relation Schemas ====================\r\n\r\n/**\r\n * Complete Relation schema with all fields.\r\n * Used for validating full relation objects including timestamps.\r\n */\r\nexport const RelationSchema = z.object({\r\n from: entityNameSchema,\r\n to: entityNameSchema,\r\n relationType: relationTypeSchema,\r\n createdAt: isoDateSchema.optional(),\r\n lastModified: isoDateSchema.optional(),\r\n}).strict();\r\n\r\n/**\r\n * Relation creation input schema.\r\n * Used for validating user input when creating new relations.\r\n * Timestamps are optional and will be auto-generated if not provided.\r\n */\r\nexport const CreateRelationSchema = z.object({\r\n from: entityNameSchema,\r\n to: entityNameSchema,\r\n relationType: relationTypeSchema,\r\n createdAt: isoDateSchema.optional(),\r\n lastModified: isoDateSchema.optional(),\r\n}).strict();\r\n\r\n// ==================== Search Schemas ====================\r\n\r\n/**\r\n * Search query validation.\r\n * Validates text search queries with reasonable length constraints.\r\n */\r\nexport const SearchQuerySchema = z.string()\r\n .min(1, 'Search query cannot be empty')\r\n .max(1000, 'Search query cannot exceed 1000 characters')\r\n .trim();\r\n\r\n/**\r\n * Date range validation for search filters.\r\n */\r\nexport const DateRangeSchema = z.object({\r\n start: isoDateSchema,\r\n end: isoDateSchema,\r\n}).strict().refine(\r\n (data) => new Date(data.start) <= new Date(data.end),\r\n { message: 'Start date must be before or equal to end date' }\r\n);\r\n\r\n// ==================== Tag Schemas ====================\r\n\r\n/**\r\n * Tag alias validation for TagManager.\r\n */\r\nexport const TagAliasSchema = z.object({\r\n canonical: tagSchema,\r\n aliases: z.array(tagSchema).min(1, 'Must have at least one alias'),\r\n}).strict();\r\n\r\n// ==================== Export Schemas ====================\r\n\r\n/**\r\n * Export format validation.\r\n */\r\nexport const ExportFormatSchema = z.enum(['json', 'graphml', 'csv']);\r\n\r\n// ==================== Batch Operation Schemas ====================\r\n\r\n/**\r\n * Batch entity creation validation.\r\n * Validates array of entities with maximum constraints.\r\n * Empty arrays are allowed (no-op).\r\n */\r\nexport const BatchCreateEntitiesSchema = z.array(CreateEntitySchema)\r\n .max(1000, 'Cannot create more than 1000 entities in a single batch');\r\n\r\n/**\r\n * Batch relation creation validation.\r\n * Validates array of relations with maximum constraints.\r\n * Empty arrays are allowed (no-op).\r\n */\r\nexport const BatchCreateRelationsSchema = z.array(CreateRelationSchema)\r\n .max(1000, 'Cannot create more than 1000 relations in a single batch');\r\n\r\n/**\r\n * Entity name array validation for batch deletion.\r\n */\r\nexport const EntityNamesSchema = z.array(entityNameSchema)\r\n .min(1, 'Must specify at least one entity name')\r\n .max(1000, 'Cannot delete more than 1000 entities in a single batch');\r\n\r\n/**\r\n * Relation array validation for batch deletion.\r\n */\r\nexport const DeleteRelationsSchema = z.array(CreateRelationSchema)\r\n .min(1, 'Must specify at least one relation')\r\n .max(1000, 'Cannot delete more than 1000 relations in a single batch');\r\n\r\n// ==================== Observation Schemas ====================\r\n\r\n/**\r\n * Single observation input for add operations.\r\n * Empty contents array is allowed (no-op).\r\n */\r\nexport const AddObservationInputSchema = z.object({\r\n entityName: entityNameSchema,\r\n contents: z.array(observationSchema),\r\n}).strict();\r\n\r\n/**\r\n * Batch observation addition validation.\r\n * Empty array is allowed (no-op).\r\n */\r\nexport const AddObservationsInputSchema = z.array(AddObservationInputSchema)\r\n .max(1000, 'Cannot add observations to more than 1000 entities in a single batch');\r\n\r\n/**\r\n * Single observation deletion input.\r\n * Empty observations array is allowed (no-op).\r\n * Non-existent entities are silently skipped by the manager.\r\n */\r\nexport const DeleteObservationInputSchema = z.object({\r\n entityName: entityNameSchema,\r\n observations: z.array(observationSchema),\r\n}).strict();\r\n\r\n/**\r\n * Batch observation deletion validation.\r\n * Empty array is allowed (no-op).\r\n */\r\nexport const DeleteObservationsInputSchema = z.array(DeleteObservationInputSchema)\r\n .max(1000, 'Cannot delete observations from more than 1000 entities in a single batch');\r\n\r\n// ==================== Archive Schema ====================\r\n\r\n/**\r\n * Archive criteria validation.\r\n * All fields are optional - the manager handles the case when no criteria provided.\r\n */\r\nexport const ArchiveCriteriaSchema = z.object({\r\n olderThan: isoDateSchema.optional(),\r\n importanceLessThan: z.number().min(0).max(10).optional(),\r\n tags: z.array(tagSchema).optional(),\r\n}).strict();\r\n\r\n// ==================== Saved Search Schemas ====================\r\n\r\n/**\r\n * Saved search creation input validation.\r\n */\r\nexport const SavedSearchInputSchema = z.object({\r\n name: z.string().min(1, 'Search name cannot be empty').max(200, 'Search name cannot exceed 200 characters').trim(),\r\n description: z.string().max(1000, 'Description cannot exceed 1000 characters').optional(),\r\n query: SearchQuerySchema,\r\n tags: z.array(tagSchema).optional(),\r\n minImportance: importanceSchema.optional(),\r\n maxImportance: importanceSchema.optional(),\r\n entityType: entityTypeSchema.optional(),\r\n}).strict();\r\n\r\n/**\r\n * Saved search update validation.\r\n * All fields are optional for partial updates.\r\n */\r\nexport const SavedSearchUpdateSchema = z.object({\r\n description: z.string().max(1000, 'Description cannot exceed 1000 characters').optional(),\r\n query: SearchQuerySchema.optional(),\r\n tags: z.array(tagSchema).optional(),\r\n minImportance: importanceSchema.optional(),\r\n maxImportance: importanceSchema.optional(),\r\n entityType: entityTypeSchema.optional(),\r\n}).strict();\r\n\r\n// ==================== Import/Export Schemas ====================\r\n\r\n/**\r\n * Import format validation.\r\n */\r\nexport const ImportFormatSchema = z.enum(['json', 'csv', 'graphml']);\r\n\r\n/**\r\n * Export format validation (includes all output formats).\r\n */\r\nexport const ExtendedExportFormatSchema = z.enum(['json', 'csv', 'graphml', 'gexf', 'dot', 'markdown', 'mermaid']);\r\n\r\n/**\r\n * Merge strategy validation for imports.\r\n */\r\nexport const MergeStrategySchema = z.enum(['replace', 'skip', 'merge', 'fail']);\r\n\r\n/**\r\n * Export filter validation.\r\n */\r\nexport const ExportFilterSchema = z.object({\r\n startDate: isoDateSchema.optional(),\r\n endDate: isoDateSchema.optional(),\r\n entityType: entityTypeSchema.optional(),\r\n tags: z.array(tagSchema).optional(),\r\n}).strict();\r\n\r\n// ==================== Search Parameter Schemas ====================\r\n\r\n/**\r\n * Tags array validation (optional, for search filters).\r\n */\r\nexport const OptionalTagsSchema = z.array(tagSchema).optional();\r\n\r\n/**\r\n * Optional entity names array validation.\r\n */\r\nexport const OptionalEntityNamesSchema = z.array(entityNameSchema).optional();\r\n\r\n// ==================== Schema Type Exports ====================\r\n\r\nexport type EntityInput = z.infer<typeof EntitySchema>;\r\nexport type CreateEntityInput = z.infer<typeof CreateEntitySchema>;\r\nexport type UpdateEntityInput = z.infer<typeof UpdateEntitySchema>;\r\nexport type RelationInput = z.infer<typeof RelationSchema>;\r\nexport type CreateRelationInput = z.infer<typeof CreateRelationSchema>;\r\nexport type SearchQuery = z.infer<typeof SearchQuerySchema>;\r\nexport type DateRange = z.infer<typeof DateRangeSchema>;\r\nexport type TagAliasInput = z.infer<typeof TagAliasSchema>;\r\nexport type AddObservationInput = z.infer<typeof AddObservationInputSchema>;\r\nexport type DeleteObservationInput = z.infer<typeof DeleteObservationInputSchema>;\r\nexport type ArchiveCriteriaInput = z.infer<typeof ArchiveCriteriaSchema>;\r\nexport type SavedSearchInput = z.infer<typeof SavedSearchInputSchema>;\r\nexport type SavedSearchUpdateInput = z.infer<typeof SavedSearchUpdateSchema>;\r\nexport type ImportFormatInput = z.infer<typeof ImportFormatSchema>;\r\nexport type ExtendedExportFormatInput = z.infer<typeof ExtendedExportFormatSchema>;\r\nexport type MergeStrategyInput = z.infer<typeof MergeStrategySchema>;\r\nexport type ExportFilterInput = z.infer<typeof ExportFilterSchema>;\r\n\r\n// ==================== Validation Result Type ====================\r\n\r\n/**\r\n * Validation result with status and error messages.\r\n */\r\nexport interface ValidationResult {\r\n valid: boolean;\r\n errors: string[];\r\n}\r\n\r\n// ==================== Zod Validation Helpers ====================\r\n\r\n/**\r\n * Formats Zod errors into human-readable strings.\r\n *\r\n * @param error - Zod error object\r\n * @returns Array of formatted error messages\r\n */\r\nexport function formatZodErrors(error: ZodError): string[] {\r\n return error.issues.map(issue => {\r\n const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : '';\r\n return `${path}${issue.message}`;\r\n });\r\n}\r\n\r\n/**\r\n * Validates data against a Zod schema and returns the typed result.\r\n * Throws ValidationError with formatted error messages on failure.\r\n *\r\n * @param data - The data to validate\r\n * @param schema - The Zod schema to validate against\r\n * @param errorMessage - Custom error message prefix (default: 'Validation failed')\r\n * @returns The validated and typed data\r\n * @throws ValidationError if validation fails\r\n *\r\n * @example\r\n * ```typescript\r\n * const entities = validateWithSchema(\r\n * input,\r\n * BatchCreateEntitiesSchema,\r\n * 'Invalid entity data'\r\n * );\r\n * ```\r\n */\r\nexport function validateWithSchema<T>(\r\n data: unknown,\r\n schema: ZodSchema<T>,\r\n errorMessage: string = 'Validation failed'\r\n): T {\r\n const result = schema.safeParse(data);\r\n if (!result.success) {\r\n const errors = formatZodErrors(result.error);\r\n throw new ValidationError(errorMessage, errors);\r\n }\r\n return result.data;\r\n}\r\n\r\n/**\r\n * Validates data and returns a result object instead of throwing.\r\n * Useful when you want to handle validation errors gracefully.\r\n *\r\n * @param data - The data to validate\r\n * @param schema - The Zod schema to validate against\r\n * @returns Result object with success status and either data or errors\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = validateSafe(input, EntitySchema);\r\n * if (result.success) {\r\n * console.log(result.data);\r\n * } else {\r\n * console.error(result.errors);\r\n * }\r\n * ```\r\n */\r\nexport function validateSafe<T>(\r\n data: unknown,\r\n schema: ZodSchema<T>\r\n): { success: true; data: T } | { success: false; errors: string[] } {\r\n const result = schema.safeParse(data);\r\n if (result.success) {\r\n return { success: true, data: result.data };\r\n }\r\n return { success: false, errors: formatZodErrors(result.error) };\r\n}\r\n\r\n/**\r\n * Validates an array of items against a schema.\r\n * Returns detailed information about which items failed validation.\r\n *\r\n * @param items - Array of items to validate\r\n * @param schema - Zod schema for individual items\r\n * @param errorMessage - Custom error message prefix\r\n * @returns Array of validated items\r\n * @throws ValidationError if any item fails validation\r\n */\r\nexport function validateArrayWithSchema<T>(\r\n items: unknown[],\r\n schema: ZodSchema<T>,\r\n errorMessage: string = 'Array validation failed'\r\n): T[] {\r\n const errors: string[] = [];\r\n const validated: T[] = [];\r\n\r\n for (let i = 0; i < items.length; i++) {\r\n const result = schema.safeParse(items[i]);\r\n if (result.success) {\r\n validated.push(result.data);\r\n } else {\r\n const itemErrors = formatZodErrors(result.error);\r\n errors.push(...itemErrors.map(e => `[${i}] ${e}`));\r\n }\r\n }\r\n\r\n if (errors.length > 0) {\r\n throw new ValidationError(errorMessage, errors);\r\n }\r\n\r\n return validated;\r\n}\r\n\r\n// ==================== Manual Validation Functions ====================\r\n\r\n/**\r\n * Type guard to check if value is a non-null object.\r\n */\r\nfunction isObject(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n\r\n/**\r\n * Validate an entity object.\r\n *\r\n * Checks required fields and data types.\r\n *\r\n * @param entity - Entity to validate (unknown type for runtime validation)\r\n * @returns Validation result\r\n */\r\nexport function validateEntity(entity: unknown): ValidationResult {\r\n const errors: string[] = [];\r\n\r\n if (!isObject(entity)) {\r\n return { valid: false, errors: ['Entity must be an object'] };\r\n }\r\n\r\n if (!entity.name || typeof entity.name !== 'string' || entity.name.trim() === '') {\r\n errors.push('Entity name is required and must be a non-empty string');\r\n }\r\n\r\n if (!entity.entityType || typeof entity.entityType !== 'string' || entity.entityType.trim() === '') {\r\n errors.push('Entity type is required and must be a non-empty string');\r\n }\r\n\r\n if (!Array.isArray(entity.observations)) {\r\n errors.push('Observations must be an array');\r\n } else if (!entity.observations.every((o: unknown) => typeof o === 'string')) {\r\n errors.push('All observations must be strings');\r\n }\r\n\r\n if (entity.tags !== undefined) {\r\n if (!Array.isArray(entity.tags)) {\r\n errors.push('Tags must be an array');\r\n } else if (!entity.tags.every((t: unknown) => typeof t === 'string')) {\r\n errors.push('All tags must be strings');\r\n }\r\n }\r\n\r\n if (entity.importance !== undefined) {\r\n if (typeof entity.importance !== 'number') {\r\n errors.push('Importance must be a number');\r\n } else if (!validateImportance(entity.importance)) {\r\n errors.push('Importance must be between 0 and 10');\r\n }\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n\r\n/**\r\n * Validate a relation object.\r\n *\r\n * Checks required fields and data types.\r\n *\r\n * @param relation - Relation to validate (unknown type for runtime validation)\r\n * @returns Validation result\r\n */\r\nexport function validateRelation(relation: unknown): ValidationResult {\r\n const errors: string[] = [];\r\n\r\n if (!isObject(relation)) {\r\n return { valid: false, errors: ['Relation must be an object'] };\r\n }\r\n\r\n if (!relation.from || typeof relation.from !== 'string' || relation.from.trim() === '') {\r\n errors.push('Relation \"from\" is required and must be a non-empty string');\r\n }\r\n\r\n if (!relation.to || typeof relation.to !== 'string' || relation.to.trim() === '') {\r\n errors.push('Relation \"to\" is required and must be a non-empty string');\r\n }\r\n\r\n if (!relation.relationType || typeof relation.relationType !== 'string' || relation.relationType.trim() === '') {\r\n errors.push('Relation type is required and must be a non-empty string');\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n\r\n/**\r\n * Validate importance level (must be 0-10).\r\n *\r\n * @param importance - Importance value to validate\r\n * @returns True if valid\r\n */\r\nexport function validateImportance(importance: number): boolean {\r\n return typeof importance === 'number'\r\n && !isNaN(importance)\r\n && importance >= IMPORTANCE_RANGE.MIN\r\n && importance <= IMPORTANCE_RANGE.MAX;\r\n}\r\n\r\n/**\r\n * Validate an array of tags.\r\n *\r\n * @param tags - Tags array to validate (unknown type for runtime validation)\r\n * @returns Validation result\r\n */\r\nexport function validateTags(tags: unknown): ValidationResult {\r\n const errors: string[] = [];\r\n\r\n if (!Array.isArray(tags)) {\r\n return { valid: false, errors: ['Tags must be an array'] };\r\n }\r\n\r\n if (!tags.every((t: unknown) => typeof t === 'string' && t.trim() !== '')) {\r\n errors.push('All tags must be non-empty strings');\r\n }\r\n\r\n return { valid: errors.length === 0, errors };\r\n}\r\n","/**\r\n * Response and Pagination Formatters\r\n *\r\n * Consolidated module for MCP tool response formatting and pagination utilities.\r\n * Centralizes response formatting for MCP tool calls to eliminate redundant patterns.\r\n *\r\n * @module utils/formatters\r\n */\r\n\r\nimport { SEARCH_LIMITS } from './constants.js';\r\n\r\n// ==================== MCP Tool Response Formatting ====================\r\n\r\n/**\r\n * MCP Tool Response type - uses the exact shape expected by the SDK.\r\n * The 'as const' assertion ensures the type literal is preserved.\r\n */\r\nexport type ToolResponse = {\r\n content: Array<{ type: 'text'; text: string }>;\r\n isError?: boolean;\r\n};\r\n\r\n/**\r\n * Formats data as an MCP tool response with JSON content.\r\n * Centralizes the response format to ensure consistency and reduce duplication.\r\n *\r\n * @param data - Any data to be JSON stringified\r\n * @returns Formatted MCP tool response\r\n */\r\nexport function formatToolResponse(data: unknown) {\r\n return {\r\n content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }],\r\n };\r\n}\r\n\r\n/**\r\n * Formats a simple text message as an MCP tool response.\r\n * Use for success messages that don't need JSON formatting.\r\n *\r\n * @param message - Plain text message\r\n * @returns Formatted MCP tool response\r\n */\r\nexport function formatTextResponse(message: string) {\r\n return {\r\n content: [{ type: 'text' as const, text: message }],\r\n };\r\n}\r\n\r\n/**\r\n * Formats raw string content as an MCP tool response.\r\n * Use for export formats that return pre-formatted strings (markdown, CSV, etc.)\r\n *\r\n * @param content - Raw string content\r\n * @returns Formatted MCP tool response\r\n */\r\nexport function formatRawResponse(content: string) {\r\n return {\r\n content: [{ type: 'text' as const, text: content }],\r\n };\r\n}\r\n\r\n/**\r\n * Formats an error as an MCP tool response with isError flag.\r\n *\r\n * @param error - Error object or message string\r\n * @returns Formatted MCP tool error response\r\n */\r\nexport function formatErrorResponse(error: Error | string) {\r\n const message = error instanceof Error ? error.message : error;\r\n return {\r\n content: [{ type: 'text' as const, text: message }],\r\n isError: true,\r\n };\r\n}\r\n\r\n// ==================== Pagination Utilities ====================\r\n\r\n/**\r\n * Validated pagination parameters with helper methods.\r\n */\r\nexport interface ValidatedPagination {\r\n /** Validated offset (guaranteed >= 0) */\r\n offset: number;\r\n /** Validated limit (guaranteed within SEARCH_LIMITS.MIN to SEARCH_LIMITS.MAX) */\r\n limit: number;\r\n /**\r\n * Check if there are more results beyond the current page.\r\n * @param totalCount - Total number of items\r\n * @returns true if there are more items after this page\r\n */\r\n hasMore: (totalCount: number) => boolean;\r\n}\r\n\r\n/**\r\n * Validates and normalizes pagination parameters.\r\n * Ensures offset is non-negative and limit is within configured bounds.\r\n *\r\n * @param offset - Starting position (default: 0)\r\n * @param limit - Maximum results to return (default: SEARCH_LIMITS.DEFAULT)\r\n * @returns Validated pagination parameters with helper methods\r\n *\r\n * @example\r\n * ```typescript\r\n * const pagination = validatePagination(10, 50);\r\n * const results = items.slice(pagination.offset, pagination.offset + pagination.limit);\r\n * if (pagination.hasMore(items.length)) {\r\n * console.log('More results available');\r\n * }\r\n * ```\r\n */\r\nexport function validatePagination(\r\n offset: number = 0,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n): ValidatedPagination {\r\n const validatedOffset = Math.max(0, offset);\r\n const validatedLimit = Math.min(\r\n Math.max(SEARCH_LIMITS.MIN, limit),\r\n SEARCH_LIMITS.MAX\r\n );\r\n\r\n return {\r\n offset: validatedOffset,\r\n limit: validatedLimit,\r\n hasMore: (totalCount: number) => validatedOffset + validatedLimit < totalCount,\r\n };\r\n}\r\n\r\n/**\r\n * Applies pagination to an array of items.\r\n *\r\n * @param items - Array to paginate\r\n * @param pagination - Validated pagination parameters\r\n * @returns Paginated slice of the array\r\n *\r\n * @example\r\n * ```typescript\r\n * const pagination = validatePagination(offset, limit);\r\n * const pageResults = applyPagination(allResults, pagination);\r\n * ```\r\n */\r\nexport function applyPagination<T>(\r\n items: T[],\r\n pagination: ValidatedPagination\r\n): T[] {\r\n return items.slice(pagination.offset, pagination.offset + pagination.limit);\r\n}\r\n\r\n/**\r\n * Applies pagination using raw offset and limit values.\r\n * Combines validation and application in one call.\r\n *\r\n * @param items - Array to paginate\r\n * @param offset - Starting position\r\n * @param limit - Maximum results\r\n * @returns Paginated slice of the array\r\n */\r\nexport function paginateArray<T>(\r\n items: T[],\r\n offset: number = 0,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n): T[] {\r\n const pagination = validatePagination(offset, limit);\r\n return applyPagination(items, pagination);\r\n}\r\n\r\n/**\r\n * Calculates pagination metadata for a result set.\r\n *\r\n * @param totalCount - Total number of items\r\n * @param offset - Current offset\r\n * @param limit - Current limit\r\n * @returns Pagination metadata\r\n */\r\nexport function getPaginationMeta(\r\n totalCount: number,\r\n offset: number = 0,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n): {\r\n totalCount: number;\r\n offset: number;\r\n limit: number;\r\n hasMore: boolean;\r\n pageNumber: number;\r\n totalPages: number;\r\n} {\r\n const pagination = validatePagination(offset, limit);\r\n\r\n return {\r\n totalCount,\r\n offset: pagination.offset,\r\n limit: pagination.limit,\r\n hasMore: pagination.hasMore(totalCount),\r\n pageNumber: Math.floor(pagination.offset / pagination.limit) + 1,\r\n totalPages: Math.ceil(totalCount / pagination.limit),\r\n };\r\n}\r\n","/**\r\n * Entity Utilities\r\n *\r\n * Consolidated module for entity-related utilities including:\r\n * - Entity lookup and manipulation functions\r\n * - Tag normalization and matching\r\n * - Date parsing and validation\r\n * - Entity filtering by various criteria\r\n * - Path utilities and validation\r\n *\r\n * @module utils/entityUtils\r\n */\r\n\r\nimport { promises as fs } from 'fs';\r\nimport path from 'path';\r\nimport type { Entity, KnowledgeGraph } from '../types/index.js';\r\nimport { EntityNotFoundError, FileOperationError } from './errors.js';\r\n\r\n// ==================== Hash Functions ====================\r\n\r\n/**\r\n * FNV-1a hash function for fast string hashing.\r\n *\r\n * This is a non-cryptographic hash function that provides good distribution\r\n * for bucketing and deduplication purposes. It's optimized for speed\r\n * and produces a 32-bit unsigned integer.\r\n *\r\n * FNV-1a has the following properties:\r\n * - Fast computation (single pass through string)\r\n * - Good distribution for hash table use\r\n * - Deterministic output for same input\r\n *\r\n * @param text - The string to hash\r\n * @returns A 32-bit unsigned integer hash value\r\n *\r\n * @example\r\n * ```typescript\r\n * const hash = fnv1aHash('hello');\r\n * console.log(hash); // 1335831723\r\n *\r\n * // Use for bucketing similar entities\r\n * const bucket = fnv1aHash(entity.name.toLowerCase()) % numBuckets;\r\n * ```\r\n */\r\nexport function fnv1aHash(text: string): number {\r\n let hash = 2166136261; // FNV offset basis\r\n for (let i = 0; i < text.length; i++) {\r\n hash ^= text.charCodeAt(i);\r\n hash = Math.imul(hash, 16777619); // FNV prime\r\n }\r\n return hash >>> 0; // Convert to unsigned 32-bit integer\r\n}\r\n\r\n// ==================== Entity Lookup Functions ====================\r\n\r\n/**\r\n * Finds an entity by name in the graph.\r\n * Overloaded to provide type-safe returns based on throwIfNotFound parameter.\r\n *\r\n * @param graph - The knowledge graph to search\r\n * @param name - The entity name to find\r\n * @param throwIfNotFound - Whether to throw if entity doesn't exist (default: true)\r\n * @returns The entity if found, null if not found and throwIfNotFound is false\r\n * @throws EntityNotFoundError if entity not found and throwIfNotFound is true\r\n */\r\nexport function findEntityByName(\r\n graph: KnowledgeGraph,\r\n name: string,\r\n throwIfNotFound: true\r\n): Entity;\r\nexport function findEntityByName(\r\n graph: KnowledgeGraph,\r\n name: string,\r\n throwIfNotFound: false\r\n): Entity | null;\r\nexport function findEntityByName(\r\n graph: KnowledgeGraph,\r\n name: string,\r\n throwIfNotFound?: boolean\r\n): Entity | null;\r\nexport function findEntityByName(\r\n graph: KnowledgeGraph,\r\n name: string,\r\n throwIfNotFound: boolean = true\r\n): Entity | null {\r\n const entity = graph.entities.find(e => e.name === name);\r\n if (!entity && throwIfNotFound) {\r\n throw new EntityNotFoundError(name);\r\n }\r\n return entity ?? null;\r\n}\r\n\r\n/**\r\n * Finds multiple entities by name.\r\n *\r\n * @param graph - The knowledge graph to search\r\n * @param names - Array of entity names to find\r\n * @param throwIfAnyNotFound - Whether to throw if any entity doesn't exist (default: true)\r\n * @returns Array of found entities (may be shorter than names if throwIfAnyNotFound is false)\r\n * @throws EntityNotFoundError if any entity not found and throwIfAnyNotFound is true\r\n */\r\nexport function findEntitiesByNames(\r\n graph: KnowledgeGraph,\r\n names: string[],\r\n throwIfAnyNotFound: boolean = true\r\n): Entity[] {\r\n const entities: Entity[] = [];\r\n\r\n for (const name of names) {\r\n const entity = findEntityByName(graph, name, false);\r\n if (entity) {\r\n entities.push(entity);\r\n } else if (throwIfAnyNotFound) {\r\n throw new EntityNotFoundError(name);\r\n }\r\n }\r\n\r\n return entities;\r\n}\r\n\r\n/**\r\n * Checks if an entity exists in the graph.\r\n *\r\n * @param graph - The knowledge graph to search\r\n * @param name - The entity name to check\r\n * @returns true if entity exists, false otherwise\r\n */\r\nexport function entityExists(graph: KnowledgeGraph, name: string): boolean {\r\n return graph.entities.some(e => e.name === name);\r\n}\r\n\r\n/**\r\n * Gets the index of an entity in the graph's entities array.\r\n *\r\n * @param graph - The knowledge graph to search\r\n * @param name - The entity name to find\r\n * @returns The index if found, -1 otherwise\r\n */\r\nexport function getEntityIndex(graph: KnowledgeGraph, name: string): number {\r\n return graph.entities.findIndex(e => e.name === name);\r\n}\r\n\r\n/**\r\n * Removes an entity from the graph by name.\r\n * Mutates the graph's entities array in place.\r\n *\r\n * @param graph - The knowledge graph to modify\r\n * @param name - The entity name to remove\r\n * @returns true if entity was removed, false if not found\r\n */\r\nexport function removeEntityByName(graph: KnowledgeGraph, name: string): boolean {\r\n const index = getEntityIndex(graph, name);\r\n if (index === -1) return false;\r\n graph.entities.splice(index, 1);\r\n return true;\r\n}\r\n\r\n/**\r\n * Gets all entity names as a Set for fast lookup.\r\n *\r\n * @param graph - The knowledge graph\r\n * @returns Set of all entity names\r\n */\r\nexport function getEntityNameSet(graph: KnowledgeGraph): Set<string> {\r\n return new Set(graph.entities.map(e => e.name));\r\n}\r\n\r\n/**\r\n * Groups entities by their type.\r\n *\r\n * @param entities - Array of entities to group\r\n * @returns Map of entity type to array of entities\r\n */\r\nexport function groupEntitiesByType(entities: Entity[]): Map<string, Entity[]> {\r\n const groups = new Map<string, Entity[]>();\r\n\r\n for (const entity of entities) {\r\n const type = entity.entityType;\r\n if (!groups.has(type)) {\r\n groups.set(type, []);\r\n }\r\n groups.get(type)!.push(entity);\r\n }\r\n\r\n return groups;\r\n}\r\n\r\n/**\r\n * Updates the lastModified timestamp on an entity.\r\n * Mutates the entity in place.\r\n *\r\n * @param entity - The entity to update\r\n * @returns The updated entity (same reference)\r\n */\r\nexport function touchEntity(entity: Entity): Entity {\r\n entity.lastModified = new Date().toISOString();\r\n return entity;\r\n}\r\n\r\n// ==================== Tag Normalization and Matching ====================\r\n\r\n/**\r\n * Normalizes a single tag to lowercase and trimmed.\r\n *\r\n * @param tag - Tag to normalize\r\n * @returns Normalized tag\r\n */\r\nexport function normalizeTag(tag: string): string {\r\n return tag.toLowerCase().trim();\r\n}\r\n\r\n/**\r\n * Normalizes an array of tags to lowercase.\r\n * Handles undefined/null input gracefully.\r\n *\r\n * @param tags - Array of tags to normalize, or undefined\r\n * @returns Normalized tags array, or empty array if input is undefined/null\r\n */\r\nexport function normalizeTags(tags: string[] | undefined | null): string[] {\r\n if (!tags || tags.length === 0) return [];\r\n return tags.map(tag => tag.toLowerCase());\r\n}\r\n\r\n/**\r\n * Checks if an entity's tags include any of the specified search tags.\r\n * Both inputs are normalized before comparison.\r\n *\r\n * @param entityTags - Tags on the entity (may be undefined)\r\n * @param searchTags - Tags to search for (may be undefined)\r\n * @returns true if any search tag matches any entity tag, false if no match or either is empty\r\n */\r\nexport function hasMatchingTag(\r\n entityTags: string[] | undefined,\r\n searchTags: string[] | undefined\r\n): boolean {\r\n if (!entityTags || entityTags.length === 0) return false;\r\n if (!searchTags || searchTags.length === 0) return false;\r\n\r\n const normalizedEntity = normalizeTags(entityTags);\r\n const normalizedSearch = normalizeTags(searchTags);\r\n\r\n return normalizedSearch.some(tag => normalizedEntity.includes(tag));\r\n}\r\n\r\n/**\r\n * Checks if entity tags include ALL of the specified required tags.\r\n *\r\n * @param entityTags - Tags on the entity (may be undefined)\r\n * @param requiredTags - All tags that must be present\r\n * @returns true if all required tags are present\r\n */\r\nexport function hasAllTags(\r\n entityTags: string[] | undefined,\r\n requiredTags: string[]\r\n): boolean {\r\n if (!entityTags || entityTags.length === 0) return false;\r\n if (requiredTags.length === 0) return true;\r\n\r\n const normalizedEntity = normalizeTags(entityTags);\r\n return normalizeTags(requiredTags).every(tag => normalizedEntity.includes(tag));\r\n}\r\n\r\n/**\r\n * Filters entities by tag match.\r\n * Returns all entities if searchTags is empty or undefined.\r\n *\r\n * @param entities - Array of entities with optional tags property\r\n * @param searchTags - Tags to filter by\r\n * @returns Filtered entities that have at least one matching tag\r\n */\r\nexport function filterByTags<T extends { tags?: string[] }>(\r\n entities: T[],\r\n searchTags: string[] | undefined\r\n): T[] {\r\n if (!searchTags || searchTags.length === 0) {\r\n return entities;\r\n }\r\n\r\n const normalizedSearch = normalizeTags(searchTags);\r\n\r\n return entities.filter(entity => {\r\n if (!entity.tags || entity.tags.length === 0) return false;\r\n const normalizedEntity = normalizeTags(entity.tags);\r\n return normalizedSearch.some(tag => normalizedEntity.includes(tag));\r\n });\r\n}\r\n\r\n/**\r\n * Adds new tags to an existing tag array, avoiding duplicates.\r\n * All tags are normalized to lowercase.\r\n *\r\n * @param existingTags - Current tags (may be undefined)\r\n * @param newTags - Tags to add\r\n * @returns Combined tags array with no duplicates\r\n */\r\nexport function addUniqueTags(\r\n existingTags: string[] | undefined,\r\n newTags: string[]\r\n): string[] {\r\n const existing = normalizeTags(existingTags);\r\n const toAdd = normalizeTags(newTags);\r\n\r\n const uniqueNew = toAdd.filter(tag => !existing.includes(tag));\r\n return [...existing, ...uniqueNew];\r\n}\r\n\r\n/**\r\n * Removes specified tags from an existing tag array.\r\n * Comparison is case-insensitive.\r\n *\r\n * @param existingTags - Current tags (may be undefined)\r\n * @param tagsToRemove - Tags to remove\r\n * @returns Tags array with specified tags removed\r\n */\r\nexport function removeTags(\r\n existingTags: string[] | undefined,\r\n tagsToRemove: string[]\r\n): string[] {\r\n if (!existingTags || existingTags.length === 0) return [];\r\n\r\n const toRemoveNormalized = normalizeTags(tagsToRemove);\r\n return existingTags.filter(tag => !toRemoveNormalized.includes(tag.toLowerCase()));\r\n}\r\n\r\n// ==================== Date Utilities ====================\r\n\r\n/**\r\n * Check if a date falls within a specified range.\r\n *\r\n * @param date - ISO 8601 date string to check (may be undefined)\r\n * @param start - Optional start date (inclusive)\r\n * @param end - Optional end date (inclusive)\r\n * @returns True if date is within range or no filters are set\r\n *\r\n * @example\r\n * ```typescript\r\n * isWithinDateRange('2024-06-15T00:00:00Z', '2024-01-01T00:00:00Z', '2024-12-31T23:59:59Z'); // true\r\n * isWithinDateRange('2024-06-15T00:00:00Z', '2024-07-01T00:00:00Z'); // false\r\n * isWithinDateRange(undefined); // true (no filters)\r\n * isWithinDateRange(undefined, '2024-01-01T00:00:00Z'); // false (has filter but no date)\r\n * ```\r\n */\r\nexport function isWithinDateRange(\r\n date: string | undefined,\r\n start?: string,\r\n end?: string\r\n): boolean {\r\n // If no filters set, always pass\r\n if (!start && !end) {\r\n return true;\r\n }\r\n\r\n // If date is undefined but we have filters, fail\r\n if (!date) {\r\n return false;\r\n }\r\n\r\n const dateObj = new Date(date);\r\n\r\n if (isNaN(dateObj.getTime())) {\r\n return false;\r\n }\r\n\r\n if (start) {\r\n const startObj = new Date(start);\r\n if (isNaN(startObj.getTime())) {\r\n return false;\r\n }\r\n if (dateObj < startObj) {\r\n return false;\r\n }\r\n }\r\n\r\n if (end) {\r\n const endObj = new Date(end);\r\n if (isNaN(endObj.getTime())) {\r\n return false;\r\n }\r\n if (dateObj > endObj) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Parse and validate date range strings.\r\n *\r\n * @param startDate - Optional ISO 8601 start date\r\n * @param endDate - Optional ISO 8601 end date\r\n * @returns Parsed Date objects or null\r\n */\r\nexport function parseDateRange(\r\n startDate?: string,\r\n endDate?: string\r\n): { start: Date | null; end: Date | null } {\r\n let start: Date | null = null;\r\n let end: Date | null = null;\r\n\r\n if (startDate) {\r\n start = new Date(startDate);\r\n if (isNaN(start.getTime())) {\r\n start = null;\r\n }\r\n }\r\n\r\n if (endDate) {\r\n end = new Date(endDate);\r\n if (isNaN(end.getTime())) {\r\n end = null;\r\n }\r\n }\r\n\r\n return { start, end };\r\n}\r\n\r\n/**\r\n * Validate if a string is a valid ISO 8601 date.\r\n *\r\n * @param date - Date string to validate\r\n * @returns True if valid ISO 8601 date\r\n */\r\nexport function isValidISODate(date: string): boolean {\r\n const dateObj = new Date(date);\r\n return !isNaN(dateObj.getTime()) && dateObj.toISOString() === date;\r\n}\r\n\r\n/**\r\n * Get current timestamp in ISO 8601 format.\r\n *\r\n * @returns Current timestamp string\r\n */\r\nexport function getCurrentTimestamp(): string {\r\n return new Date().toISOString();\r\n}\r\n\r\n// ==================== Filter Utilities ====================\r\n\r\n/**\r\n * Checks if an entity's importance is within the specified range.\r\n * Entities without importance are treated as not matching if any filter is set.\r\n *\r\n * @param importance - The entity's importance value (may be undefined)\r\n * @param minImportance - Minimum importance filter (inclusive)\r\n * @param maxImportance - Maximum importance filter (inclusive)\r\n * @returns true if importance is within range or no filters are set\r\n *\r\n * @example\r\n * ```typescript\r\n * // Check if entity passes importance filter\r\n * if (isWithinImportanceRange(entity.importance, 5, 10)) {\r\n * // Entity has importance between 5 and 10\r\n * }\r\n * ```\r\n */\r\nexport function isWithinImportanceRange(\r\n importance: number | undefined,\r\n minImportance?: number,\r\n maxImportance?: number\r\n): boolean {\r\n // If no filters set, always pass\r\n if (minImportance === undefined && maxImportance === undefined) {\r\n return true;\r\n }\r\n\r\n // Check minimum importance\r\n if (minImportance !== undefined) {\r\n if (importance === undefined || importance < minImportance) {\r\n return false;\r\n }\r\n }\r\n\r\n // Check maximum importance\r\n if (maxImportance !== undefined) {\r\n if (importance === undefined || importance > maxImportance) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Filters entities by importance range.\r\n * Returns all entities if no importance filters are specified.\r\n *\r\n * @param entities - Array of entities to filter\r\n * @param minImportance - Minimum importance filter (inclusive)\r\n * @param maxImportance - Maximum importance filter (inclusive)\r\n * @returns Filtered entities within the importance range\r\n */\r\nexport function filterByImportance(\r\n entities: Entity[],\r\n minImportance?: number,\r\n maxImportance?: number\r\n): Entity[] {\r\n if (minImportance === undefined && maxImportance === undefined) {\r\n return entities;\r\n }\r\n return entities.filter(e =>\r\n isWithinImportanceRange(e.importance, minImportance, maxImportance)\r\n );\r\n}\r\n\r\n/**\r\n * Filters entities by creation date range.\r\n *\r\n * @param entities - Array of entities to filter\r\n * @param startDate - Start of date range (inclusive)\r\n * @param endDate - End of date range (inclusive)\r\n * @returns Filtered entities created within the date range\r\n */\r\nexport function filterByCreatedDate(\r\n entities: Entity[],\r\n startDate?: string,\r\n endDate?: string\r\n): Entity[] {\r\n if (!startDate && !endDate) {\r\n return entities;\r\n }\r\n return entities.filter(e =>\r\n isWithinDateRange(e.createdAt, startDate, endDate)\r\n );\r\n}\r\n\r\n/**\r\n * Filters entities by last modified date range.\r\n *\r\n * @param entities - Array of entities to filter\r\n * @param startDate - Start of date range (inclusive)\r\n * @param endDate - End of date range (inclusive)\r\n * @returns Filtered entities modified within the date range\r\n */\r\nexport function filterByModifiedDate(\r\n entities: Entity[],\r\n startDate?: string,\r\n endDate?: string\r\n): Entity[] {\r\n if (!startDate && !endDate) {\r\n return entities;\r\n }\r\n return entities.filter(e =>\r\n isWithinDateRange(e.lastModified, startDate, endDate)\r\n );\r\n}\r\n\r\n/**\r\n * Filters entities by entity type.\r\n *\r\n * @param entities - Array of entities to filter\r\n * @param entityType - Entity type to filter by (case-sensitive)\r\n * @returns Filtered entities of the specified type\r\n */\r\nexport function filterByEntityType(\r\n entities: Entity[],\r\n entityType?: string\r\n): Entity[] {\r\n if (!entityType) {\r\n return entities;\r\n }\r\n return entities.filter(e => e.entityType === entityType);\r\n}\r\n\r\n/**\r\n * Common search filters that can be applied to entities.\r\n */\r\nexport interface CommonSearchFilters {\r\n tags?: string[];\r\n minImportance?: number;\r\n maxImportance?: number;\r\n entityType?: string;\r\n createdAfter?: string;\r\n createdBefore?: string;\r\n modifiedAfter?: string;\r\n modifiedBefore?: string;\r\n}\r\n\r\n/**\r\n * Checks if an entity passes all the specified filters.\r\n * Short-circuits on first failing filter for performance.\r\n *\r\n * Note: Tag filtering should be handled separately using hasMatchingTag\r\n * as it requires special normalization logic.\r\n *\r\n * @param entity - Entity to check\r\n * @param filters - Filters to apply\r\n * @returns true if entity passes all filters\r\n */\r\nexport function entityPassesFilters(\r\n entity: Entity,\r\n filters: Omit<CommonSearchFilters, 'tags'>\r\n): boolean {\r\n // Importance filter\r\n if (!isWithinImportanceRange(entity.importance, filters.minImportance, filters.maxImportance)) {\r\n return false;\r\n }\r\n\r\n // Entity type filter\r\n if (filters.entityType && entity.entityType !== filters.entityType) {\r\n return false;\r\n }\r\n\r\n // Created date filter\r\n if (!isWithinDateRange(entity.createdAt, filters.createdAfter, filters.createdBefore)) {\r\n return false;\r\n }\r\n\r\n // Modified date filter\r\n if (!isWithinDateRange(entity.lastModified, filters.modifiedAfter, filters.modifiedBefore)) {\r\n return false;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n// ==================== Security Utilities ====================\r\n\r\n/**\r\n * Dangerous keys that should never be allowed in object assignment.\r\n * These can be used for prototype pollution attacks.\r\n */\r\nconst DANGEROUS_KEYS = new Set([\r\n '__proto__',\r\n 'constructor',\r\n 'prototype',\r\n]);\r\n\r\n/**\r\n * Sanitizes an object by removing potentially dangerous keys.\r\n * This prevents prototype pollution attacks when using Object.assign() or spread operators.\r\n *\r\n * @param obj - The object to sanitize\r\n * @returns A new object with dangerous keys removed\r\n *\r\n * @example\r\n * ```typescript\r\n * // Safe usage with Object.assign\r\n * const updates = sanitizeObject(userInput);\r\n * Object.assign(entity, updates);\r\n *\r\n * // Protects against prototype pollution\r\n * const malicious = { __proto__: { admin: true } };\r\n * const safe = sanitizeObject(malicious); // { }\r\n * ```\r\n */\r\nexport function sanitizeObject<T extends Record<string, unknown>>(obj: T): Partial<T> {\r\n if (obj === null || typeof obj !== 'object') {\r\n return obj;\r\n }\r\n\r\n const result: Partial<T> = {};\r\n\r\n for (const key of Object.keys(obj)) {\r\n // Skip dangerous keys\r\n if (DANGEROUS_KEYS.has(key)) {\r\n continue;\r\n }\r\n\r\n // Recursively sanitize nested objects\r\n const value = obj[key];\r\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\r\n result[key as keyof T] = sanitizeObject(value as Record<string, unknown>) as T[keyof T];\r\n } else {\r\n result[key as keyof T] = value as T[keyof T];\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * CSV formula injection dangerous characters.\r\n * These can cause spreadsheet applications to execute formulas.\r\n */\r\nconst CSV_FORMULA_CHARS = new Set(['=', '+', '-', '@', '\\t', '\\r']);\r\n\r\n/**\r\n * Escapes a CSV field to prevent formula injection attacks.\r\n * Prepends a single quote to values that start with dangerous characters.\r\n *\r\n * @param field - The field value to escape\r\n * @returns Escaped field value safe for CSV export\r\n *\r\n * @example\r\n * ```typescript\r\n * escapeCsvFormula('=SUM(A1:A10)'); // \"'=SUM(A1:A10)\"\r\n * escapeCsvFormula('normal text'); // 'normal text'\r\n * ```\r\n */\r\nexport function escapeCsvFormula(field: string | undefined | null): string {\r\n if (field === undefined || field === null) return '';\r\n const str = String(field);\r\n\r\n // Prefix with single quote if starts with dangerous character\r\n if (str.length > 0 && CSV_FORMULA_CHARS.has(str[0])) {\r\n return \"'\" + str;\r\n }\r\n return str;\r\n}\r\n\r\n// ==================== Path Utilities ====================\r\n\r\n/**\r\n * Validate and normalize a file path to prevent path traversal attacks.\r\n *\r\n * This function:\r\n * - Normalizes the path to canonical form\r\n * - Converts relative paths to absolute paths\r\n * - Detects and prevents path traversal attempts (..)\r\n *\r\n * @param filePath - The file path to validate\r\n * @param baseDir - Optional base directory for relative paths (defaults to process.cwd())\r\n * @returns Validated absolute file path\r\n * @throws {FileOperationError} If path traversal is detected or path is invalid\r\n *\r\n * @example\r\n * ```typescript\r\n * // Valid paths\r\n * validateFilePath('/var/data/memory.jsonl'); // Returns absolute path\r\n * validateFilePath('data/memory.jsonl'); // Returns absolute path from cwd\r\n *\r\n * // Invalid paths (throws FileOperationError)\r\n * validateFilePath('../../../etc/passwd'); // Path traversal detected\r\n * validateFilePath('/var/data/../../../etc/passwd'); // Path traversal detected\r\n * ```\r\n */\r\nexport function validateFilePath(filePath: string, baseDir: string = process.cwd()): string {\r\n // Normalize path to remove redundant separators and resolve . and ..\r\n const normalized = path.normalize(filePath);\r\n\r\n // Convert to absolute path\r\n const absolute = path.isAbsolute(normalized)\r\n ? normalized\r\n : path.join(baseDir, normalized);\r\n\r\n // After normalization, check if path still contains .. which would indicate\r\n // traversal beyond the base directory\r\n const finalNormalized = path.normalize(absolute);\r\n\r\n // Split path into segments and check for suspicious patterns\r\n const segments = finalNormalized.split(path.sep);\r\n if (segments.includes('..')) {\r\n throw new FileOperationError(\r\n `Path traversal detected in file path: ${filePath}`,\r\n filePath\r\n );\r\n }\r\n\r\n return finalNormalized;\r\n}\r\n\r\n/**\r\n * Default memory file path (in current working directory).\r\n * Uses process.cwd() to ensure the path is relative to the consuming project,\r\n * not the library's installed location.\r\n */\r\nexport const defaultMemoryPath = path.join(process.cwd(), 'memory.jsonl');\r\n\r\n/**\r\n * Ensure memory file path with backward compatibility migration.\r\n *\r\n * Handles:\r\n * 1. Custom MEMORY_FILE_PATH environment variable (with path traversal protection)\r\n * 2. Backward compatibility: migrates memory.json to memory.jsonl\r\n * 3. Absolute vs relative path resolution\r\n *\r\n * @returns Resolved and validated memory file path\r\n * @throws {FileOperationError} If path traversal is detected in MEMORY_FILE_PATH\r\n *\r\n * @example\r\n * ```typescript\r\n * // Use environment variable\r\n * process.env.MEMORY_FILE_PATH = '/data/memory.jsonl';\r\n * const path = await ensureMemoryFilePath(); // '/data/memory.jsonl'\r\n *\r\n * // Use default path\r\n * delete process.env.MEMORY_FILE_PATH;\r\n * const path = await ensureMemoryFilePath(); // './memory.jsonl'\r\n *\r\n * // Invalid path (throws error)\r\n * process.env.MEMORY_FILE_PATH = '../../../etc/passwd';\r\n * await ensureMemoryFilePath(); // Throws FileOperationError\r\n * ```\r\n */\r\nexport async function ensureMemoryFilePath(): Promise<string> {\r\n if (process.env.MEMORY_FILE_PATH) {\r\n // Custom path provided, validate and resolve to absolute\r\n // Use process.cwd() as baseDir so paths are relative to consuming project\r\n const validatedPath = validateFilePath(process.env.MEMORY_FILE_PATH, process.cwd());\r\n return validatedPath;\r\n }\r\n\r\n // No custom path set, check for backward compatibility migration\r\n // Use process.cwd() so paths are relative to consuming project, not library location\r\n const oldMemoryPath = path.join(process.cwd(), 'memory.json');\r\n const newMemoryPath = defaultMemoryPath;\r\n\r\n try {\r\n // Check if old file exists\r\n await fs.access(oldMemoryPath);\r\n\r\n try {\r\n // Check if new file exists\r\n await fs.access(newMemoryPath);\r\n // Both files exist, use new one (no migration needed)\r\n return newMemoryPath;\r\n } catch {\r\n // Old file exists, new file doesn't - migrate\r\n console.log('[INFO] Found legacy memory.json file, migrating to memory.jsonl for JSONL format compatibility');\r\n await fs.rename(oldMemoryPath, newMemoryPath);\r\n console.log('[INFO] Successfully migrated memory.json to memory.jsonl');\r\n return newMemoryPath;\r\n }\r\n } catch {\r\n // Old file doesn't exist, use new path\r\n return newMemoryPath;\r\n }\r\n}\r\n","/**\r\n * Parallel Utilities\r\n *\r\n * Utilities for parallel array operations using workerpool.\r\n * Phase 8 Sprint 3: Parallel array operations for improved performance.\r\n *\r\n * **SECURITY WARNING:** These functions use `new Function()` internally for worker serialization.\r\n * The `fn` parameter MUST be a real function object, never a user-provided string.\r\n * Runtime validation ensures only function objects are accepted.\r\n *\r\n * @module utils/parallelUtils\r\n */\r\n\r\nimport workerpool from '@danielsimonjr/workerpool';\r\n\r\n/**\r\n * Validates that the input is a real function object.\r\n * Prevents code injection through string masquerading as functions.\r\n *\r\n * @param fn - Function to validate\r\n * @param paramName - Parameter name for error message\r\n * @throws {TypeError} If fn is not a function\r\n * @internal\r\n */\r\nfunction validateFunction(fn: unknown, paramName: string): void {\r\n if (typeof fn !== 'function') {\r\n throw new TypeError(`${paramName} must be a function, got ${typeof fn}`);\r\n }\r\n}\r\n\r\n/**\r\n * Default chunk size for parallel operations.\r\n * Can be overridden per operation.\r\n */\r\nconst DEFAULT_CHUNK_SIZE = 100;\r\n\r\n/**\r\n * Minimum array size to activate parallel processing.\r\n * For smaller arrays, single-threaded is more efficient due to worker overhead.\r\n */\r\nconst MIN_PARALLEL_SIZE = 200;\r\n\r\n/**\r\n * Shared worker pool instance for all parallel utilities.\r\n * Initialized lazily on first use.\r\n */\r\nlet sharedPool: workerpool.Pool | null = null;\r\n\r\n/**\r\n * Get or create the shared worker pool.\r\n * Uses inline worker execution (no separate worker file needed).\r\n *\r\n * @returns Worker pool instance\r\n */\r\nfunction getPool(): workerpool.Pool {\r\n if (!sharedPool) {\r\n sharedPool = workerpool.pool({\r\n maxWorkers: Math.max(1, workerpool.cpus - 1),\r\n workerType: 'thread',\r\n });\r\n }\r\n return sharedPool;\r\n}\r\n\r\n/**\r\n * Shutdown the shared worker pool and clean up resources.\r\n * Should be called when parallel utilities are no longer needed.\r\n */\r\nexport async function shutdownParallelUtils(): Promise<void> {\r\n if (sharedPool) {\r\n await sharedPool.terminate();\r\n sharedPool = null;\r\n }\r\n}\r\n\r\n/**\r\n * Map items in parallel using workerpool.\r\n *\r\n * Splits the array into chunks and processes each chunk in a worker thread.\r\n * Falls back to single-threaded for small arrays (< MIN_PARALLEL_SIZE).\r\n *\r\n * **Note:** The mapping function must be serializable (no closures, external variables).\r\n * Due to ESM/worker thread compatibility issues, this may fall back to single-threaded\r\n * execution in some environments (e.g., vitest test runner).\r\n *\r\n * @template T - Input item type\r\n * @template R - Output item type\r\n * @param items - Array of items to map\r\n * @param fn - Mapping function (must be serializable)\r\n * @param chunkSize - Optional chunk size (default: DEFAULT_CHUNK_SIZE)\r\n * @returns Promise resolving to array of mapped results\r\n *\r\n * @example\r\n * ```typescript\r\n * // Map numbers to their squares\r\n * const numbers = [1, 2, 3, 4, 5];\r\n * const squared = await parallelMap(numbers, (n: number) => n * n);\r\n * // Result: [1, 4, 9, 16, 25]\r\n * ```\r\n */\r\nexport async function parallelMap<T, R>(\r\n items: T[],\r\n fn: (item: T) => R,\r\n chunkSize: number = DEFAULT_CHUNK_SIZE\r\n): Promise<R[]> {\r\n // Security: Validate that fn is a real function, not a user-provided string\r\n validateFunction(fn, 'fn');\r\n\r\n // Fall back to single-threaded for small arrays\r\n if (items.length < MIN_PARALLEL_SIZE) {\r\n return items.map(fn);\r\n }\r\n\r\n try {\r\n const pool = getPool();\r\n\r\n // Split items into chunks\r\n const chunks: T[][] = [];\r\n for (let i = 0; i < items.length; i += chunkSize) {\r\n chunks.push(items.slice(i, i + chunkSize));\r\n }\r\n\r\n // Convert function to string for serialization\r\n const fnString = fn.toString();\r\n\r\n // Process chunks in parallel using inline function execution\r\n const results = await Promise.all(\r\n chunks.map(chunk =>\r\n pool.exec(\r\n (chunkData: T[], fnStr: string) => {\r\n // Reconstruct function from string\r\n // eslint-disable-next-line no-new-func\r\n const mapFn = new Function('return ' + fnStr)() as (item: T) => R;\r\n return chunkData.map(mapFn);\r\n },\r\n [chunk, fnString]\r\n ) as Promise<R[]>\r\n )\r\n );\r\n\r\n // Flatten results\r\n return results.flat();\r\n } catch (error) {\r\n // Fall back to single-threaded if worker execution fails\r\n // (e.g., in test environments with ESM/worker compatibility issues)\r\n return items.map(fn);\r\n }\r\n}\r\n\r\n/**\r\n * Filter items in parallel using workerpool.\r\n *\r\n * Splits the array into chunks and processes each chunk in a worker thread.\r\n * Falls back to single-threaded for small arrays (< MIN_PARALLEL_SIZE).\r\n *\r\n * **Note:** The predicate function must be serializable (no closures, external variables).\r\n * Due to ESM/worker thread compatibility issues, this may fall back to single-threaded\r\n * execution in some environments (e.g., vitest test runner).\r\n *\r\n * @template T - Item type\r\n * @param items - Array of items to filter\r\n * @param predicate - Filter predicate (must be serializable)\r\n * @param chunkSize - Optional chunk size (default: DEFAULT_CHUNK_SIZE)\r\n * @returns Promise resolving to filtered array\r\n *\r\n * @example\r\n * ```typescript\r\n * // Filter even numbers\r\n * const numbers = [1, 2, 3, 4, 5, 6];\r\n * const evens = await parallelFilter(numbers, (n: number) => n % 2 === 0);\r\n * // Result: [2, 4, 6]\r\n * ```\r\n */\r\nexport async function parallelFilter<T>(\r\n items: T[],\r\n predicate: (item: T) => boolean,\r\n chunkSize: number = DEFAULT_CHUNK_SIZE\r\n): Promise<T[]> {\r\n // Security: Validate that predicate is a real function, not a user-provided string\r\n validateFunction(predicate, 'predicate');\r\n\r\n // Fall back to single-threaded for small arrays\r\n if (items.length < MIN_PARALLEL_SIZE) {\r\n return items.filter(predicate);\r\n }\r\n\r\n try {\r\n const pool = getPool();\r\n\r\n // Split items into chunks\r\n const chunks: T[][] = [];\r\n for (let i = 0; i < items.length; i += chunkSize) {\r\n chunks.push(items.slice(i, i + chunkSize));\r\n }\r\n\r\n // Convert function to string for serialization\r\n const predicateString = predicate.toString();\r\n\r\n // Process chunks in parallel using inline function execution\r\n const results = await Promise.all(\r\n chunks.map(chunk =>\r\n pool.exec(\r\n (chunkData: T[], predicateStr: string) => {\r\n // Reconstruct function from string\r\n // eslint-disable-next-line no-new-func\r\n const filterFn = new Function('return ' + predicateStr)() as (item: T) => boolean;\r\n return chunkData.filter(filterFn);\r\n },\r\n [chunk, predicateString]\r\n ) as Promise<T[]>\r\n )\r\n );\r\n\r\n // Flatten results\r\n return results.flat();\r\n } catch (error) {\r\n // Fall back to single-threaded if worker execution fails\r\n // (e.g., in test environments with ESM/worker compatibility issues)\r\n return items.filter(predicate);\r\n }\r\n}\r\n\r\n/**\r\n * Get statistics about the worker pool.\r\n *\r\n * @returns Pool statistics or null if pool is not initialized\r\n */\r\nexport function getPoolStats(): workerpool.PoolStats | null {\r\n if (!sharedPool) {\r\n return null;\r\n }\r\n return sharedPool.stats();\r\n}\r\n","/**\r\n * Task Scheduler\r\n *\r\n * Advanced task scheduling utilities using workerpool.\r\n * Phase 8 Sprint 4: Priority queues, concurrency control, progress tracking.\r\n *\r\n * **SECURITY WARNING:** TaskQueue uses `new Function()` internally for worker serialization.\r\n * Task functions MUST be real function objects, never user-provided strings.\r\n * Runtime validation ensures only function objects are accepted.\r\n *\r\n * @module utils/taskScheduler\r\n */\r\n\r\nimport workerpool from '@danielsimonjr/workerpool';\r\n\r\n/**\r\n * Validates that the input is a real function object.\r\n * Prevents code injection through string masquerading as functions.\r\n *\r\n * @param fn - Function to validate\r\n * @param paramName - Parameter name for error message\r\n * @throws {TypeError} If fn is not a function\r\n * @internal\r\n */\r\nfunction validateFunction(fn: unknown, paramName: string): void {\r\n if (typeof fn !== 'function') {\r\n throw new TypeError(`${paramName} must be a function, got ${typeof fn}`);\r\n }\r\n}\r\n\r\n// ==================== Types ====================\r\n\r\n/**\r\n * Task priority levels.\r\n * Higher priority tasks are executed first.\r\n */\r\nexport enum TaskPriority {\r\n LOW = 0,\r\n NORMAL = 1,\r\n HIGH = 2,\r\n CRITICAL = 3,\r\n}\r\n\r\n/**\r\n * Task status in the queue.\r\n */\r\nexport enum TaskStatus {\r\n PENDING = 'pending',\r\n RUNNING = 'running',\r\n COMPLETED = 'completed',\r\n FAILED = 'failed',\r\n CANCELLED = 'cancelled',\r\n}\r\n\r\n/**\r\n * Task definition for the queue.\r\n */\r\nexport interface Task<T = unknown, R = unknown> {\r\n /** Unique task identifier */\r\n id: string;\r\n /** Task priority */\r\n priority: TaskPriority;\r\n /** Function to execute (must be serializable) */\r\n fn: (input: T) => R;\r\n /** Input data for the function */\r\n input: T;\r\n /** Optional timeout in milliseconds */\r\n timeout?: number;\r\n /** Optional task metadata */\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Task result after execution.\r\n */\r\nexport interface TaskResult<R = unknown> {\r\n /** Task identifier */\r\n id: string;\r\n /** Task status */\r\n status: TaskStatus;\r\n /** Result if successful */\r\n result?: R;\r\n /** Error if failed */\r\n error?: Error;\r\n /** Execution duration in milliseconds */\r\n duration: number;\r\n /** Start timestamp */\r\n startedAt: number;\r\n /** End timestamp */\r\n completedAt: number;\r\n}\r\n\r\n/**\r\n * Progress callback for batch operations.\r\n */\r\nexport type ProgressCallback = (progress: {\r\n completed: number;\r\n total: number;\r\n percentage: number;\r\n currentTaskId?: string;\r\n}) => void;\r\n\r\n/**\r\n * Batch processing options.\r\n */\r\nexport interface TaskBatchOptions {\r\n /** Maximum concurrent tasks (default: CPU count - 1) */\r\n concurrency?: number;\r\n /** Task timeout in milliseconds (default: 30000) */\r\n timeout?: number;\r\n /** Progress callback */\r\n onProgress?: ProgressCallback;\r\n /** Whether to stop on first error (default: false) */\r\n stopOnError?: boolean;\r\n}\r\n\r\n/**\r\n * Task queue statistics.\r\n */\r\nexport interface QueueStats {\r\n /** Number of pending tasks */\r\n pending: number;\r\n /** Number of running tasks */\r\n running: number;\r\n /** Number of completed tasks */\r\n completed: number;\r\n /** Number of failed tasks */\r\n failed: number;\r\n /** Average execution time in milliseconds */\r\n averageExecutionTime: number;\r\n /** Total tasks processed */\r\n totalProcessed: number;\r\n}\r\n\r\n// ==================== Task Queue Implementation ====================\r\n\r\n/**\r\n * Internal task wrapper with tracking information.\r\n */\r\ninterface QueuedTask<T = unknown, R = unknown> extends Task<T, R> {\r\n status: TaskStatus;\r\n addedAt: number;\r\n resolve: (result: TaskResult<R>) => void;\r\n reject: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * Priority Task Queue with advanced scheduling.\r\n *\r\n * Features:\r\n * - Priority-based execution (CRITICAL > HIGH > NORMAL > LOW)\r\n * - Configurable concurrency limits\r\n * - Progress tracking\r\n * - Graceful error handling\r\n * - Task cancellation\r\n *\r\n * @example\r\n * ```typescript\r\n * const queue = new TaskQueue({ concurrency: 4 });\r\n *\r\n * // Add tasks with different priorities\r\n * queue.enqueue({\r\n * id: 'task1',\r\n * priority: TaskPriority.HIGH,\r\n * fn: (x: number) => x * 2,\r\n * input: 5,\r\n * });\r\n *\r\n * // Process all tasks\r\n * const results = await queue.processAll();\r\n * ```\r\n */\r\nexport class TaskQueue {\r\n private queue: QueuedTask[] = [];\r\n private running: Map<string, QueuedTask> = new Map();\r\n private completed: TaskResult[] = [];\r\n private pool: workerpool.Pool | null = null;\r\n private concurrency: number;\r\n private defaultTimeout: number;\r\n private isProcessing = false;\r\n private totalExecutionTime = 0;\r\n private totalProcessed = 0;\r\n private useWorkerPool: boolean;\r\n\r\n constructor(options: { concurrency?: number; timeout?: number; useWorkerPool?: boolean } = {}) {\r\n this.concurrency = options.concurrency ?? Math.max(1, workerpool.cpus - 1);\r\n this.defaultTimeout = options.timeout ?? 30000;\r\n this.useWorkerPool = options.useWorkerPool ?? true;\r\n }\r\n\r\n /**\r\n * Get or create the worker pool.\r\n */\r\n private getPool(): workerpool.Pool {\r\n if (!this.pool) {\r\n this.pool = workerpool.pool({\r\n maxWorkers: this.concurrency,\r\n workerType: 'thread',\r\n });\r\n }\r\n return this.pool;\r\n }\r\n\r\n /**\r\n * Add a task to the queue.\r\n *\r\n * @param task - Task to add\r\n * @returns Promise that resolves when the task completes\r\n */\r\n enqueue<T, R>(task: Task<T, R>): Promise<TaskResult<R>> {\r\n // Security: Validate that task.fn is a real function, not a user-provided string\r\n validateFunction(task.fn, 'task.fn');\r\n\r\n return new Promise((resolve, reject) => {\r\n const queuedTask: QueuedTask<T, R> = {\r\n ...task,\r\n status: TaskStatus.PENDING,\r\n addedAt: Date.now(),\r\n resolve: resolve as (result: TaskResult<unknown>) => void,\r\n reject,\r\n };\r\n\r\n // Insert based on priority (higher priority first)\r\n const insertIndex = this.queue.findIndex(t => t.priority < task.priority);\r\n if (insertIndex === -1) {\r\n this.queue.push(queuedTask as QueuedTask);\r\n } else {\r\n this.queue.splice(insertIndex, 0, queuedTask as QueuedTask);\r\n }\r\n\r\n // Start processing if not already running\r\n if (!this.isProcessing) {\r\n this.processNext();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Process the next task in the queue.\r\n */\r\n private async processNext(): Promise<void> {\r\n if (this.running.size >= this.concurrency || this.queue.length === 0) {\r\n if (this.running.size === 0 && this.queue.length === 0) {\r\n this.isProcessing = false;\r\n }\r\n return;\r\n }\r\n\r\n this.isProcessing = true;\r\n const task = this.queue.shift();\r\n if (!task) return;\r\n\r\n task.status = TaskStatus.RUNNING;\r\n this.running.set(task.id, task);\r\n\r\n const startTime = Date.now();\r\n\r\n try {\r\n // Execute task - try worker pool first, fall back to direct execution\r\n let result: unknown;\r\n\r\n if (this.useWorkerPool) {\r\n try {\r\n const pool = this.getPool();\r\n const fnString = task.fn.toString();\r\n const timeout = task.timeout ?? this.defaultTimeout;\r\n\r\n result = await pool\r\n .exec(\r\n (input: unknown, fnStr: string) => {\r\n // eslint-disable-next-line no-new-func\r\n const fn = new Function('return ' + fnStr)();\r\n return fn(input);\r\n },\r\n [task.input, fnString]\r\n )\r\n .timeout(timeout);\r\n } catch {\r\n // Fall back to direct execution\r\n result = await Promise.resolve(task.fn(task.input));\r\n }\r\n } else {\r\n // Direct execution without worker pool\r\n result = await Promise.resolve(task.fn(task.input));\r\n }\r\n\r\n const endTime = Date.now();\r\n const duration = endTime - startTime;\r\n\r\n const taskResult: TaskResult = {\r\n id: task.id,\r\n status: TaskStatus.COMPLETED,\r\n result,\r\n duration,\r\n startedAt: startTime,\r\n completedAt: endTime,\r\n };\r\n\r\n this.totalExecutionTime += duration;\r\n this.totalProcessed++;\r\n this.completed.push(taskResult);\r\n this.running.delete(task.id);\r\n task.resolve(taskResult);\r\n } catch (error) {\r\n const endTime = Date.now();\r\n const duration = endTime - startTime;\r\n\r\n const taskResult: TaskResult = {\r\n id: task.id,\r\n status: TaskStatus.FAILED,\r\n error: error instanceof Error ? error : new Error(String(error)),\r\n duration,\r\n startedAt: startTime,\r\n completedAt: endTime,\r\n };\r\n\r\n this.totalProcessed++;\r\n this.completed.push(taskResult);\r\n this.running.delete(task.id);\r\n task.resolve(taskResult);\r\n }\r\n\r\n // Process next task\r\n this.processNext();\r\n }\r\n\r\n /**\r\n * Cancel a pending task.\r\n *\r\n * @param taskId - ID of the task to cancel\r\n * @returns True if task was cancelled, false if not found or already running\r\n */\r\n cancel(taskId: string): boolean {\r\n const index = this.queue.findIndex(t => t.id === taskId);\r\n if (index === -1) return false;\r\n\r\n const task = this.queue.splice(index, 1)[0];\r\n task.status = TaskStatus.CANCELLED;\r\n\r\n const result: TaskResult = {\r\n id: task.id,\r\n status: TaskStatus.CANCELLED,\r\n duration: 0,\r\n startedAt: Date.now(),\r\n completedAt: Date.now(),\r\n };\r\n\r\n task.resolve(result);\r\n return true;\r\n }\r\n\r\n /**\r\n * Wait for all tasks to complete.\r\n *\r\n * @returns Array of all task results\r\n */\r\n async drain(): Promise<TaskResult[]> {\r\n // Wait for queue to empty and all running tasks to complete\r\n while (this.queue.length > 0 || this.running.size > 0) {\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n }\r\n return [...this.completed];\r\n }\r\n\r\n /**\r\n * Get queue statistics.\r\n */\r\n getStats(): QueueStats {\r\n return {\r\n pending: this.queue.length,\r\n running: this.running.size,\r\n completed: this.completed.filter(r => r.status === TaskStatus.COMPLETED).length,\r\n failed: this.completed.filter(r => r.status === TaskStatus.FAILED).length,\r\n averageExecutionTime:\r\n this.totalProcessed > 0 ? this.totalExecutionTime / this.totalProcessed : 0,\r\n totalProcessed: this.totalProcessed,\r\n };\r\n }\r\n\r\n /**\r\n * Clear all completed results.\r\n */\r\n clearCompleted(): void {\r\n this.completed = [];\r\n }\r\n\r\n /**\r\n * Shutdown the task queue and release resources.\r\n */\r\n async shutdown(): Promise<void> {\r\n // Cancel all pending tasks\r\n for (const task of this.queue) {\r\n task.status = TaskStatus.CANCELLED;\r\n task.resolve({\r\n id: task.id,\r\n status: TaskStatus.CANCELLED,\r\n duration: 0,\r\n startedAt: Date.now(),\r\n completedAt: Date.now(),\r\n });\r\n }\r\n this.queue = [];\r\n\r\n // Terminate worker pool\r\n if (this.pool) {\r\n await this.pool.terminate();\r\n this.pool = null;\r\n }\r\n\r\n this.isProcessing = false;\r\n }\r\n}\r\n\r\n// ==================== Batch Processing Utilities ====================\r\n\r\n/**\r\n * Process items in parallel batches with progress tracking.\r\n *\r\n * Unlike parallelMap, this provides:\r\n * - Progress callbacks\r\n * - Configurable concurrency\r\n * - Error handling options\r\n * - Task-level timeouts\r\n *\r\n * @template T - Input item type\r\n * @template R - Output item type\r\n * @param items - Array of items to process\r\n * @param fn - Processing function (must be serializable)\r\n * @param options - Batch processing options\r\n * @returns Array of results (or errors if stopOnError is false)\r\n *\r\n * @example\r\n * ```typescript\r\n * const results = await batchProcess(\r\n * urls,\r\n * (url: string) => fetch(url).then(r => r.json()),\r\n * {\r\n * concurrency: 5,\r\n * timeout: 10000,\r\n * onProgress: ({ completed, total, percentage }) => {\r\n * console.log(`Progress: ${percentage.toFixed(1)}%`);\r\n * },\r\n * }\r\n * );\r\n * ```\r\n */\r\nexport async function batchProcess<T, R>(\r\n items: T[],\r\n fn: (item: T) => R | Promise<R>,\r\n options: TaskBatchOptions = {}\r\n): Promise<Array<{ success: true; result: R } | { success: false; error: Error }>> {\r\n const {\r\n concurrency = Math.max(1, workerpool.cpus - 1),\r\n timeout = 30000,\r\n onProgress,\r\n stopOnError = false,\r\n } = options;\r\n\r\n const results: Array<{ success: true; result: R } | { success: false; error: Error }> = [];\r\n let completed = 0;\r\n const total = items.length;\r\n\r\n // Process in batches respecting concurrency\r\n for (let i = 0; i < items.length; i += concurrency) {\r\n const batch = items.slice(i, i + concurrency);\r\n\r\n const batchPromises = batch.map(async (item, batchIndex) => {\r\n const itemIndex = i + batchIndex;\r\n\r\n try {\r\n // Execute with timeout\r\n const result = await Promise.race([\r\n Promise.resolve(fn(item)),\r\n new Promise<never>((_, reject) =>\r\n setTimeout(() => reject(new Error('Task timeout')), timeout)\r\n ),\r\n ]);\r\n\r\n results[itemIndex] = { success: true, result };\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n results[itemIndex] = { success: false, error: err };\r\n\r\n if (stopOnError) {\r\n throw err;\r\n }\r\n } finally {\r\n completed++;\r\n if (onProgress) {\r\n onProgress({\r\n completed,\r\n total,\r\n percentage: (completed / total) * 100,\r\n currentTaskId: `item-${itemIndex}`,\r\n });\r\n }\r\n }\r\n });\r\n\r\n await Promise.all(batchPromises);\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Execute tasks with rate limiting.\r\n *\r\n * Ensures tasks don't exceed a specified rate (tasks per second).\r\n *\r\n * @template T - Input item type\r\n * @template R - Output item type\r\n * @param items - Items to process\r\n * @param fn - Processing function\r\n * @param rateLimit - Maximum tasks per second\r\n * @returns Array of results\r\n *\r\n * @example\r\n * ```typescript\r\n * // Process max 10 items per second\r\n * const results = await rateLimitedProcess(\r\n * items,\r\n * (item) => processItem(item),\r\n * 10\r\n * );\r\n * ```\r\n */\r\nexport async function rateLimitedProcess<T, R>(\r\n items: T[],\r\n fn: (item: T) => R | Promise<R>,\r\n rateLimit: number\r\n): Promise<R[]> {\r\n const results: R[] = [];\r\n const minInterval = 1000 / rateLimit;\r\n let lastExecutionTime = 0;\r\n\r\n for (const item of items) {\r\n // Calculate wait time\r\n const now = Date.now();\r\n const timeSinceLast = now - lastExecutionTime;\r\n if (timeSinceLast < minInterval) {\r\n await new Promise(resolve => setTimeout(resolve, minInterval - timeSinceLast));\r\n }\r\n\r\n lastExecutionTime = Date.now();\r\n const result = await fn(item);\r\n results.push(result);\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Retry a function with exponential backoff.\r\n *\r\n * @template T - Return type\r\n * @param fn - Function to retry\r\n * @param options - Retry options\r\n * @returns Result of the function\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = await withRetry(\r\n * () => fetchData(),\r\n * { maxRetries: 3, baseDelay: 1000 }\r\n * );\r\n * ```\r\n */\r\nexport async function withRetry<T>(\r\n fn: () => T | Promise<T>,\r\n options: {\r\n maxRetries?: number;\r\n baseDelay?: number;\r\n maxDelay?: number;\r\n onRetry?: (error: Error, attempt: number) => void;\r\n } = {}\r\n): Promise<T> {\r\n const { maxRetries = 3, baseDelay = 1000, maxDelay = 30000, onRetry } = options;\r\n\r\n let lastError: Error | undefined;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n return await fn();\r\n } catch (error) {\r\n lastError = error instanceof Error ? error : new Error(String(error));\r\n\r\n if (attempt < maxRetries) {\r\n const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);\r\n if (onRetry) {\r\n onRetry(lastError, attempt + 1);\r\n }\r\n await new Promise(resolve => setTimeout(resolve, delay));\r\n }\r\n }\r\n }\r\n\r\n throw lastError;\r\n}\r\n\r\n/**\r\n * Create a debounced version of a function.\r\n *\r\n * @template T - Function arguments type\r\n * @template R - Return type\r\n * @param fn - Function to debounce\r\n * @param delay - Delay in milliseconds\r\n * @returns Debounced function\r\n */\r\nexport function debounce<T extends unknown[], R>(\r\n fn: (...args: T) => R,\r\n delay: number\r\n): (...args: T) => Promise<R> {\r\n let timeoutId: NodeJS.Timeout | null = null;\r\n let pendingResolve: ((value: R) => void) | null = null;\r\n\r\n return (...args: T): Promise<R> => {\r\n return new Promise(resolve => {\r\n if (timeoutId) {\r\n clearTimeout(timeoutId);\r\n }\r\n pendingResolve = resolve;\r\n\r\n timeoutId = setTimeout(() => {\r\n const result = fn(...args);\r\n if (pendingResolve) {\r\n pendingResolve(result);\r\n }\r\n timeoutId = null;\r\n pendingResolve = null;\r\n }, delay);\r\n });\r\n };\r\n}\r\n\r\n/**\r\n * Create a throttled version of a function.\r\n *\r\n * @template T - Function arguments type\r\n * @template R - Return type\r\n * @param fn - Function to throttle\r\n * @param limit - Minimum time between calls in milliseconds\r\n * @returns Throttled function\r\n */\r\nexport function throttle<T extends unknown[], R>(\r\n fn: (...args: T) => R,\r\n limit: number\r\n): (...args: T) => R | undefined {\r\n let lastCall = 0;\r\n\r\n return (...args: T): R | undefined => {\r\n const now = Date.now();\r\n if (now - lastCall >= limit) {\r\n lastCall = now;\r\n return fn(...args);\r\n }\r\n return undefined;\r\n };\r\n}\r\n","/**\r\n * Operation Utilities\r\n *\r\n * Phase 9B: Utilities for long-running operations with progress tracking\r\n * and cancellation support.\r\n *\r\n * @module utils/operationUtils\r\n */\r\n\r\nimport { OperationCancelledError } from './errors.js';\r\nimport type { ProgressCallback } from './taskScheduler.js';\r\n\r\n/**\r\n * Check if an operation has been cancelled via AbortSignal.\r\n * Throws OperationCancelledError if the signal is aborted.\r\n *\r\n * @param signal - Optional AbortSignal to check\r\n * @param operation - Optional operation name for error message\r\n * @throws OperationCancelledError if signal is aborted\r\n *\r\n * @example\r\n * ```typescript\r\n * for (const item of items) {\r\n * checkCancellation(options?.signal, 'batch processing');\r\n * await processItem(item);\r\n * }\r\n * ```\r\n */\r\nexport function checkCancellation(signal?: AbortSignal, operation?: string): void {\r\n if (signal?.aborted) {\r\n throw new OperationCancelledError(operation);\r\n }\r\n}\r\n\r\n/**\r\n * Create a throttled progress reporter to avoid excessive callback invocations.\r\n * Returns undefined if no callback is provided.\r\n *\r\n * @param callback - Optional progress callback to throttle\r\n * @param throttleMs - Minimum time between callbacks (default: 100ms)\r\n * @returns Throttled callback or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const reportProgress = createProgressReporter(options?.onProgress, 50);\r\n * for (let i = 0; i < total; i++) {\r\n * reportProgress?.({ completed: i, total, percentage: (i / total) * 100 });\r\n * }\r\n * ```\r\n */\r\nexport function createProgressReporter(\r\n callback?: ProgressCallback,\r\n throttleMs: number = 100\r\n): ProgressCallback | undefined {\r\n if (!callback) return undefined;\r\n\r\n let lastCallTime = 0;\r\n\r\n return (progress) => {\r\n const now = Date.now();\r\n // Always report 0% and 100%\r\n if (progress.percentage === 0 || progress.percentage >= 100 || now - lastCallTime >= throttleMs) {\r\n lastCallTime = now;\r\n callback(progress);\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Create a progress object for reporting.\r\n *\r\n * @param completed - Number of completed items\r\n * @param total - Total number of items\r\n * @param currentTaskId - Optional current task identifier\r\n * @returns Progress object suitable for ProgressCallback\r\n *\r\n * @example\r\n * ```typescript\r\n * reportProgress?.(createProgress(50, 100, 'processing entities'));\r\n * // { completed: 50, total: 100, percentage: 50, currentTaskId: 'processing entities' }\r\n * ```\r\n */\r\nexport function createProgress(\r\n completed: number,\r\n total: number,\r\n currentTaskId?: string\r\n): { completed: number; total: number; percentage: number; currentTaskId?: string } {\r\n return {\r\n completed,\r\n total,\r\n percentage: total > 0 ? Math.round((completed / total) * 100) : 0,\r\n currentTaskId,\r\n };\r\n}\r\n\r\n/**\r\n * Phase definition for executeWithPhases.\r\n */\r\nexport interface PhaseDefinition<T> {\r\n /** Phase name (used for progress reporting and cancellation error messages) */\r\n name: string;\r\n /** Weight of this phase relative to others (higher = more of total progress) */\r\n weight: number;\r\n /** Executor function that performs the phase work */\r\n execute: (phaseProgress: (pct: number) => void) => Promise<T>;\r\n}\r\n\r\n/**\r\n * Execute an operation with multiple distinct phases.\r\n * Useful when an operation has multiple distinct phases with different weights.\r\n *\r\n * @param phases - Array of phase definitions with weight and executor\r\n * @param onProgress - Optional progress callback\r\n * @param signal - Optional abort signal\r\n * @returns Array of results from each phase\r\n * @throws OperationCancelledError if cancelled during any phase\r\n *\r\n * @example\r\n * ```typescript\r\n * const [parseResult, processResult, saveResult] = await executeWithPhases([\r\n * { name: 'parsing', weight: 20, execute: () => parseData() },\r\n * { name: 'processing', weight: 60, execute: () => processEntities() },\r\n * { name: 'saving', weight: 20, execute: () => saveResults() },\r\n * ], options?.onProgress, options?.signal);\r\n * ```\r\n */\r\nexport async function executeWithPhases<T>(\r\n phases: PhaseDefinition<T>[],\r\n onProgress?: ProgressCallback,\r\n signal?: AbortSignal\r\n): Promise<T[]> {\r\n const totalWeight = phases.reduce((sum, p) => sum + p.weight, 0);\r\n let completedWeight = 0;\r\n const results: T[] = [];\r\n\r\n for (const phase of phases) {\r\n checkCancellation(signal, phase.name);\r\n\r\n const phaseStartWeight = completedWeight;\r\n const phaseProgress = (phasePct: number) => {\r\n if (onProgress) {\r\n const overallPct = ((phaseStartWeight + (phase.weight * phasePct / 100)) / totalWeight) * 100;\r\n onProgress({\r\n completed: Math.round(overallPct),\r\n total: 100,\r\n percentage: Math.round(overallPct),\r\n currentTaskId: phase.name,\r\n });\r\n }\r\n };\r\n\r\n const result = await phase.execute(phaseProgress);\r\n results.push(result);\r\n completedWeight += phase.weight;\r\n }\r\n\r\n // Report 100% completion\r\n onProgress?.({\r\n completed: 100,\r\n total: 100,\r\n percentage: 100,\r\n });\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Execute an operation in batches with progress tracking and cancellation support.\r\n *\r\n * @param items - Array of items to process\r\n * @param batchSize - Size of each batch\r\n * @param processBatch - Function to process each batch\r\n * @param onProgress - Optional progress callback\r\n * @param signal - Optional abort signal\r\n * @param operationName - Optional operation name for cancellation error\r\n * @returns Array of results from all batches\r\n *\r\n * @example\r\n * ```typescript\r\n * const results = await processBatchesWithProgress(\r\n * entities,\r\n * 100,\r\n * async (batch) => {\r\n * for (const entity of batch) {\r\n * await saveEntity(entity);\r\n * }\r\n * return batch.length;\r\n * },\r\n * options?.onProgress,\r\n * options?.signal,\r\n * 'createEntities'\r\n * );\r\n * ```\r\n */\r\nexport async function processBatchesWithProgress<T, R>(\r\n items: T[],\r\n batchSize: number,\r\n processBatch: (batch: T[], batchIndex: number) => Promise<R>,\r\n onProgress?: ProgressCallback,\r\n signal?: AbortSignal,\r\n operationName?: string\r\n): Promise<R[]> {\r\n const results: R[] = [];\r\n const total = items.length;\r\n let processed = 0;\r\n\r\n const reportProgress = createProgressReporter(onProgress);\r\n reportProgress?.(createProgress(0, total, operationName));\r\n\r\n for (let i = 0; i < items.length; i += batchSize) {\r\n checkCancellation(signal, operationName);\r\n\r\n const batch = items.slice(i, i + batchSize);\r\n const result = await processBatch(batch, Math.floor(i / batchSize));\r\n results.push(result);\r\n\r\n processed += batch.length;\r\n reportProgress?.(createProgress(processed, total, operationName));\r\n }\r\n\r\n reportProgress?.(createProgress(total, total, operationName));\r\n return results;\r\n}\r\n","/**\n * Worker Pool Manager\n *\n * Phase 12 Sprint 2: Unified worker pool management for all parallelizable operations.\n * Provides centralized lifecycle management, configuration, and statistics.\n *\n * @module utils/WorkerPoolManager\n */\n\nimport workerpool from '@danielsimonjr/workerpool';\nimport type { Pool, PoolStats } from '@danielsimonjr/workerpool';\n\n/**\n * Configuration options for worker pools.\n */\nexport interface WorkerPoolConfig {\n /** Maximum number of worker threads (default: CPU count - 1) */\n maxWorkers?: number;\n /** Worker type: 'thread' or 'process' (default: 'thread') */\n workerType?: 'thread' | 'process';\n /** Optional path to worker script file */\n workerPath?: string;\n /** Minimum array size to use parallel processing (default: 200) */\n minParallelSize?: number;\n /** Default task timeout in milliseconds (default: 30000) */\n defaultTimeout?: number;\n}\n\n/**\n * Extended pool statistics with additional metadata.\n */\nexport interface ExtendedPoolStats extends PoolStats {\n /** Pool identifier */\n poolId: string;\n /** When the pool was created */\n createdAt: number;\n /** Total tasks executed since creation */\n totalTasksExecuted: number;\n /** Total execution time in milliseconds */\n totalExecutionTime: number;\n /** Average task execution time in milliseconds */\n averageExecutionTime: number;\n}\n\n/**\n * Callback for pool events.\n */\nexport type PoolEventCallback = (poolId: string, event: 'created' | 'shutdown' | 'error', data?: unknown) => void;\n\n/**\n * Internal pool entry with metadata.\n */\ninterface PoolEntry {\n pool: Pool;\n config: WorkerPoolConfig;\n createdAt: number;\n totalTasksExecuted: number;\n totalExecutionTime: number;\n}\n\n/**\n * Default configuration values.\n */\nconst DEFAULT_CONFIG: Required<WorkerPoolConfig> = {\n maxWorkers: Math.max(1, workerpool.cpus - 1),\n workerType: 'thread',\n workerPath: '',\n minParallelSize: 200,\n defaultTimeout: 30000,\n};\n\n/**\n * WorkerPoolManager - Unified worker pool management\n *\n * Provides centralized management of worker pools for parallel processing.\n * Features:\n * - Named pool registration with automatic lifecycle management\n * - Pool cleanup on process exit\n * - Statistics tracking per pool\n * - Event callbacks for monitoring\n *\n * @example\n * ```typescript\n * const manager = WorkerPoolManager.getInstance();\n *\n * // Get or create a pool\n * const pool = manager.getPool('fuzzySearch', {\n * maxWorkers: 4,\n * workerPath: '/path/to/worker.js'\n * });\n *\n * // Execute task\n * const result = await pool.exec('searchEntities', [data]);\n *\n * // Get statistics\n * const stats = manager.getPoolStats('fuzzySearch');\n *\n * // Shutdown all pools on exit\n * await manager.shutdownAll();\n * ```\n */\nexport class WorkerPoolManager {\n private static instance: WorkerPoolManager | null = null;\n\n private pools: Map<string, PoolEntry> = new Map();\n private eventCallbacks: PoolEventCallback[] = [];\n private isShuttingDown = false;\n private shutdownRegistered = false;\n\n /**\n * Private constructor for singleton pattern.\n */\n private constructor() {\n this.registerShutdownHandlers();\n }\n\n /**\n * Get the singleton instance of WorkerPoolManager.\n *\n * @returns The WorkerPoolManager instance\n */\n static getInstance(): WorkerPoolManager {\n if (!WorkerPoolManager.instance) {\n WorkerPoolManager.instance = new WorkerPoolManager();\n }\n return WorkerPoolManager.instance;\n }\n\n /**\n * Reset the singleton instance (primarily for testing).\n */\n static resetInstance(): void {\n if (WorkerPoolManager.instance) {\n WorkerPoolManager.instance.shutdownAll().catch(() => {\n // Ignore errors during reset\n });\n WorkerPoolManager.instance = null;\n }\n }\n\n /**\n * Register process exit handlers for cleanup.\n */\n private registerShutdownHandlers(): void {\n if (this.shutdownRegistered) return;\n this.shutdownRegistered = true;\n\n const shutdownHandler = () => {\n if (!this.isShuttingDown) {\n this.shutdownAllSync();\n }\n };\n\n // Register for various exit signals\n process.on('exit', shutdownHandler);\n process.on('SIGINT', () => {\n this.shutdownAll().then(() => process.exit(0)).catch(() => process.exit(1));\n });\n process.on('SIGTERM', () => {\n this.shutdownAll().then(() => process.exit(0)).catch(() => process.exit(1));\n });\n process.on('uncaughtException', (err) => {\n console.error('Uncaught exception:', err);\n this.shutdownAllSync();\n process.exit(1);\n });\n }\n\n /**\n * Get or create a named worker pool.\n *\n * If a pool with the given ID exists, returns the existing pool.\n * Otherwise, creates a new pool with the provided configuration.\n *\n * @param poolId - Unique identifier for the pool\n * @param config - Pool configuration options\n * @returns The worker pool instance\n */\n getPool(poolId: string, config: WorkerPoolConfig = {}): Pool {\n const existing = this.pools.get(poolId);\n if (existing) {\n return existing.pool;\n }\n\n return this.createPool(poolId, config);\n }\n\n /**\n * Create a new worker pool with the given ID.\n *\n * @param poolId - Unique identifier for the pool\n * @param config - Pool configuration options\n * @returns The newly created worker pool\n * @throws Error if a pool with the same ID already exists\n */\n createPool(poolId: string, config: WorkerPoolConfig = {}): Pool {\n if (this.pools.has(poolId)) {\n throw new Error(`Pool with ID '${poolId}' already exists`);\n }\n\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n\n // Create pool options with inline type definition\n // Using inline type since WorkerPoolOptions is not directly exported\n const poolOptions: {\n maxWorkers?: number;\n workerType?: 'auto' | 'web' | 'process' | 'thread';\n workerThreadOpts?: Record<string, unknown>;\n } = {\n maxWorkers: mergedConfig.maxWorkers,\n workerType: mergedConfig.workerType,\n };\n\n // Add worker thread options for ESM support\n if (mergedConfig.workerType === 'thread') {\n poolOptions.workerThreadOpts = { type: 'module' };\n }\n\n // Create pool with or without worker script\n let pool: Pool;\n if (mergedConfig.workerPath) {\n pool = workerpool.pool(mergedConfig.workerPath, poolOptions);\n } else {\n pool = workerpool.pool(poolOptions);\n }\n\n const entry: PoolEntry = {\n pool,\n config: mergedConfig,\n createdAt: Date.now(),\n totalTasksExecuted: 0,\n totalExecutionTime: 0,\n };\n\n this.pools.set(poolId, entry);\n this.emitEvent(poolId, 'created');\n\n return pool;\n }\n\n /**\n * Check if a pool with the given ID exists.\n *\n * @param poolId - Pool identifier to check\n * @returns True if pool exists\n */\n hasPool(poolId: string): boolean {\n return this.pools.has(poolId);\n }\n\n /**\n * Get the configuration for a pool.\n *\n * @param poolId - Pool identifier\n * @returns Pool configuration or undefined if not found\n */\n getPoolConfig(poolId: string): WorkerPoolConfig | undefined {\n const entry = this.pools.get(poolId);\n return entry ? { ...entry.config } : undefined;\n }\n\n /**\n * Get extended statistics for a pool.\n *\n * @param poolId - Pool identifier\n * @returns Extended pool statistics or undefined if not found\n */\n getPoolStats(poolId: string): ExtendedPoolStats | undefined {\n const entry = this.pools.get(poolId);\n if (!entry) return undefined;\n\n const baseStats = entry.pool.stats();\n return {\n ...baseStats,\n poolId,\n createdAt: entry.createdAt,\n totalTasksExecuted: entry.totalTasksExecuted,\n totalExecutionTime: entry.totalExecutionTime,\n averageExecutionTime:\n entry.totalTasksExecuted > 0\n ? entry.totalExecutionTime / entry.totalTasksExecuted\n : 0,\n };\n }\n\n /**\n * Get statistics for all pools.\n *\n * @returns Map of pool IDs to their statistics\n */\n getAllPoolStats(): Map<string, ExtendedPoolStats> {\n const stats = new Map<string, ExtendedPoolStats>();\n for (const poolId of this.pools.keys()) {\n const poolStats = this.getPoolStats(poolId);\n if (poolStats) {\n stats.set(poolId, poolStats);\n }\n }\n return stats;\n }\n\n /**\n * Record task execution for statistics tracking.\n *\n * @param poolId - Pool identifier\n * @param executionTimeMs - Task execution time in milliseconds\n */\n recordTaskExecution(poolId: string, executionTimeMs: number): void {\n const entry = this.pools.get(poolId);\n if (entry) {\n entry.totalTasksExecuted++;\n entry.totalExecutionTime += executionTimeMs;\n }\n }\n\n /**\n * Execute a task on a pool with automatic statistics tracking.\n *\n * @template T - Result type\n * @param poolId - Pool identifier\n * @param method - Method name to execute (for worker script pools) or inline function\n * @param args - Arguments to pass to the method/function\n * @param timeout - Optional timeout in milliseconds\n * @returns Promise resolving to the task result\n */\n async executeTask<T>(\n poolId: string,\n method: string | ((...args: unknown[]) => T),\n args: unknown[] = [],\n timeout?: number\n ): Promise<T> {\n const entry = this.pools.get(poolId);\n if (!entry) {\n throw new Error(`Pool '${poolId}' not found`);\n }\n\n const effectiveTimeout = timeout ?? entry.config.defaultTimeout ?? DEFAULT_CONFIG.defaultTimeout;\n const startTime = Date.now();\n\n try {\n let result: T;\n if (typeof method === 'string') {\n // Execute named method from worker script\n result = await entry.pool.exec(method, args).timeout(effectiveTimeout) as T;\n } else {\n // Execute inline function\n result = await entry.pool.exec(method, args).timeout(effectiveTimeout) as T;\n }\n\n const executionTime = Date.now() - startTime;\n this.recordTaskExecution(poolId, executionTime);\n\n return result;\n } catch (error) {\n const executionTime = Date.now() - startTime;\n this.recordTaskExecution(poolId, executionTime);\n throw error;\n }\n }\n\n /**\n * Shutdown a specific pool.\n *\n * @param poolId - Pool identifier\n * @param force - If true, forcefully terminate workers (default: false)\n * @returns Promise resolving when shutdown is complete\n */\n async shutdownPool(poolId: string, force = false): Promise<void> {\n const entry = this.pools.get(poolId);\n if (!entry) return;\n\n try {\n await entry.pool.terminate(force);\n this.emitEvent(poolId, 'shutdown');\n } catch (error) {\n this.emitEvent(poolId, 'error', error);\n throw error;\n } finally {\n this.pools.delete(poolId);\n }\n }\n\n /**\n * Shutdown all pools asynchronously.\n *\n * @param force - If true, forcefully terminate workers (default: false)\n * @returns Promise resolving when all pools are shut down\n */\n async shutdownAll(force = false): Promise<void> {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n const shutdownPromises: Promise<void>[] = [];\n for (const poolId of this.pools.keys()) {\n shutdownPromises.push(this.shutdownPool(poolId, force));\n }\n\n try {\n await Promise.allSettled(shutdownPromises);\n } finally {\n this.pools.clear();\n this.isShuttingDown = false;\n }\n }\n\n /**\n * Synchronous shutdown for process exit handlers.\n * Forces immediate termination of all pools.\n */\n private shutdownAllSync(): void {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n for (const [poolId, entry] of this.pools) {\n try {\n entry.pool.terminate(true);\n this.emitEvent(poolId, 'shutdown');\n } catch {\n // Ignore errors during sync shutdown\n }\n }\n\n this.pools.clear();\n this.isShuttingDown = false;\n }\n\n /**\n * Register an event callback for pool events.\n *\n * @param callback - Callback function to invoke on events\n * @returns Unsubscribe function\n */\n onEvent(callback: PoolEventCallback): () => void {\n this.eventCallbacks.push(callback);\n return () => {\n const index = this.eventCallbacks.indexOf(callback);\n if (index >= 0) {\n this.eventCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Emit an event to all registered callbacks.\n */\n private emitEvent(poolId: string, event: 'created' | 'shutdown' | 'error', data?: unknown): void {\n for (const callback of this.eventCallbacks) {\n try {\n callback(poolId, event, data);\n } catch {\n // Ignore callback errors\n }\n }\n }\n\n /**\n * Get the number of active pools.\n *\n * @returns Number of pools currently managed\n */\n get poolCount(): number {\n return this.pools.size;\n }\n\n /**\n * Get all pool IDs.\n *\n * @returns Array of pool identifiers\n */\n getPoolIds(): string[] {\n return Array.from(this.pools.keys());\n }\n\n /**\n * Check if the minimum parallel size threshold is met.\n *\n * @param poolId - Pool identifier\n * @param size - Size of the data to process\n * @returns True if size meets or exceeds minimum threshold\n */\n shouldUseParallel(poolId: string, size: number): boolean {\n const entry = this.pools.get(poolId);\n const minSize = entry?.config.minParallelSize ?? DEFAULT_CONFIG.minParallelSize;\n return size >= minSize;\n }\n\n /**\n * Get the default configuration values.\n *\n * @returns Copy of default configuration\n */\n static getDefaultConfig(): Required<WorkerPoolConfig> {\n return { ...DEFAULT_CONFIG };\n }\n\n /**\n * Get the CPU count available for workers.\n *\n * @returns Number of CPUs\n */\n static getCpuCount(): number {\n return workerpool.cpus;\n }\n}\n\n/**\n * Convenience function to get the WorkerPoolManager instance.\n *\n * @returns The WorkerPoolManager singleton\n */\nexport function getWorkerPoolManager(): WorkerPoolManager {\n return WorkerPoolManager.getInstance();\n}\n","/**\n * Batch Processor\n *\n * Phase 12 Sprint 2: Generic batch processing utility with parallel execution,\n * retry logic, progress callbacks, and error collection.\n *\n * @module utils/BatchProcessor\n */\n\n/**\n * Progress information for batch processing.\n */\nexport interface BatchProgress {\n /** Number of items completed (successfully or failed) */\n completed: number;\n /** Total number of items to process */\n total: number;\n /** Completion percentage (0-100) */\n percentage: number;\n /** Number of successful items */\n succeeded: number;\n /** Number of failed items */\n failed: number;\n /** Current item being processed (if applicable) */\n currentItem?: unknown;\n /** Current batch index */\n batchIndex: number;\n /** Total number of batches */\n totalBatches: number;\n}\n\n/**\n * Progress callback function type.\n */\nexport type BatchProgressCallback = (progress: BatchProgress) => void;\n\n/**\n * Result for a single item in the batch.\n */\nexport interface BatchItemResult<T> {\n /** Index of the item in the original array */\n index: number;\n /** Whether the processing succeeded */\n success: boolean;\n /** Result value if successful */\n result?: T;\n /** Error if failed */\n error?: Error;\n /** Number of attempts made */\n attempts: number;\n /** Processing time in milliseconds */\n durationMs: number;\n}\n\n/**\n * Overall result of batch processing.\n */\nexport interface BatchProcessResult<T> {\n /** Results for each item */\n results: BatchItemResult<T>[];\n /** Number of successful items */\n succeeded: number;\n /** Number of failed items */\n failed: number;\n /** Total processing time in milliseconds */\n totalTimeMs: number;\n /** Whether all items succeeded */\n allSucceeded: boolean;\n /** Errors encountered (indexed by item position) */\n errors: Map<number, Error>;\n}\n\n/**\n * Options for batch processing.\n */\nexport interface BatchProcessorOptions {\n /** Number of items to process concurrently (default: 4) */\n concurrency?: number;\n /** Size of each batch for progress reporting (default: equals concurrency) */\n batchSize?: number;\n /** Maximum retry attempts per item (default: 0 - no retries) */\n maxRetries?: number;\n /** Initial delay between retries in milliseconds (default: 1000) */\n retryDelayMs?: number;\n /** Exponential backoff multiplier (default: 2) */\n retryBackoffMultiplier?: number;\n /** Maximum retry delay in milliseconds (default: 30000) */\n maxRetryDelayMs?: number;\n /** Whether to continue processing on item failure (default: true) */\n continueOnError?: boolean;\n /** Progress callback function */\n onProgress?: BatchProgressCallback;\n /** Timeout per item in milliseconds (default: no timeout) */\n itemTimeoutMs?: number;\n /** Optional abort signal for cancellation */\n signal?: AbortSignal;\n}\n\n/**\n * Default options for batch processing.\n */\nconst DEFAULT_OPTIONS: Required<Omit<BatchProcessorOptions, 'onProgress' | 'signal'>> = {\n concurrency: 4,\n batchSize: 4,\n maxRetries: 0,\n retryDelayMs: 1000,\n retryBackoffMultiplier: 2,\n maxRetryDelayMs: 30000,\n continueOnError: true,\n itemTimeoutMs: 0,\n};\n\n/**\n * BatchProcessor - Generic batch processing with parallel execution\n *\n * Provides a flexible utility for processing arrays of items with:\n * - Configurable concurrency\n * - Automatic retry with exponential backoff\n * - Progress callbacks for monitoring\n * - Error collection without failing the entire batch\n * - Cancellation support via AbortSignal\n *\n * @example\n * ```typescript\n * const processor = new BatchProcessor<string, number>({\n * concurrency: 4,\n * maxRetries: 3,\n * onProgress: (p) => console.log(`${p.percentage.toFixed(1)}% complete`),\n * });\n *\n * const result = await processor.process(\n * ['item1', 'item2', 'item3'],\n * async (item) => {\n * const response = await fetchData(item);\n * return response.value;\n * }\n * );\n *\n * console.log(`Succeeded: ${result.succeeded}, Failed: ${result.failed}`);\n * ```\n */\nexport class BatchProcessor<TInput, TOutput> {\n private options: Required<Omit<BatchProcessorOptions, 'onProgress' | 'signal'>> & {\n onProgress?: BatchProgressCallback;\n signal?: AbortSignal;\n };\n\n constructor(options: BatchProcessorOptions = {}) {\n this.options = {\n ...DEFAULT_OPTIONS,\n ...options,\n batchSize: options.batchSize ?? options.concurrency ?? DEFAULT_OPTIONS.concurrency,\n };\n }\n\n /**\n * Process all items in batches with the specified processor function.\n *\n * @param items - Array of items to process\n * @param processor - Async function to process each item\n * @returns Batch processing result with all item results and statistics\n */\n async process(\n items: TInput[],\n processor: (item: TInput, index: number) => Promise<TOutput>\n ): Promise<BatchProcessResult<TOutput>> {\n const startTime = Date.now();\n const results: BatchItemResult<TOutput>[] = new Array(items.length);\n const errors = new Map<number, Error>();\n let succeeded = 0;\n let failed = 0;\n\n const totalBatches = Math.ceil(items.length / this.options.batchSize);\n\n // Process items in batches\n for (let batchIndex = 0; batchIndex < items.length; batchIndex += this.options.batchSize) {\n // Check for cancellation\n if (this.options.signal?.aborted) {\n throw new Error('Batch processing cancelled');\n }\n\n const batchItems = items.slice(batchIndex, batchIndex + this.options.batchSize);\n const batchStartIndex = batchIndex;\n const currentBatchIndex = Math.floor(batchIndex / this.options.batchSize);\n\n // Process batch concurrently\n const batchPromises = batchItems.map((item, localIndex) =>\n this.processItem(item, batchStartIndex + localIndex, processor)\n );\n\n const batchResults = await Promise.all(batchPromises);\n\n // Collect results\n for (let i = 0; i < batchResults.length; i++) {\n const itemResult = batchResults[i];\n const globalIndex = batchStartIndex + i;\n results[globalIndex] = itemResult;\n\n if (itemResult.success) {\n succeeded++;\n } else {\n failed++;\n if (itemResult.error) {\n errors.set(globalIndex, itemResult.error);\n }\n\n // Stop if continueOnError is false\n if (!this.options.continueOnError) {\n return {\n results,\n succeeded,\n failed,\n totalTimeMs: Date.now() - startTime,\n allSucceeded: false,\n errors,\n };\n }\n }\n }\n\n // Report progress\n if (this.options.onProgress) {\n const completed = Math.min(batchIndex + batchItems.length, items.length);\n this.options.onProgress({\n completed,\n total: items.length,\n percentage: (completed / items.length) * 100,\n succeeded,\n failed,\n batchIndex: currentBatchIndex,\n totalBatches,\n });\n }\n }\n\n return {\n results,\n succeeded,\n failed,\n totalTimeMs: Date.now() - startTime,\n allSucceeded: failed === 0,\n errors,\n };\n }\n\n /**\n * Process a single item with retry logic.\n */\n private async processItem(\n item: TInput,\n index: number,\n processor: (item: TInput, index: number) => Promise<TOutput>\n ): Promise<BatchItemResult<TOutput>> {\n const startTime = Date.now();\n let lastError: Error | undefined;\n let attempts = 0;\n\n for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {\n attempts = attempt + 1;\n\n // Check for cancellation before each attempt\n if (this.options.signal?.aborted) {\n return {\n index,\n success: false,\n error: new Error('Processing cancelled'),\n attempts,\n durationMs: Date.now() - startTime,\n };\n }\n\n try {\n let result: TOutput;\n\n if (this.options.itemTimeoutMs > 0) {\n // Process with timeout\n result = await Promise.race([\n processor(item, index),\n this.createTimeout<TOutput>(this.options.itemTimeoutMs),\n ]);\n } else {\n result = await processor(item, index);\n }\n\n return {\n index,\n success: true,\n result,\n attempts,\n durationMs: Date.now() - startTime,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on last attempt\n if (attempt < this.options.maxRetries) {\n const delay = this.calculateRetryDelay(attempt);\n await this.sleep(delay);\n }\n }\n }\n\n return {\n index,\n success: false,\n error: lastError,\n attempts,\n durationMs: Date.now() - startTime,\n };\n }\n\n /**\n * Calculate delay for retry with exponential backoff.\n */\n private calculateRetryDelay(attempt: number): number {\n const delay = this.options.retryDelayMs * Math.pow(this.options.retryBackoffMultiplier, attempt);\n return Math.min(delay, this.options.maxRetryDelayMs);\n }\n\n /**\n * Create a timeout promise.\n */\n private createTimeout<T>(ms: number): Promise<T> {\n return new Promise((_, reject) => {\n setTimeout(() => reject(new Error('Item processing timeout')), ms);\n });\n }\n\n /**\n * Sleep for the specified duration.\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n /**\n * Get the configured options.\n */\n getOptions(): BatchProcessorOptions {\n return { ...this.options };\n }\n}\n\n/**\n * Process items in parallel batches (convenience function).\n *\n * @template TInput - Input item type\n * @template TOutput - Output result type\n * @param items - Array of items to process\n * @param processor - Async function to process each item\n * @param options - Batch processing options\n * @returns Batch processing result\n *\n * @example\n * ```typescript\n * const result = await processBatch(\n * urls,\n * async (url) => fetch(url).then(r => r.json()),\n * { concurrency: 5, maxRetries: 2 }\n * );\n * ```\n */\nexport async function processBatch<TInput, TOutput>(\n items: TInput[],\n processor: (item: TInput, index: number) => Promise<TOutput>,\n options: BatchProcessorOptions = {}\n): Promise<BatchProcessResult<TOutput>> {\n const batchProcessor = new BatchProcessor<TInput, TOutput>(options);\n return batchProcessor.process(items, processor);\n}\n\n/**\n * Process items with automatic retry on failure (convenience function).\n *\n * @template TInput - Input item type\n * @template TOutput - Output result type\n * @param items - Array of items to process\n * @param processor - Async function to process each item\n * @param maxRetries - Maximum retry attempts (default: 3)\n * @param onProgress - Optional progress callback\n * @returns Batch processing result\n *\n * @example\n * ```typescript\n * const result = await processWithRetry(\n * items,\n * async (item) => unreliableOperation(item),\n * 3,\n * (p) => console.log(`${p.percentage}%`)\n * );\n * ```\n */\nexport async function processWithRetry<TInput, TOutput>(\n items: TInput[],\n processor: (item: TInput, index: number) => Promise<TOutput>,\n maxRetries = 3,\n onProgress?: BatchProgressCallback\n): Promise<BatchProcessResult<TOutput>> {\n return processBatch(items, processor, { maxRetries, onProgress });\n}\n\n/**\n * Chunk an array into smaller arrays of specified size.\n *\n * @template T - Item type\n * @param array - Array to chunk\n * @param size - Size of each chunk\n * @returns Array of chunks\n *\n * @example\n * ```typescript\n * const chunks = chunkArray([1, 2, 3, 4, 5], 2);\n * // [[1, 2], [3, 4], [5]]\n * ```\n */\nexport function chunkArray<T>(array: T[], size: number): T[][] {\n if (size <= 0) {\n throw new Error('Chunk size must be greater than 0');\n }\n\n const chunks: T[][] = [];\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size));\n }\n return chunks;\n}\n\n/**\n * Execute async functions in parallel with a concurrency limit.\n *\n * @template T - Result type\n * @param tasks - Array of async functions to execute\n * @param concurrency - Maximum concurrent executions\n * @returns Array of results (successful results or errors)\n *\n * @example\n * ```typescript\n * const tasks = urls.map(url => () => fetch(url));\n * const results = await parallelLimit(tasks, 5);\n * ```\n */\nexport async function parallelLimit<T>(\n tasks: Array<() => Promise<T>>,\n concurrency: number\n): Promise<Array<{ success: true; value: T } | { success: false; error: Error }>> {\n const results: Array<{ success: true; value: T } | { success: false; error: Error }> = [];\n\n for (let i = 0; i < tasks.length; i += concurrency) {\n const batch = tasks.slice(i, i + concurrency);\n\n const batchResults = await Promise.all(\n batch.map(async task => {\n try {\n const value = await task();\n return { success: true as const, value };\n } catch (error) {\n return {\n success: false as const,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n })\n );\n\n results.push(...batchResults);\n }\n\n return results;\n}\n\n/**\n * Map over items in parallel with a concurrency limit.\n *\n * @template TInput - Input item type\n * @template TOutput - Output result type\n * @param items - Array of items to map\n * @param mapper - Async mapping function\n * @param concurrency - Maximum concurrent operations (default: 4)\n * @returns Array of results (in order)\n *\n * @example\n * ```typescript\n * const results = await mapParallel(\n * ids,\n * async (id) => fetchUser(id),\n * 10\n * );\n * ```\n */\nexport async function mapParallel<TInput, TOutput>(\n items: TInput[],\n mapper: (item: TInput, index: number) => Promise<TOutput>,\n concurrency = 4\n): Promise<TOutput[]> {\n const results: TOutput[] = new Array(items.length);\n\n for (let i = 0; i < items.length; i += concurrency) {\n const batch = items.slice(i, i + concurrency);\n const startIndex = i;\n\n const batchResults = await Promise.all(\n batch.map((item, localIndex) => mapper(item, startIndex + localIndex))\n );\n\n for (let j = 0; j < batchResults.length; j++) {\n results[startIndex + j] = batchResults[j];\n }\n }\n\n return results;\n}\n\n/**\n * Filter items in parallel with a concurrency limit.\n *\n * @template T - Item type\n * @param items - Array of items to filter\n * @param predicate - Async predicate function\n * @param concurrency - Maximum concurrent operations (default: 4)\n * @returns Filtered array (in order)\n *\n * @example\n * ```typescript\n * const validItems = await filterParallel(\n * items,\n * async (item) => validateItem(item),\n * 10\n * );\n * ```\n */\nexport async function filterParallel<T>(\n items: T[],\n predicate: (item: T, index: number) => Promise<boolean>,\n concurrency = 4\n): Promise<T[]> {\n const includeFlags = await mapParallel(items, predicate, concurrency);\n return items.filter((_, index) => includeFlags[index]);\n}\n","/**\n * Memory Usage Monitor\n *\n * Phase 12 Sprint 6: Track memory usage across all components\n * with human-readable formatting.\n *\n * @module utils/MemoryMonitor\n */\n\n/**\n * Memory usage for a single component.\n */\nexport interface ComponentMemoryUsage {\n /** Component name */\n name: string;\n /** Estimated memory usage in bytes */\n bytes: number;\n /** Item count (if applicable) */\n itemCount?: number;\n /** Average bytes per item */\n bytesPerItem?: number;\n}\n\n/**\n * Aggregate memory usage statistics.\n */\nexport interface MemoryUsageStats {\n /** Total memory usage in bytes */\n totalBytes: number;\n /** Formatted total memory */\n totalFormatted: string;\n /** Per-component breakdown */\n components: ComponentMemoryUsage[];\n /** Timestamp of measurement */\n timestamp: Date;\n /** Node.js heap stats (if available) */\n heapStats?: {\n heapUsed: number;\n heapTotal: number;\n external: number;\n rss: number;\n };\n}\n\n/**\n * Memory threshold configuration.\n */\nexport interface MemoryThresholds {\n /** Warning threshold in bytes */\n warning: number;\n /** Critical threshold in bytes */\n critical: number;\n}\n\n/**\n * Memory alert.\n */\nexport interface MemoryAlert {\n /** Alert level */\n level: 'warning' | 'critical';\n /** Component that triggered the alert */\n component: string;\n /** Current usage */\n currentBytes: number;\n /** Threshold exceeded */\n threshold: number;\n /** Message */\n message: string;\n}\n\n/**\n * Callback for memory change events.\n */\nexport type MemoryChangeCallback = (usage: MemoryUsageStats) => void;\n\n/**\n * Default memory thresholds.\n */\nconst DEFAULT_THRESHOLDS: MemoryThresholds = {\n warning: 100 * 1024 * 1024, // 100 MB\n critical: 500 * 1024 * 1024, // 500 MB\n};\n\n/**\n * Memory Monitor for tracking usage across components.\n *\n * @example\n * ```typescript\n * const monitor = new MemoryMonitor();\n *\n * // Register components\n * monitor.registerComponent('entities', () => entities.length * 500);\n * monitor.registerComponent('vectors', () => vectors.size * dimension * 4);\n *\n * // Get usage stats\n * const stats = monitor.getUsage();\n * console.log(`Total memory: ${stats.totalFormatted}`);\n *\n * // Check for alerts\n * const alerts = monitor.checkThresholds();\n * ```\n */\nexport class MemoryMonitor {\n private componentEstimators: Map<string, () => number>;\n private itemCounters: Map<string, () => number>;\n private thresholds: MemoryThresholds;\n private listeners: MemoryChangeCallback[];\n private lastUsage: MemoryUsageStats | null = null;\n\n constructor(thresholds?: Partial<MemoryThresholds>) {\n this.componentEstimators = new Map();\n this.itemCounters = new Map();\n this.thresholds = { ...DEFAULT_THRESHOLDS, ...thresholds };\n this.listeners = [];\n }\n\n /**\n * Register a component for memory tracking.\n *\n * @param name - Component name\n * @param estimator - Function that returns estimated bytes\n * @param itemCounter - Optional function that returns item count\n */\n registerComponent(\n name: string,\n estimator: () => number,\n itemCounter?: () => number\n ): void {\n this.componentEstimators.set(name, estimator);\n if (itemCounter) {\n this.itemCounters.set(name, itemCounter);\n }\n }\n\n /**\n * Unregister a component.\n *\n * @param name - Component name\n */\n unregisterComponent(name: string): void {\n this.componentEstimators.delete(name);\n this.itemCounters.delete(name);\n }\n\n /**\n * Get current memory usage statistics.\n */\n getUsage(): MemoryUsageStats {\n const components: ComponentMemoryUsage[] = [];\n let totalBytes = 0;\n\n for (const [name, estimator] of this.componentEstimators) {\n const bytes = estimator();\n totalBytes += bytes;\n\n const itemCounter = this.itemCounters.get(name);\n const itemCount = itemCounter ? itemCounter() : undefined;\n const bytesPerItem = itemCount && itemCount > 0 ? Math.round(bytes / itemCount) : undefined;\n\n components.push({\n name,\n bytes,\n itemCount,\n bytesPerItem,\n });\n }\n\n // Sort by usage descending\n components.sort((a, b) => b.bytes - a.bytes);\n\n // Get Node.js heap stats if available\n let heapStats: MemoryUsageStats['heapStats'];\n if (typeof process !== 'undefined' && process.memoryUsage) {\n const mem = process.memoryUsage();\n heapStats = {\n heapUsed: mem.heapUsed,\n heapTotal: mem.heapTotal,\n external: mem.external,\n rss: mem.rss,\n };\n }\n\n const stats: MemoryUsageStats = {\n totalBytes,\n totalFormatted: this.formatBytes(totalBytes),\n components,\n timestamp: new Date(),\n heapStats,\n };\n\n this.lastUsage = stats;\n this.notifyListeners(stats);\n\n return stats;\n }\n\n /**\n * Get memory usage for a specific component.\n *\n * @param name - Component name\n */\n getComponentUsage(name: string): ComponentMemoryUsage | undefined {\n const estimator = this.componentEstimators.get(name);\n if (!estimator) return undefined;\n\n const bytes = estimator();\n const itemCounter = this.itemCounters.get(name);\n const itemCount = itemCounter ? itemCounter() : undefined;\n const bytesPerItem = itemCount && itemCount > 0 ? Math.round(bytes / itemCount) : undefined;\n\n return {\n name,\n bytes,\n itemCount,\n bytesPerItem,\n };\n }\n\n /**\n * Check memory thresholds and return alerts.\n */\n checkThresholds(): MemoryAlert[] {\n const alerts: MemoryAlert[] = [];\n const usage = this.getUsage();\n\n // Check total memory\n if (usage.totalBytes >= this.thresholds.critical) {\n alerts.push({\n level: 'critical',\n component: 'total',\n currentBytes: usage.totalBytes,\n threshold: this.thresholds.critical,\n message: `Total memory usage (${this.formatBytes(usage.totalBytes)}) exceeds critical threshold (${this.formatBytes(this.thresholds.critical)})`,\n });\n } else if (usage.totalBytes >= this.thresholds.warning) {\n alerts.push({\n level: 'warning',\n component: 'total',\n currentBytes: usage.totalBytes,\n threshold: this.thresholds.warning,\n message: `Total memory usage (${this.formatBytes(usage.totalBytes)}) exceeds warning threshold (${this.formatBytes(this.thresholds.warning)})`,\n });\n }\n\n // Check per-component thresholds (50% of total threshold per component)\n const componentWarning = this.thresholds.warning * 0.5;\n const componentCritical = this.thresholds.critical * 0.5;\n\n for (const component of usage.components) {\n if (component.bytes >= componentCritical) {\n alerts.push({\n level: 'critical',\n component: component.name,\n currentBytes: component.bytes,\n threshold: componentCritical,\n message: `Component '${component.name}' (${this.formatBytes(component.bytes)}) exceeds critical threshold`,\n });\n } else if (component.bytes >= componentWarning) {\n alerts.push({\n level: 'warning',\n component: component.name,\n currentBytes: component.bytes,\n threshold: componentWarning,\n message: `Component '${component.name}' (${this.formatBytes(component.bytes)}) exceeds warning threshold`,\n });\n }\n }\n\n return alerts;\n }\n\n /**\n * Set memory thresholds.\n *\n * @param thresholds - New threshold values\n */\n setThresholds(thresholds: Partial<MemoryThresholds>): void {\n this.thresholds = { ...this.thresholds, ...thresholds };\n }\n\n /**\n * Get current thresholds.\n */\n getThresholds(): MemoryThresholds {\n return { ...this.thresholds };\n }\n\n /**\n * Add a listener for memory changes.\n *\n * @param callback - Callback to invoke on memory changes\n */\n addListener(callback: MemoryChangeCallback): void {\n this.listeners.push(callback);\n }\n\n /**\n * Remove a listener.\n *\n * @param callback - Callback to remove\n */\n removeListener(callback: MemoryChangeCallback): void {\n const index = this.listeners.indexOf(callback);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n }\n }\n\n /**\n * Get a human-readable summary of memory usage.\n */\n getSummary(): string {\n const usage = this.getUsage();\n const lines: string[] = [\n '=== Memory Usage Summary ===',\n `Total: ${usage.totalFormatted}`,\n '',\n 'By Component:',\n ];\n\n for (const component of usage.components) {\n const itemInfo = component.itemCount\n ? ` (${component.itemCount.toLocaleString()} items, ~${this.formatBytes(component.bytesPerItem ?? 0)}/item)`\n : '';\n lines.push(` ${component.name}: ${this.formatBytes(component.bytes)}${itemInfo}`);\n }\n\n if (usage.heapStats) {\n lines.push('');\n lines.push('Node.js Heap:');\n lines.push(` Heap Used: ${this.formatBytes(usage.heapStats.heapUsed)}`);\n lines.push(` Heap Total: ${this.formatBytes(usage.heapStats.heapTotal)}`);\n lines.push(` External: ${this.formatBytes(usage.heapStats.external)}`);\n lines.push(` RSS: ${this.formatBytes(usage.heapStats.rss)}`);\n }\n\n return lines.join('\\n');\n }\n\n /**\n * Get the last recorded usage without triggering a new measurement.\n */\n getLastUsage(): MemoryUsageStats | null {\n return this.lastUsage;\n }\n\n /**\n * Format bytes as human-readable string.\n *\n * @param bytes - Number of bytes\n */\n formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B';\n\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n const k = 1024;\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n const value = bytes / Math.pow(k, i);\n\n return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;\n }\n\n /**\n * Parse a formatted byte string back to number.\n *\n * @param formatted - Formatted string like \"10 MB\"\n */\n parseBytes(formatted: string): number {\n const match = formatted.match(/^([\\d.]+)\\s*(B|KB|MB|GB|TB)$/i);\n if (!match) return 0;\n\n const value = parseFloat(match[1]);\n const unit = match[2].toUpperCase();\n const units: Record<string, number> = {\n 'B': 1,\n 'KB': 1024,\n 'MB': 1024 * 1024,\n 'GB': 1024 * 1024 * 1024,\n 'TB': 1024 * 1024 * 1024 * 1024,\n };\n\n return value * (units[unit] ?? 1);\n }\n\n /**\n * Clear all registered components.\n */\n clear(): void {\n this.componentEstimators.clear();\n this.itemCounters.clear();\n this.lastUsage = null;\n }\n\n // Private methods\n\n private notifyListeners(usage: MemoryUsageStats): void {\n for (const listener of this.listeners) {\n try {\n listener(usage);\n } catch {\n // Ignore listener errors\n }\n }\n }\n}\n\n/**\n * Singleton instance for global memory monitoring.\n */\nexport const globalMemoryMonitor = new MemoryMonitor();\n","/**\n * Relation Helper Utilities\n *\n * Type guards and builder pattern for working with relations.\n *\n * @module utils/relationHelpers\n */\n\nimport type {\n Relation,\n WeightedRelation,\n TemporalRelation,\n BidirectionalRelation,\n RelationProperties,\n} from '../types/types.js';\n\n/**\n * Type guard to check if a relation has a weight.\n *\n * @param relation - The relation to check\n * @returns True if the relation has a numeric weight\n *\n * @example\n * ```typescript\n * const rel: Relation = { from: 'A', to: 'B', relationType: 'knows', weight: 0.5 };\n * if (isWeightedRelation(rel)) {\n * console.log(rel.weight); // TypeScript knows weight is number\n * }\n * ```\n */\nexport function isWeightedRelation(relation: Relation): relation is WeightedRelation {\n return typeof relation.weight === 'number';\n}\n\n/**\n * Type guard to check if a relation has temporal validity.\n *\n * @param relation - The relation to check\n * @returns True if the relation has a validFrom property\n *\n * @example\n * ```typescript\n * if (isTemporalRelation(rel)) {\n * console.log(`Valid from: ${rel.properties.validFrom}`);\n * }\n * ```\n */\nexport function isTemporalRelation(relation: Relation): relation is TemporalRelation {\n return typeof relation.properties?.validFrom === 'string';\n}\n\n/**\n * Type guard to check if a relation is bidirectional.\n *\n * @param relation - The relation to check\n * @returns True if the relation is marked as bidirectional\n *\n * @example\n * ```typescript\n * if (isBidirectionalRelation(rel)) {\n * // This relation implies the reverse relation exists\n * }\n * ```\n */\nexport function isBidirectionalRelation(relation: Relation): relation is BidirectionalRelation {\n return relation.properties?.bidirectional === true;\n}\n\n/**\n * Check if a relation has a confidence score.\n *\n * @param relation - The relation to check\n * @returns True if the relation has a confidence score\n */\nexport function hasConfidence(relation: Relation): boolean {\n return typeof relation.confidence === 'number';\n}\n\n/**\n * Check if a relation is currently valid based on temporal properties.\n *\n * @param relation - The relation to check\n * @param referenceDate - Date to check against (default: now)\n * @returns True if the relation is valid at the reference date\n */\nexport function isCurrentlyValid(relation: Relation, referenceDate: Date = new Date()): boolean {\n if (!relation.properties) {\n return true; // No temporal constraints means always valid\n }\n\n const { validFrom, validUntil } = relation.properties;\n\n if (validFrom) {\n const fromDate = new Date(validFrom);\n if (referenceDate < fromDate) {\n return false;\n }\n }\n\n if (validUntil) {\n const untilDate = new Date(validUntil);\n if (referenceDate > untilDate) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Fluent builder for constructing relations.\n *\n * Provides a convenient way to build relations with validation.\n *\n * @example\n * ```typescript\n * const relation = new RelationBuilder('Alice', 'Bob', 'knows')\n * .withWeight(0.8)\n * .withConfidence(0.95)\n * .bidirectional()\n * .validFrom('2024-01-01')\n * .build();\n * ```\n */\nexport class RelationBuilder {\n private relation: Relation;\n\n /**\n * Create a new RelationBuilder.\n *\n * @param from - Source entity name\n * @param to - Target entity name\n * @param relationType - Type of relationship\n */\n constructor(from: string, to: string, relationType: string) {\n this.relation = { from, to, relationType };\n }\n\n /**\n * Set the weight for this relation.\n *\n * @param weight - Weight value (must be between 0 and 1)\n * @throws Error if weight is out of range\n */\n withWeight(weight: number): this {\n if (weight < 0 || weight > 1) {\n throw new Error('Weight must be between 0 and 1');\n }\n this.relation.weight = weight;\n return this;\n }\n\n /**\n * Set the confidence score for this relation.\n *\n * @param confidence - Confidence value (must be between 0 and 1)\n * @throws Error if confidence is out of range\n */\n withConfidence(confidence: number): this {\n if (confidence < 0 || confidence > 1) {\n throw new Error('Confidence must be between 0 and 1');\n }\n this.relation.confidence = confidence;\n return this;\n }\n\n /**\n * Mark this relation as bidirectional.\n *\n * @param value - Whether the relation is bidirectional (default: true)\n */\n bidirectional(value: boolean = true): this {\n this.relation.properties = {\n ...this.relation.properties,\n bidirectional: value,\n };\n return this;\n }\n\n /**\n * Set when this relation becomes valid.\n *\n * @param date - ISO 8601 date string\n */\n validFrom(date: string): this {\n this.relation.properties = {\n ...this.relation.properties,\n validFrom: date,\n };\n return this;\n }\n\n /**\n * Set when this relation stops being valid.\n *\n * @param date - ISO 8601 date string\n */\n validUntil(date: string): this {\n this.relation.properties = {\n ...this.relation.properties,\n validUntil: date,\n };\n return this;\n }\n\n /**\n * Set the source/provenance of this relation.\n *\n * @param source - Source identifier\n */\n withSource(source: string): this {\n this.relation.properties = {\n ...this.relation.properties,\n source,\n };\n return this;\n }\n\n /**\n * Set how this relation was established.\n *\n * @param method - Establishment method\n */\n withMethod(method: RelationProperties['method']): this {\n this.relation.properties = {\n ...this.relation.properties,\n method,\n };\n return this;\n }\n\n /**\n * Add arbitrary metadata to the relation.\n *\n * @param metadata - Key-value pairs to add\n */\n withMetadata(metadata: Record<string, unknown>): this {\n this.relation.metadata = { ...this.relation.metadata, ...metadata };\n return this;\n }\n\n /**\n * Build the final relation object.\n *\n * Automatically sets createdAt timestamp if not already set.\n *\n * @returns The constructed relation\n */\n build(): Relation {\n this.relation.createdAt = new Date().toISOString();\n return { ...this.relation };\n }\n}\n","/**\n * Relation Validation Utilities\n *\n * Functions for validating relation properties.\n *\n * @module utils/relationValidation\n */\n\nimport type { Relation } from '../types/types.js';\n\n/**\n * A validation error for relation fields.\n * Named distinctly from ValidationIssue in types.ts which is for graph validation.\n */\nexport interface RelationValidationError {\n /** Field that has the error */\n field: string;\n /** Error message */\n message: string;\n /** The invalid value (if applicable) */\n value?: unknown;\n}\n\n/**\n * A validation warning for relation fields.\n * Warnings don't make a relation invalid, but may indicate potential issues.\n */\nexport interface RelationValidationWarning {\n /** Field that has the warning */\n field: string;\n /** Warning message */\n message: string;\n /** Suggested fix (if applicable) */\n suggestion?: string;\n}\n\n/**\n * Result of relation validation.\n */\nexport interface RelationValidationResult {\n /** Whether the relation is valid (no errors) */\n isValid: boolean;\n /** List of validation errors */\n errors: RelationValidationError[];\n /** List of validation warnings */\n warnings: RelationValidationWarning[];\n}\n\n/**\n * Validate relation properties.\n *\n * Checks:\n * - Required fields (from, to, relationType)\n * - Weight range (0-1) if present\n * - Confidence range (0-1) if present\n * - Temporal consistency (validFrom <= validUntil) if both present\n * - Self-referential relations (warning)\n *\n * @param relation - The relation to validate\n * @returns Validation result with errors and warnings\n *\n * @example\n * ```typescript\n * const result = validateRelationMetadata({\n * from: 'Alice',\n * to: 'Bob',\n * relationType: 'knows',\n * weight: 1.5, // Invalid - out of range\n * });\n *\n * if (!result.isValid) {\n * console.log('Errors:', result.errors);\n * }\n * ```\n */\nexport function validateRelationMetadata(relation: Relation): RelationValidationResult {\n const errors: RelationValidationError[] = [];\n const warnings: RelationValidationWarning[] = [];\n\n // Required fields\n if (!relation.from || typeof relation.from !== 'string') {\n errors.push({\n field: 'from',\n message: 'from is required and must be a non-empty string',\n value: relation.from,\n });\n }\n\n if (!relation.to || typeof relation.to !== 'string') {\n errors.push({\n field: 'to',\n message: 'to is required and must be a non-empty string',\n value: relation.to,\n });\n }\n\n if (!relation.relationType || typeof relation.relationType !== 'string') {\n errors.push({\n field: 'relationType',\n message: 'relationType is required and must be a non-empty string',\n value: relation.relationType,\n });\n }\n\n // Weight validation (0-1 range)\n if (relation.weight !== undefined) {\n if (typeof relation.weight !== 'number') {\n errors.push({\n field: 'weight',\n message: 'weight must be a number',\n value: relation.weight,\n });\n } else if (isNaN(relation.weight)) {\n errors.push({\n field: 'weight',\n message: 'weight must not be NaN',\n value: relation.weight,\n });\n } else if (relation.weight < 0 || relation.weight > 1) {\n errors.push({\n field: 'weight',\n message: 'weight must be between 0 and 1',\n value: relation.weight,\n });\n }\n }\n\n // Confidence validation (0-1 range)\n if (relation.confidence !== undefined) {\n if (typeof relation.confidence !== 'number') {\n errors.push({\n field: 'confidence',\n message: 'confidence must be a number',\n value: relation.confidence,\n });\n } else if (isNaN(relation.confidence)) {\n errors.push({\n field: 'confidence',\n message: 'confidence must not be NaN',\n value: relation.confidence,\n });\n } else if (relation.confidence < 0 || relation.confidence > 1) {\n errors.push({\n field: 'confidence',\n message: 'confidence must be between 0 and 1',\n value: relation.confidence,\n });\n }\n }\n\n // Temporal validation\n if (relation.properties?.validFrom !== undefined) {\n const from = new Date(relation.properties.validFrom);\n if (isNaN(from.getTime())) {\n errors.push({\n field: 'properties.validFrom',\n message: 'validFrom must be a valid ISO 8601 date',\n value: relation.properties.validFrom,\n });\n }\n }\n\n if (relation.properties?.validUntil !== undefined) {\n const until = new Date(relation.properties.validUntil);\n if (isNaN(until.getTime())) {\n errors.push({\n field: 'properties.validUntil',\n message: 'validUntil must be a valid ISO 8601 date',\n value: relation.properties.validUntil,\n });\n }\n }\n\n // Temporal consistency check\n if (relation.properties?.validFrom && relation.properties?.validUntil) {\n const from = new Date(relation.properties.validFrom);\n const until = new Date(relation.properties.validUntil);\n\n if (!isNaN(from.getTime()) && !isNaN(until.getTime()) && from > until) {\n errors.push({\n field: 'properties.validFrom/validUntil',\n message: 'validFrom must be before or equal to validUntil',\n value: { validFrom: relation.properties.validFrom, validUntil: relation.properties.validUntil },\n });\n }\n }\n\n // Confirmation/contradiction counts validation\n if (relation.properties?.confirmationCount !== undefined) {\n if (typeof relation.properties.confirmationCount !== 'number' ||\n relation.properties.confirmationCount < 0 ||\n !Number.isInteger(relation.properties.confirmationCount)) {\n errors.push({\n field: 'properties.confirmationCount',\n message: 'confirmationCount must be a non-negative integer',\n value: relation.properties.confirmationCount,\n });\n }\n }\n\n if (relation.properties?.contradictionCount !== undefined) {\n if (typeof relation.properties.contradictionCount !== 'number' ||\n relation.properties.contradictionCount < 0 ||\n !Number.isInteger(relation.properties.contradictionCount)) {\n errors.push({\n field: 'properties.contradictionCount',\n message: 'contradictionCount must be a non-negative integer',\n value: relation.properties.contradictionCount,\n });\n }\n }\n\n // Self-referential warning\n if (relation.from && relation.to && relation.from === relation.to) {\n warnings.push({\n field: 'from/to',\n message: 'Relation is self-referential (from === to)',\n suggestion: 'Verify this is intentional. Self-referential relations are unusual but sometimes valid.',\n });\n }\n\n // Low confidence warning\n if (typeof relation.confidence === 'number' && relation.confidence < 0.5) {\n warnings.push({\n field: 'confidence',\n message: `Relation has low confidence (${relation.confidence})`,\n suggestion: 'Consider verifying this relation or marking it for review.',\n });\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n };\n}\n\n/**\n * Validate multiple relations at once.\n *\n * @param relations - Array of relations to validate\n * @returns Array of validation results (same order as input)\n */\nexport function validateRelationsMetadata(relations: Relation[]): RelationValidationResult[] {\n return relations.map(validateRelationMetadata);\n}\n\n/**\n * Check if all relations are valid.\n *\n * @param relations - Array of relations to check\n * @returns True if all relations are valid\n */\nexport function allRelationsValidMetadata(relations: Relation[]): boolean {\n return relations.every(rel => validateRelationMetadata(rel).isValid);\n}\n","/**\n * Entity Validator\n *\n * Validates entities against configurable rules.\n * Types are embedded to avoid collision with ValidationIssue in types.ts\n * Phase 1 Sprint 9: Entity Validation Helpers.\n *\n * @module utils/EntityValidator\n */\n\nimport type { Entity } from '../types/types.js';\n\n// ==================== Validation Types ====================\n// Note: These are ENTITY validation types, distinct from graph ValidationIssue\n\n/**\n * A validation rule for entities.\n */\nexport interface EntityValidationRule {\n /** Name of this rule for identification */\n name: string;\n /** Field path to validate (e.g., 'name', 'observations.0') */\n field: string;\n /** Validation function */\n validate: (entity: Entity) => EntityRuleResult | Promise<EntityRuleResult>;\n /** Error message if validation fails */\n message: string;\n /** Severity level */\n severity?: 'error' | 'warning';\n}\n\n/**\n * Result of a single rule validation.\n */\nexport interface EntityRuleResult {\n /** Whether validation passed */\n valid: boolean;\n /** Optional custom message override */\n message?: string;\n}\n\n/**\n * A validation issue found during entity validation.\n * Named distinctly from ValidationIssue in types.ts (which is for graph validation).\n */\nexport interface EntityValidationIssue {\n /** Field that failed validation */\n field: string;\n /** Error/warning message */\n message: string;\n /** Severity level */\n severity: 'error' | 'warning';\n /** Current value of the field */\n value?: unknown;\n /** Name of the rule that failed */\n rule?: string;\n /** Suggestion for fixing the issue */\n suggestion?: string;\n}\n\n/**\n * Complete validation result for an entity.\n */\nexport interface EntityValidationResult {\n /** Whether entity is valid */\n isValid: boolean;\n /** Validation errors */\n errors: EntityValidationIssue[];\n /** Validation warnings */\n warnings: EntityValidationIssue[];\n /** The entity that was validated */\n entity: Entity;\n}\n\n// ==================== Validator Class ====================\n\nexport interface EntityValidatorConfig {\n /** Rules to apply */\n rules?: EntityValidationRule[];\n /** Stop on first error */\n failFast?: boolean;\n /** Treat warnings as errors */\n strict?: boolean;\n}\n\n/**\n * Validates entities against configurable rules.\n *\n * @example\n * ```typescript\n * import { EntityValidator, required, minLength, pattern } from './EntityValidator.js';\n *\n * const validator = new EntityValidator({\n * rules: [\n * required('name'),\n * minLength('name', 3),\n * pattern('name', /^[a-zA-Z]/),\n * ],\n * });\n *\n * const result = await validator.validate(entity);\n * if (!result.isValid) {\n * console.error(result.errors);\n * }\n * ```\n */\nexport class EntityValidator {\n private readonly config: Required<EntityValidatorConfig>;\n\n constructor(config: EntityValidatorConfig = {}) {\n this.config = {\n rules: config.rules ?? [],\n failFast: config.failFast ?? false,\n strict: config.strict ?? false,\n };\n }\n\n /**\n * Add a validation rule.\n */\n addRule(rule: EntityValidationRule): this {\n this.config.rules.push(rule);\n return this;\n }\n\n /**\n * Add multiple validation rules.\n */\n addRules(rules: EntityValidationRule[]): this {\n this.config.rules.push(...rules);\n return this;\n }\n\n /**\n * Get all registered rules.\n */\n getRules(): EntityValidationRule[] {\n return [...this.config.rules];\n }\n\n /**\n * Clear all rules.\n */\n clearRules(): this {\n this.config.rules.length = 0;\n return this;\n }\n\n /**\n * Validate an entity against all rules.\n */\n async validate(entity: Entity): Promise<EntityValidationResult> {\n const errors: EntityValidationIssue[] = [];\n const warnings: EntityValidationIssue[] = [];\n\n for (const rule of this.config.rules) {\n try {\n const result = await rule.validate(entity);\n\n if (!result.valid) {\n const issue: EntityValidationIssue = {\n field: rule.field,\n message: result.message || rule.message,\n severity: rule.severity || 'error',\n value: this.getFieldValue(entity, rule.field),\n rule: rule.name,\n };\n\n if (issue.severity === 'error') {\n errors.push(issue);\n if (this.config.failFast) break;\n } else {\n warnings.push(issue);\n }\n }\n } catch (error) {\n errors.push({\n field: rule.field,\n message: `Validation error: ${(error as Error).message}`,\n severity: 'error',\n rule: rule.name,\n });\n if (this.config.failFast) break;\n }\n }\n\n const isValid = errors.length === 0 && (!this.config.strict || warnings.length === 0);\n\n return {\n isValid,\n errors,\n warnings,\n entity,\n };\n }\n\n /**\n * Validate multiple entities.\n */\n async validateAll(entities: Entity[]): Promise<Map<string, EntityValidationResult>> {\n const results = new Map<string, EntityValidationResult>();\n\n for (const entity of entities) {\n const result = await this.validate(entity);\n results.set(entity.name, result);\n }\n\n return results;\n }\n\n /**\n * Validate an entity synchronously (only works if all rules are sync).\n */\n validateSync(entity: Entity): EntityValidationResult {\n const errors: EntityValidationIssue[] = [];\n const warnings: EntityValidationIssue[] = [];\n\n for (const rule of this.config.rules) {\n try {\n const result = rule.validate(entity);\n\n // Check if async (returns Promise)\n if (result instanceof Promise) {\n throw new Error(`Rule \"${rule.name}\" is async. Use validate() instead of validateSync()`);\n }\n\n if (!result.valid) {\n const issue: EntityValidationIssue = {\n field: rule.field,\n message: result.message || rule.message,\n severity: rule.severity || 'error',\n value: this.getFieldValue(entity, rule.field),\n rule: rule.name,\n };\n\n if (issue.severity === 'error') {\n errors.push(issue);\n if (this.config.failFast) break;\n } else {\n warnings.push(issue);\n }\n }\n } catch (error) {\n errors.push({\n field: rule.field,\n message: `Validation error: ${(error as Error).message}`,\n severity: 'error',\n rule: rule.name,\n });\n if (this.config.failFast) break;\n }\n }\n\n const isValid = errors.length === 0 && (!this.config.strict || warnings.length === 0);\n\n return {\n isValid,\n errors,\n warnings,\n entity,\n };\n }\n\n /**\n * Get a field value from entity using dot notation path.\n */\n private getFieldValue(entity: Entity, field: string): unknown {\n const parts = field.split('.');\n let value: unknown = entity;\n\n for (const part of parts) {\n if (value && typeof value === 'object') {\n // Handle array index\n if (/^\\d+$/.test(part)) {\n value = (value as unknown[])[parseInt(part, 10)];\n } else {\n value = (value as Record<string, unknown>)[part];\n }\n } else {\n return undefined;\n }\n }\n\n return value;\n }\n}\n","/**\n * Built-in Validators\n *\n * Factory functions for common validation rules.\n * Phase 1 Sprint 9: Entity Validation Helpers.\n *\n * @module utils/validators\n */\n\nimport type { Entity } from '../types/types.js';\nimport type { EntityValidationRule, EntityRuleResult } from './EntityValidator.js';\n\n// Helper to get nested field value\nfunction getField(entity: Entity, field: string): unknown {\n const parts = field.split('.');\n let value: unknown = entity;\n\n for (const part of parts) {\n if (value && typeof value === 'object') {\n if (/^\\d+$/.test(part)) {\n value = (value as unknown[])[parseInt(part, 10)];\n } else {\n value = (value as Record<string, unknown>)[part];\n }\n } else {\n return undefined;\n }\n }\n\n return value;\n}\n\n/**\n * Require a field to have a value.\n */\nexport function required(field: string, message?: string): EntityValidationRule {\n return {\n name: 'required',\n field,\n message: message ?? `${field} is required`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n const valid = value !== undefined && value !== null && value !== '';\n return { valid };\n },\n };\n}\n\n/**\n * Require a string field to have minimum length.\n */\nexport function minLength(field: string, min: number, message?: string): EntityValidationRule {\n return {\n name: 'minLength',\n field,\n message: message ?? `${field} must be at least ${min} characters`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'string') return { valid: true }; // Skip if not string\n return { valid: value.length >= min };\n },\n };\n}\n\n/**\n * Require a string field to have maximum length.\n */\nexport function maxLength(field: string, max: number, message?: string): EntityValidationRule {\n return {\n name: 'maxLength',\n field,\n message: message ?? `${field} must be at most ${max} characters`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'string') return { valid: true };\n return { valid: value.length <= max };\n },\n };\n}\n\n/**\n * Require a string field to match a pattern.\n */\nexport function pattern(\n field: string,\n regex: RegExp,\n description?: string,\n message?: string\n): EntityValidationRule {\n return {\n name: 'pattern',\n field,\n message: message ?? `${field} must match ${description || regex.toString()}`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'string') return { valid: true };\n return { valid: regex.test(value) };\n },\n };\n}\n\n/**\n * Require a numeric field to be within range.\n */\nexport function range(field: string, min: number, max: number, message?: string): EntityValidationRule {\n return {\n name: 'range',\n field,\n message: message ?? `${field} must be between ${min} and ${max}`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'number') return { valid: true };\n return { valid: value >= min && value <= max };\n },\n };\n}\n\n/**\n * Require a numeric field to be at least a minimum value.\n */\nexport function min(field: string, minValue: number, message?: string): EntityValidationRule {\n return {\n name: 'min',\n field,\n message: message ?? `${field} must be at least ${minValue}`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'number') return { valid: true };\n return { valid: value >= minValue };\n },\n };\n}\n\n/**\n * Require a numeric field to be at most a maximum value.\n */\nexport function max(field: string, maxValue: number, message?: string): EntityValidationRule {\n return {\n name: 'max',\n field,\n message: message ?? `${field} must be at most ${maxValue}`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'number') return { valid: true };\n return { valid: value <= maxValue };\n },\n };\n}\n\n/**\n * Require a field to be one of specified values.\n */\nexport function oneOf<T>(field: string, values: T[], message?: string): EntityValidationRule {\n return {\n name: 'oneOf',\n field,\n message: message ?? `${field} must be one of: ${values.join(', ')}`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n return { valid: values.includes(value as T) };\n },\n };\n}\n\n/**\n * Require array field to have minimum items.\n */\nexport function minItems(field: string, minCount: number, message?: string): EntityValidationRule {\n return {\n name: 'minItems',\n field,\n message: message ?? `${field} must have at least ${minCount} items`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (!Array.isArray(value)) return { valid: true };\n return { valid: value.length >= minCount };\n },\n };\n}\n\n/**\n * Require array field to have maximum items.\n */\nexport function maxItems(field: string, maxCount: number, message?: string): EntityValidationRule {\n return {\n name: 'maxItems',\n field,\n message: message ?? `${field} must have at most ${maxCount} items`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (!Array.isArray(value)) return { valid: true };\n return { valid: value.length <= maxCount };\n },\n };\n}\n\n/**\n * Require a field to be a valid email address.\n */\nexport function email(field: string, message?: string): EntityValidationRule {\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return {\n name: 'email',\n field,\n message: message ?? `${field} must be a valid email address`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'string') return { valid: true };\n return { valid: emailRegex.test(value) };\n },\n };\n}\n\n/**\n * Require a field to be a valid URL.\n */\nexport function url(field: string, message?: string): EntityValidationRule {\n return {\n name: 'url',\n field,\n message: message ?? `${field} must be a valid URL`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'string') return { valid: true };\n try {\n new URL(value);\n return { valid: true };\n } catch {\n return { valid: false };\n }\n },\n };\n}\n\n/**\n * Require a field to be a valid ISO 8601 date string.\n */\nexport function isoDate(field: string, message?: string): EntityValidationRule {\n return {\n name: 'isoDate',\n field,\n message: message ?? `${field} must be a valid ISO 8601 date`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (typeof value !== 'string') return { valid: true };\n const date = new Date(value);\n return { valid: !isNaN(date.getTime()) };\n },\n };\n}\n\n/**\n * Require a field to be of a specific type.\n */\nexport function typeOf(\n field: string,\n expectedType: 'string' | 'number' | 'boolean' | 'object' | 'array',\n message?: string\n): EntityValidationRule {\n return {\n name: 'typeOf',\n field,\n message: message ?? `${field} must be of type ${expectedType}`,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n if (value === undefined || value === null) return { valid: true };\n\n if (expectedType === 'array') {\n return { valid: Array.isArray(value) };\n }\n\n return { valid: typeof value === expectedType };\n },\n };\n}\n\n/**\n * Custom validator with user-provided function.\n */\nexport function custom(\n field: string,\n validator: (value: unknown, entity: Entity) => boolean | Promise<boolean>,\n message: string\n): EntityValidationRule {\n return {\n name: 'custom',\n field,\n message,\n validate: async (entity: Entity): Promise<EntityRuleResult> => {\n const value = getField(entity, field);\n const valid = await validator(value, entity);\n return { valid };\n },\n };\n}\n\n/**\n * Synchronous custom validator.\n */\nexport function customSync(\n field: string,\n validator: (value: unknown, entity: Entity) => boolean,\n message: string\n): EntityValidationRule {\n return {\n name: 'customSync',\n field,\n message,\n validate: (entity: Entity): EntityRuleResult => {\n const value = getField(entity, field);\n const valid = validator(value, entity);\n return { valid };\n },\n };\n}\n\n/**\n * Create a warning (non-blocking) version of a rule.\n */\nexport function asWarning(rule: EntityValidationRule): EntityValidationRule {\n return { ...rule, severity: 'warning' };\n}\n\n/**\n * Combine multiple rules for the same field.\n */\nexport function all(...rules: EntityValidationRule[]): EntityValidationRule[] {\n return rules;\n}\n\n/**\n * Conditionally apply a rule based on a predicate.\n */\nexport function when(\n predicate: (entity: Entity) => boolean,\n rule: EntityValidationRule\n): EntityValidationRule {\n return {\n ...rule,\n name: `conditional:${rule.name}`,\n validate: (entity: Entity): EntityRuleResult | Promise<EntityRuleResult> => {\n if (!predicate(entity)) {\n return { valid: true }; // Skip validation if predicate is false\n }\n return rule.validate(entity);\n },\n };\n}\n","/**\n * Schema Validator\n *\n * JSON Schema validation for entities using ajv.\n * Phase 1 Sprint 9: Entity Validation Helpers.\n *\n * @module utils/SchemaValidator\n */\n\nimport type { Entity } from '../types/types.js';\nimport type { EntityValidationResult, EntityValidationIssue } from './EntityValidator.js';\n\n/**\n * JSON Schema type definition (simplified).\n */\nexport interface JsonSchema {\n type?: string | string[];\n properties?: Record<string, JsonSchema>;\n required?: string[];\n additionalProperties?: boolean;\n minLength?: number;\n maxLength?: number;\n minimum?: number;\n maximum?: number;\n pattern?: string;\n enum?: unknown[];\n items?: JsonSchema;\n minItems?: number;\n maxItems?: number;\n format?: string;\n default?: unknown;\n description?: string;\n $ref?: string;\n allOf?: JsonSchema[];\n anyOf?: JsonSchema[];\n oneOf?: JsonSchema[];\n not?: JsonSchema;\n if?: JsonSchema;\n then?: JsonSchema;\n else?: JsonSchema;\n [key: string]: unknown;\n}\n\n/**\n * AJV error structure.\n */\ninterface AjvError {\n instancePath: string;\n message?: string;\n data?: unknown;\n keyword?: string;\n params?: {\n missingProperty?: string;\n additionalProperty?: string;\n limit?: number;\n pattern?: string;\n allowedValues?: unknown[];\n };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AjvInstance = any; // Dynamic import, type compatibility handled at runtime\n\n/**\n * AJV validate function interface.\n */\ninterface ValidateFunction {\n (data: unknown): boolean;\n errors?: AjvError[] | null;\n}\n\n/**\n * Validates entities against JSON Schemas.\n * Requires ajv to be installed as optional peer dependency.\n *\n * @example\n * ```typescript\n * const validator = new SchemaValidator();\n * await validator.initialize(); // Load ajv dynamically\n *\n * validator.registerSchema('person', {\n * type: 'object',\n * required: ['name', 'entityType'],\n * properties: {\n * name: { type: 'string', minLength: 1 },\n * entityType: { type: 'string', enum: ['person'] },\n * },\n * });\n *\n * const result = validator.validate(entity);\n * ```\n */\nexport class SchemaValidator {\n private schemas: Map<string, JsonSchema> = new Map();\n private ajv: AjvInstance | null = null;\n private validators: Map<string, ValidateFunction> = new Map();\n private initialized = false;\n\n /**\n * Initialize the validator by loading ajv dynamically.\n * Call this before using validate() if you want schema validation.\n */\n async initialize(): Promise<boolean> {\n if (this.initialized) return this.isAvailable();\n\n try {\n // Dynamic import to avoid bundling ajv if not used\n const AjvModule = await import('ajv');\n const Ajv = AjvModule.default;\n this.ajv = new Ajv({ allErrors: true, verbose: true });\n this.initialized = true;\n\n // Re-compile any schemas that were registered before initialization\n for (const [entityType, schema] of this.schemas) {\n this.compileSchema(entityType, schema);\n }\n\n return true;\n } catch {\n // ajv not installed\n this.initialized = true;\n return false;\n }\n }\n\n /**\n * Check if schema validation is available.\n */\n isAvailable(): boolean {\n return this.ajv !== null;\n }\n\n /**\n * Check if initialized.\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Register a schema for an entity type.\n */\n registerSchema(entityType: string, schema: JsonSchema): void {\n this.schemas.set(entityType, schema);\n\n if (this.ajv) {\n this.compileSchema(entityType, schema);\n }\n }\n\n /**\n * Compile a schema using ajv.\n */\n private compileSchema(entityType: string, schema: JsonSchema): void {\n if (!this.ajv) return;\n\n try {\n const validate = this.ajv.compile(schema);\n this.validators.set(entityType, validate);\n } catch (error) {\n console.warn(`Failed to compile schema for \"${entityType}\": ${(error as Error).message}`);\n }\n }\n\n /**\n * Unregister a schema.\n */\n unregisterSchema(entityType: string): void {\n this.schemas.delete(entityType);\n this.validators.delete(entityType);\n }\n\n /**\n * Validate an entity against its type's schema.\n */\n validate(entity: Entity): EntityValidationResult {\n const errors: EntityValidationIssue[] = [];\n const warnings: EntityValidationIssue[] = [];\n\n if (!this.initialized) {\n warnings.push({\n field: '_schema',\n message: 'SchemaValidator not initialized. Call initialize() first.',\n severity: 'warning',\n });\n return { isValid: true, errors, warnings, entity };\n }\n\n if (!this.ajv) {\n warnings.push({\n field: '_schema',\n message: 'Schema validation unavailable: ajv not installed. Run: npm install ajv',\n severity: 'warning',\n });\n return { isValid: true, errors, warnings, entity };\n }\n\n const validate = this.validators.get(entity.entityType);\n if (!validate) {\n // No schema for this type - consider valid\n return { isValid: true, errors, warnings, entity };\n }\n\n const valid = validate(entity);\n\n if (!valid && validate.errors) {\n for (const error of validate.errors) {\n const field = this.getFieldPath(error);\n const message = this.getErrorMessage(error);\n\n errors.push({\n field,\n message,\n severity: 'error',\n value: error.data,\n rule: error.keyword,\n suggestion: this.getSuggestion(error),\n });\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n entity,\n };\n }\n\n /**\n * Validate multiple entities.\n */\n validateAll(entities: Entity[]): Map<string, EntityValidationResult> {\n const results = new Map<string, EntityValidationResult>();\n\n for (const entity of entities) {\n results.set(entity.name, this.validate(entity));\n }\n\n return results;\n }\n\n /**\n * Get field path from AJV error.\n */\n private getFieldPath(error: AjvError): string {\n if (error.params?.missingProperty) {\n const basePath = error.instancePath.replace(/^\\//, '').replace(/\\//g, '.');\n return basePath ? `${basePath}.${error.params.missingProperty}` : error.params.missingProperty;\n }\n\n if (error.params?.additionalProperty) {\n const basePath = error.instancePath.replace(/^\\//, '').replace(/\\//g, '.');\n return basePath\n ? `${basePath}.${error.params.additionalProperty}`\n : error.params.additionalProperty;\n }\n\n return error.instancePath.replace(/^\\//, '').replace(/\\//g, '.') || 'root';\n }\n\n /**\n * Get human-readable error message.\n */\n private getErrorMessage(error: AjvError): string {\n if (error.message) {\n return error.message;\n }\n\n switch (error.keyword) {\n case 'required':\n return `Missing required property: ${error.params?.missingProperty}`;\n case 'type':\n return 'Invalid type';\n case 'minLength':\n return `Must be at least ${error.params?.limit} characters`;\n case 'maxLength':\n return `Must be at most ${error.params?.limit} characters`;\n case 'minimum':\n return `Must be >= ${error.params?.limit}`;\n case 'maximum':\n return `Must be <= ${error.params?.limit}`;\n case 'pattern':\n return `Must match pattern: ${error.params?.pattern}`;\n case 'enum':\n return `Must be one of: ${error.params?.allowedValues?.join(', ')}`;\n default:\n return 'Validation failed';\n }\n }\n\n /**\n * Get suggestion for fixing the error.\n */\n private getSuggestion(error: AjvError): string | undefined {\n switch (error.keyword) {\n case 'required':\n return `Add the required property \"${error.params?.missingProperty}\"`;\n case 'type':\n return 'Check the value type';\n case 'minLength':\n return `Provide at least ${error.params?.limit} characters`;\n case 'maxLength':\n return `Reduce to at most ${error.params?.limit} characters`;\n case 'minimum':\n return `Use a value >= ${error.params?.limit}`;\n case 'maximum':\n return `Use a value <= ${error.params?.limit}`;\n case 'additionalProperties':\n return `Remove the extra property \"${error.params?.additionalProperty}\"`;\n default:\n return undefined;\n }\n }\n\n /**\n * Get registered schema for entity type.\n */\n getSchema(entityType: string): JsonSchema | undefined {\n return this.schemas.get(entityType);\n }\n\n /**\n * Get all registered entity types.\n */\n getRegisteredTypes(): string[] {\n return Array.from(this.schemas.keys());\n }\n\n /**\n * Check if a schema is registered for an entity type.\n */\n hasSchema(entityType: string): boolean {\n return this.schemas.has(entityType);\n }\n}\n","/**\r\n * Utilities Module Barrel Export\r\n *\r\n * Centralizes all utility exports for convenient importing.\r\n * Consolidated from 17 files to 9 focused modules (Phase 5 cleanup).\r\n *\r\n * @module utils\r\n */\r\n\r\n// ==================== Error Types (Phase 1 Sprint 10 Enhanced) ====================\r\nexport {\r\n ErrorCode,\r\n KnowledgeGraphError,\r\n EntityNotFoundError,\r\n RelationNotFoundError,\r\n DuplicateEntityError,\r\n ValidationError,\r\n CycleDetectedError,\r\n InvalidImportanceError,\r\n FileOperationError,\r\n ImportError,\r\n ExportError,\r\n InsufficientEntitiesError,\r\n OperationCancelledError,\r\n type ErrorOptions,\r\n} from './errors.js';\r\n\r\n// ==================== Error Suggestions (Phase 1 Sprint 10) ====================\r\nexport { generateSuggestions, getQuickHint } from './errorSuggestions.js';\r\n\r\n// ==================== Constants ====================\r\nexport {\r\n FILE_EXTENSIONS,\r\n FILE_SUFFIXES,\r\n DEFAULT_FILE_NAMES,\r\n ENV_VARS,\r\n DEFAULT_BASE_DIR,\r\n LOG_PREFIXES,\r\n SIMILARITY_WEIGHTS,\r\n DEFAULT_DUPLICATE_THRESHOLD,\r\n SEARCH_LIMITS,\r\n IMPORTANCE_RANGE,\r\n GRAPH_LIMITS,\r\n QUERY_LIMITS,\r\n COMPRESSION_CONFIG,\r\n STREAMING_CONFIG,\r\n type CompressionQuality,\r\n} from './constants.js';\r\n\r\n// ==================== Compression Utilities ====================\r\nexport {\r\n compress,\r\n decompress,\r\n compressFile,\r\n decompressFile,\r\n compressToBase64,\r\n decompressFromBase64,\r\n hasBrotliExtension,\r\n getCompressionRatio,\r\n createMetadata,\r\n createUncompressedMetadata,\r\n type CompressionOptions,\r\n type CompressionResult,\r\n type CompressionMetadata,\r\n} from './compressionUtil.js';\r\n\r\n// ==================== Compressed Cache ====================\r\nexport {\r\n CompressedCache,\r\n type CompressedCacheOptions,\r\n type CompressedCacheStats,\r\n} from './compressedCache.js';\r\n\r\n// ==================== Logger ====================\r\nexport { logger } from './logger.js';\r\n\r\n// ==================== Search Algorithms ====================\r\nexport {\r\n levenshteinDistance,\r\n calculateTF,\r\n calculateIDF,\r\n calculateIDFFromTokenSets,\r\n calculateTFIDF,\r\n tokenize,\r\n} from './searchAlgorithms.js';\r\n\r\n// ==================== Indexes ====================\r\nexport {\r\n NameIndex,\r\n TypeIndex,\r\n LowercaseCache,\r\n RelationIndex,\r\n} from './indexes.js';\r\n\r\n// ==================== Search Cache ====================\r\nexport {\r\n SearchCache,\r\n searchCaches,\r\n clearAllSearchCaches,\r\n getAllCacheStats,\r\n cleanupAllCaches,\r\n type CacheStats,\r\n} from './searchCache.js';\r\n\r\n// ==================== Schemas and Validation ====================\r\n// Consolidated from: schemas.ts, validationHelper.ts, validationUtils.ts\r\nexport {\r\n // Zod schemas - Entity/Relation\r\n EntitySchema,\r\n CreateEntitySchema,\r\n UpdateEntitySchema,\r\n RelationSchema,\r\n CreateRelationSchema,\r\n SearchQuerySchema,\r\n DateRangeSchema,\r\n TagAliasSchema,\r\n ExportFormatSchema,\r\n BatchCreateEntitiesSchema,\r\n BatchCreateRelationsSchema,\r\n EntityNamesSchema,\r\n DeleteRelationsSchema,\r\n // Zod schemas - Observations\r\n AddObservationInputSchema,\r\n AddObservationsInputSchema,\r\n DeleteObservationInputSchema,\r\n DeleteObservationsInputSchema,\r\n // Zod schemas - Archive\r\n ArchiveCriteriaSchema,\r\n // Zod schemas - Saved Search\r\n SavedSearchInputSchema,\r\n SavedSearchUpdateSchema,\r\n // Zod schemas - Import/Export\r\n ImportFormatSchema,\r\n ExtendedExportFormatSchema,\r\n MergeStrategySchema,\r\n ExportFilterSchema,\r\n // Zod schemas - Search\r\n OptionalTagsSchema,\r\n OptionalEntityNamesSchema,\r\n // Schema types\r\n type EntityInput,\r\n type CreateEntityInput,\r\n type UpdateEntityInput,\r\n type RelationInput,\r\n type CreateRelationInput,\r\n type SearchQuery,\r\n type DateRange,\r\n type TagAliasInput,\r\n type AddObservationInput,\r\n type DeleteObservationInput,\r\n type ArchiveCriteriaInput,\r\n type SavedSearchInput,\r\n type SavedSearchUpdateInput,\r\n type ImportFormatInput,\r\n type ExtendedExportFormatInput,\r\n type MergeStrategyInput,\r\n type ExportFilterInput,\r\n // Validation result type\r\n type ValidationResult,\r\n // Zod helpers\r\n formatZodErrors,\r\n validateWithSchema,\r\n validateSafe,\r\n validateArrayWithSchema,\r\n // Manual validation functions\r\n validateEntity,\r\n validateRelation,\r\n validateImportance,\r\n validateTags,\r\n} from './schemas.js';\r\n\r\n// ==================== Formatters ====================\r\n// Consolidated from: responseFormatter.ts, paginationUtils.ts\r\nexport {\r\n // Response formatting\r\n formatToolResponse,\r\n formatTextResponse,\r\n formatRawResponse,\r\n formatErrorResponse,\r\n type ToolResponse,\r\n // Pagination utilities\r\n validatePagination,\r\n applyPagination,\r\n paginateArray,\r\n getPaginationMeta,\r\n type ValidatedPagination,\r\n} from './formatters.js';\r\n\r\n// ==================== Entity Utilities ====================\r\n// Consolidated from: entityUtils.ts, tagUtils.ts, dateUtils.ts, filterUtils.ts, pathUtils.ts\r\nexport {\r\n // Hash functions (Phase 12 Sprint 1)\r\n fnv1aHash,\r\n // Entity lookup\r\n findEntityByName,\r\n findEntitiesByNames,\r\n entityExists,\r\n getEntityIndex,\r\n removeEntityByName,\r\n getEntityNameSet,\r\n groupEntitiesByType,\r\n touchEntity,\r\n // Tag utilities\r\n normalizeTag,\r\n normalizeTags,\r\n hasMatchingTag,\r\n hasAllTags,\r\n filterByTags,\r\n addUniqueTags,\r\n removeTags,\r\n // Date utilities\r\n isWithinDateRange,\r\n parseDateRange,\r\n isValidISODate,\r\n getCurrentTimestamp,\r\n // Filter utilities\r\n isWithinImportanceRange,\r\n filterByImportance,\r\n filterByCreatedDate,\r\n filterByModifiedDate,\r\n filterByEntityType,\r\n entityPassesFilters,\r\n type CommonSearchFilters,\r\n // Path utilities\r\n validateFilePath,\r\n defaultMemoryPath,\r\n ensureMemoryFilePath,\r\n // Security utilities\r\n sanitizeObject,\r\n escapeCsvFormula,\r\n} from './entityUtils.js';\r\n\r\n// ==================== Parallel Utilities ====================\r\nexport {\r\n parallelMap,\r\n parallelFilter,\r\n getPoolStats,\r\n shutdownParallelUtils,\r\n} from './parallelUtils.js';\r\n\r\n// ==================== Task Scheduler ====================\r\nexport {\r\n // Types and Enums\r\n TaskPriority,\r\n TaskStatus,\r\n type Task,\r\n type TaskResult,\r\n type ProgressCallback,\r\n type TaskBatchOptions,\r\n type QueueStats,\r\n // Task Queue\r\n TaskQueue,\r\n // Batch Processing\r\n batchProcess,\r\n rateLimitedProcess,\r\n withRetry,\r\n // Rate Limiting\r\n debounce,\r\n throttle,\r\n} from './taskScheduler.js';\r\n\r\n// ==================== Operation Utilities (Phase 9B) ====================\r\nexport {\r\n checkCancellation,\r\n createProgressReporter,\r\n createProgress,\r\n executeWithPhases,\r\n processBatchesWithProgress,\r\n type PhaseDefinition,\r\n} from './operationUtils.js';\r\n\r\n// ==================== Worker Pool Manager (Phase 12 Sprint 2) ====================\r\nexport {\r\n WorkerPoolManager,\r\n getWorkerPoolManager,\r\n type WorkerPoolConfig,\r\n type ExtendedPoolStats,\r\n type PoolEventCallback,\r\n} from './WorkerPoolManager.js';\r\n\r\n// ==================== Batch Processor (Phase 12 Sprint 2) ====================\r\nexport {\r\n BatchProcessor,\r\n processBatch,\r\n processWithRetry,\r\n chunkArray,\r\n parallelLimit,\r\n mapParallel,\r\n filterParallel,\r\n type BatchProgress,\r\n type BatchProgressCallback,\r\n type BatchItemResult,\r\n type BatchProcessResult,\r\n type BatchProcessorOptions,\r\n} from './BatchProcessor.js';\r\n\r\n// ==================== Memory Monitor (Phase 12 Sprint 6) ====================\r\nexport {\r\n MemoryMonitor,\r\n globalMemoryMonitor,\r\n type ComponentMemoryUsage,\r\n type MemoryUsageStats,\r\n type MemoryThresholds,\r\n type MemoryAlert,\r\n type MemoryChangeCallback,\r\n} from './MemoryMonitor.js';\r\n\r\n// ==================== Relation Helpers (Phase 1 Sprint 4) ====================\r\nexport {\r\n isWeightedRelation,\r\n isTemporalRelation,\r\n isBidirectionalRelation,\r\n hasConfidence,\r\n isCurrentlyValid,\r\n RelationBuilder,\r\n} from './relationHelpers.js';\r\n\r\n// ==================== Relation Validation (Phase 1 Sprint 4) ====================\r\nexport {\r\n validateRelationMetadata,\r\n validateRelationsMetadata,\r\n allRelationsValidMetadata,\r\n type RelationValidationResult,\r\n type RelationValidationError,\r\n type RelationValidationWarning,\r\n} from './relationValidation.js';\r\n\r\n// ==================== Entity Validation (Phase 1 Sprint 9) ====================\r\nexport {\r\n EntityValidator,\r\n type EntityValidatorConfig,\r\n type EntityValidationRule,\r\n type EntityRuleResult,\r\n type EntityValidationIssue,\r\n type EntityValidationResult,\r\n} from './EntityValidator.js';\r\n\r\nexport {\r\n required,\r\n minLength,\r\n maxLength,\r\n pattern,\r\n range,\r\n min,\r\n max,\r\n oneOf,\r\n minItems,\r\n maxItems,\r\n email,\r\n url,\r\n isoDate,\r\n typeOf,\r\n custom,\r\n customSync,\r\n asWarning,\r\n all,\r\n when,\r\n} from './validators.js';\r\n\r\nexport { SchemaValidator, type JsonSchema } from './SchemaValidator.js';\r\n","/**\r\n * Streaming Export Module\r\n *\r\n * Provides streaming export functionality for large knowledge graphs to avoid\r\n * loading entire graphs into memory. Supports JSONL and CSV formats with\r\n * incremental writing to disk.\r\n *\r\n * @module features/StreamingExporter\r\n */\r\n\r\nimport { createWriteStream } from 'fs';\r\nimport type { Entity, ReadonlyKnowledgeGraph, LongRunningOperationOptions } from '../types/types.js';\r\nimport { checkCancellation, createProgressReporter, createProgress, validateFilePath } from '../utils/index.js';\r\n\r\n/**\r\n * Result summary from a streaming export operation.\r\n *\r\n * Provides statistics about the export including bytes written,\r\n * entity/relation counts, and duration.\r\n *\r\n * @example\r\n * ```typescript\r\n * const result: StreamResult = {\r\n * bytesWritten: 125000,\r\n * entitiesWritten: 150,\r\n * relationsWritten: 320,\r\n * durationMs: 1250\r\n * };\r\n * ```\r\n */\r\nexport interface StreamResult {\r\n /** Total bytes written to the output file */\r\n bytesWritten: number;\r\n\r\n /** Number of entities written */\r\n entitiesWritten: number;\r\n\r\n /** Number of relations written */\r\n relationsWritten: number;\r\n\r\n /** Duration of the export operation in milliseconds */\r\n durationMs: number;\r\n}\r\n\r\n/**\r\n * Streaming exporter for knowledge graphs.\r\n *\r\n * Provides memory-efficient export by streaming data directly to files\r\n * instead of building large strings in memory. Supports JSONL and CSV formats.\r\n *\r\n * @example\r\n * ```typescript\r\n * const exporter = new StreamingExporter('/path/to/output.jsonl');\r\n * const result = await exporter.streamJSONL(graph);\r\n * console.log(`Wrote ${result.bytesWritten} bytes in ${result.durationMs}ms`);\r\n * ```\r\n */\r\nexport class StreamingExporter {\r\n private readonly validatedFilePath: string;\r\n\r\n /**\r\n * Create a new streaming exporter.\r\n *\r\n * @param filePath - Path to the output file\r\n * @throws {FileOperationError} If path traversal is detected\r\n */\r\n constructor(filePath: string) {\r\n // Validate path to prevent path traversal attacks\r\n this.validatedFilePath = validateFilePath(filePath);\r\n }\r\n\r\n /**\r\n * Get the validated file path.\r\n */\r\n get filePath(): string {\r\n return this.validatedFilePath;\r\n }\r\n\r\n /**\r\n * Stream a knowledge graph to JSONL format.\r\n *\r\n * Each entity and relation is written as a separate JSON line.\r\n * Memory usage is constant regardless of graph size.\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.\r\n *\r\n * @param graph - The knowledge graph to export\r\n * @param options - Optional progress/cancellation options (Phase 9B)\r\n * @returns Promise resolving to export statistics\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n *\r\n * @example\r\n * ```typescript\r\n * const exporter = new StreamingExporter('export.jsonl');\r\n * const result = await exporter.streamJSONL(graph);\r\n * console.log(`Exported ${result.entitiesWritten} entities`);\r\n *\r\n * // With progress tracking (Phase 9B)\r\n * const result = await exporter.streamJSONL(graph, {\r\n * onProgress: (p) => console.log(`${p.percentage}% complete`),\r\n * });\r\n * ```\r\n */\r\n async streamJSONL(\r\n graph: ReadonlyKnowledgeGraph,\r\n options?: LongRunningOperationOptions\r\n ): Promise<StreamResult> {\r\n // Check for early cancellation\r\n checkCancellation(options?.signal, 'streamJSONL');\r\n\r\n // Setup progress reporter\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n const total = graph.entities.length + graph.relations.length;\r\n let processed = 0;\r\n reportProgress?.(createProgress(0, total, 'streamJSONL'));\r\n\r\n const start = Date.now();\r\n let bytesWritten = 0;\r\n let entitiesWritten = 0;\r\n let relationsWritten = 0;\r\n\r\n const writeStream = createWriteStream(this.filePath);\r\n\r\n // Write entities\r\n for (const entity of graph.entities) {\r\n // Check for cancellation periodically\r\n checkCancellation(options?.signal, 'streamJSONL');\r\n\r\n const line = JSON.stringify(entity) + '\\n';\r\n writeStream.write(line);\r\n bytesWritten += Buffer.byteLength(line, 'utf-8');\r\n entitiesWritten++;\r\n processed++;\r\n reportProgress?.(createProgress(processed, total, 'writing entities'));\r\n }\r\n\r\n // Write relations\r\n for (const relation of graph.relations) {\r\n // Check for cancellation periodically\r\n checkCancellation(options?.signal, 'streamJSONL');\r\n\r\n const line = JSON.stringify(relation) + '\\n';\r\n writeStream.write(line);\r\n bytesWritten += Buffer.byteLength(line, 'utf-8');\r\n relationsWritten++;\r\n processed++;\r\n reportProgress?.(createProgress(processed, total, 'writing relations'));\r\n }\r\n\r\n // Check for cancellation before finalizing\r\n checkCancellation(options?.signal, 'streamJSONL');\r\n\r\n // Wait for stream to finish\r\n await new Promise<void>((resolve, reject) => {\r\n writeStream.end(() => resolve());\r\n writeStream.on('error', reject);\r\n });\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(total, total, 'streamJSONL'));\r\n\r\n return {\r\n bytesWritten,\r\n entitiesWritten,\r\n relationsWritten,\r\n durationMs: Date.now() - start,\r\n };\r\n }\r\n\r\n /**\r\n * Stream a knowledge graph to CSV format.\r\n *\r\n * Exports entities as CSV rows with proper escaping for special characters.\r\n * Header row includes: name, type, observations, tags, importance, createdAt, lastModified.\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.\r\n *\r\n * @param graph - The knowledge graph to export\r\n * @param options - Optional progress/cancellation options (Phase 9B)\r\n * @returns Promise resolving to export statistics\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n *\r\n * @example\r\n * ```typescript\r\n * const exporter = new StreamingExporter('export.csv');\r\n * const result = await exporter.streamCSV(graph);\r\n * console.log(`Exported ${result.entitiesWritten} entities as CSV`);\r\n *\r\n * // With progress tracking (Phase 9B)\r\n * const result = await exporter.streamCSV(graph, {\r\n * onProgress: (p) => console.log(`${p.percentage}% complete`),\r\n * });\r\n * ```\r\n */\r\n async streamCSV(\r\n graph: ReadonlyKnowledgeGraph,\r\n options?: LongRunningOperationOptions\r\n ): Promise<StreamResult> {\r\n // Check for early cancellation\r\n checkCancellation(options?.signal, 'streamCSV');\r\n\r\n // Setup progress reporter\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n const total = graph.entities.length;\r\n let processed = 0;\r\n reportProgress?.(createProgress(0, total, 'streamCSV'));\r\n\r\n const start = Date.now();\r\n let bytesWritten = 0;\r\n let entitiesWritten = 0;\r\n const relationsWritten = 0; // CSV format doesn't export relations\r\n\r\n const writeStream = createWriteStream(this.filePath);\r\n\r\n // Write header\r\n const header = 'name,type,observations,tags,importance,createdAt,lastModified\\n';\r\n writeStream.write(header);\r\n bytesWritten += Buffer.byteLength(header, 'utf-8');\r\n\r\n // Write entity rows\r\n for (const entity of graph.entities) {\r\n // Check for cancellation periodically\r\n checkCancellation(options?.signal, 'streamCSV');\r\n\r\n const row = this.entityToCSVRow(entity) + '\\n';\r\n writeStream.write(row);\r\n bytesWritten += Buffer.byteLength(row, 'utf-8');\r\n entitiesWritten++;\r\n processed++;\r\n reportProgress?.(createProgress(processed, total, 'writing entities'));\r\n }\r\n\r\n // Check for cancellation before finalizing\r\n checkCancellation(options?.signal, 'streamCSV');\r\n\r\n // Wait for stream to finish\r\n await new Promise<void>((resolve, reject) => {\r\n writeStream.end(() => resolve());\r\n writeStream.on('error', reject);\r\n });\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(total, total, 'streamCSV'));\r\n\r\n return {\r\n bytesWritten,\r\n entitiesWritten,\r\n relationsWritten,\r\n durationMs: Date.now() - start,\r\n };\r\n }\r\n\r\n /**\r\n * Convert an entity to a CSV row with proper escaping.\r\n *\r\n * Escapes double quotes by doubling them and wraps fields in quotes.\r\n * Arrays (observations, tags) are joined with semicolons.\r\n *\r\n * @param entity - The entity to convert\r\n * @returns CSV row string (without trailing newline)\r\n *\r\n * @private\r\n */\r\n private entityToCSVRow(entity: Entity): string {\r\n const escape = (s: string) => `\"${s.replace(/\"/g, '\"\"')}\"`;\r\n\r\n return [\r\n escape(entity.name),\r\n escape(entity.entityType),\r\n escape(entity.observations.join('; ')),\r\n escape((entity.tags ?? []).join('; ')),\r\n entity.importance?.toString() ?? '',\r\n entity.createdAt ?? '',\r\n entity.lastModified ?? '',\r\n ].join(',');\r\n }\r\n\r\n}\r\n","/**\r\n * IO Manager\r\n *\r\n * Unified manager for import, export, and backup operations.\r\n * Consolidates BackupManager, ExportManager, and ImportManager (Sprint 11.4).\r\n *\r\n * @module features/IOManager\r\n */\r\n\r\nimport { promises as fs } from 'fs';\r\nimport { dirname, join } from 'path';\r\nimport type {\r\n Entity,\r\n Relation,\r\n KnowledgeGraph,\r\n ReadonlyKnowledgeGraph,\r\n ImportResult,\r\n BackupOptions,\r\n BackupResult,\r\n RestoreResult,\r\n ExportOptions,\r\n ExportResult,\r\n LongRunningOperationOptions,\r\n} from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport { FileOperationError } from '../utils/errors.js';\r\nimport {\r\n compress,\r\n decompress,\r\n hasBrotliExtension,\r\n COMPRESSION_CONFIG,\r\n STREAMING_CONFIG,\r\n checkCancellation,\r\n createProgressReporter,\r\n createProgress,\r\n validateFilePath,\r\n sanitizeObject,\r\n escapeCsvFormula,\r\n} from '../utils/index.js';\r\nimport { StreamingExporter, type StreamResult } from './StreamingExporter.js';\r\n\r\n// ============================================================\r\n// TYPE DEFINITIONS\r\n// ============================================================\r\n\r\n/**\r\n * Supported export formats.\r\n */\r\nexport type ExportFormat = 'json' | 'csv' | 'graphml' | 'gexf' | 'dot' | 'markdown' | 'mermaid';\r\n\r\n/**\r\n * Supported import formats.\r\n */\r\nexport type ImportFormat = 'json' | 'csv' | 'graphml';\r\n\r\n/**\r\n * Merge strategies for handling existing entities during import.\r\n */\r\nexport type MergeStrategy = 'replace' | 'skip' | 'merge' | 'fail';\r\n\r\n/**\r\n * Metadata stored with each backup.\r\n * Extended with compression information for Phase 3 Sprint 2.\r\n */\r\nexport interface BackupMetadata {\r\n /** Timestamp when backup was created (ISO 8601) */\r\n timestamp: string;\r\n /** Number of entities in the backup */\r\n entityCount: number;\r\n /** Number of relations in the backup */\r\n relationCount: number;\r\n /** File size in bytes (compressed size if compressed) */\r\n fileSize: number;\r\n /** Optional description/reason for backup */\r\n description?: string;\r\n /** Whether the backup is compressed (default: true for new backups) */\r\n compressed?: boolean;\r\n /** Original size before compression in bytes */\r\n originalSize?: number;\r\n /** Compression ratio achieved (compressedSize / originalSize) */\r\n compressionRatio?: number;\r\n /** Compression format used */\r\n compressionFormat?: 'brotli' | 'none';\r\n}\r\n\r\n/**\r\n * Information about a backup file.\r\n * Extended with compression details for Phase 3 Sprint 2.\r\n */\r\nexport interface BackupInfo {\r\n /** Backup file name */\r\n fileName: string;\r\n /** Full path to backup file */\r\n filePath: string;\r\n /** Backup metadata */\r\n metadata: BackupMetadata;\r\n /** Whether the backup is compressed */\r\n compressed: boolean;\r\n /** File size in bytes */\r\n size: number;\r\n}\r\n\r\n// ============================================================\r\n// IO MANAGER CLASS\r\n// ============================================================\r\n\r\n/**\r\n * Unified manager for import, export, and backup operations.\r\n *\r\n * Combines functionality from:\r\n * - ExportManager: Graph export to various formats\r\n * - ImportManager: Graph import from various formats\r\n * - BackupManager: Point-in-time backup and restore\r\n */\r\nexport class IOManager {\r\n private readonly backupDir: string;\r\n\r\n constructor(private storage: GraphStorage) {\r\n const filePath = this.storage.getFilePath();\r\n const dir = dirname(filePath);\r\n this.backupDir = join(dir, '.backups');\r\n }\r\n\r\n // ============================================================\r\n // EXPORT OPERATIONS\r\n // ============================================================\r\n\r\n /**\r\n * Export graph to specified format.\r\n *\r\n * @param graph - Knowledge graph to export\r\n * @param format - Export format\r\n * @returns Formatted export string\r\n */\r\n exportGraph(graph: ReadonlyKnowledgeGraph, format: ExportFormat): string {\r\n switch (format) {\r\n case 'json':\r\n return this.exportAsJson(graph);\r\n case 'csv':\r\n return this.exportAsCsv(graph);\r\n case 'graphml':\r\n return this.exportAsGraphML(graph);\r\n case 'gexf':\r\n return this.exportAsGEXF(graph);\r\n case 'dot':\r\n return this.exportAsDOT(graph);\r\n case 'markdown':\r\n return this.exportAsMarkdown(graph);\r\n case 'mermaid':\r\n return this.exportAsMermaid(graph);\r\n default:\r\n throw new Error(`Unsupported export format: ${format}`);\r\n }\r\n }\r\n\r\n /**\r\n * Export graph with optional brotli compression.\r\n *\r\n * Compression is applied when:\r\n * - `options.compress` is explicitly set to `true`\r\n * - The exported content exceeds 100KB (auto-compress threshold)\r\n *\r\n * Compressed content is returned as base64-encoded string.\r\n * Uncompressed content is returned as UTF-8 string.\r\n *\r\n * @param graph - Knowledge graph to export\r\n * @param format - Export format\r\n * @param options - Export options including compression settings\r\n * @returns Export result with content and compression metadata\r\n *\r\n * @example\r\n * ```typescript\r\n * // Export with explicit compression\r\n * const result = await manager.exportGraphWithCompression(graph, 'json', {\r\n * compress: true,\r\n * compressionQuality: 11\r\n * });\r\n *\r\n * // Export with auto-compression for large graphs\r\n * const result = await manager.exportGraphWithCompression(graph, 'json');\r\n * // Compresses automatically if content > 100KB\r\n * ```\r\n */\r\n async exportGraphWithCompression(\r\n graph: ReadonlyKnowledgeGraph,\r\n format: ExportFormat,\r\n options?: ExportOptions\r\n ): Promise<ExportResult> {\r\n // Check if streaming should be used\r\n const shouldStream = options?.streaming ||\r\n (options?.outputPath && graph.entities.length >= STREAMING_CONFIG.STREAMING_THRESHOLD);\r\n\r\n if (shouldStream && options?.outputPath) {\r\n return this.streamExport(format, graph, options as ExportOptions & { outputPath: string });\r\n }\r\n\r\n // Generate export content using existing method\r\n const content = this.exportGraph(graph, format);\r\n const originalSize = Buffer.byteLength(content, 'utf-8');\r\n\r\n // Determine if compression should be applied\r\n const shouldCompress =\r\n options?.compress === true ||\r\n (options?.compress !== false &&\r\n originalSize > COMPRESSION_CONFIG.AUTO_COMPRESS_EXPORT_SIZE);\r\n\r\n if (shouldCompress) {\r\n const quality =\r\n options?.compressionQuality ?? COMPRESSION_CONFIG.BROTLI_QUALITY_BATCH;\r\n\r\n const compressionResult = await compress(content, {\r\n quality,\r\n mode: 'text',\r\n });\r\n\r\n return {\r\n format,\r\n content: compressionResult.compressed.toString('base64'),\r\n entityCount: graph.entities.length,\r\n relationCount: graph.relations.length,\r\n compressed: true,\r\n encoding: 'base64',\r\n originalSize,\r\n compressedSize: compressionResult.compressedSize,\r\n compressionRatio: compressionResult.ratio,\r\n };\r\n }\r\n\r\n // Return uncompressed content\r\n return {\r\n format,\r\n content,\r\n entityCount: graph.entities.length,\r\n relationCount: graph.relations.length,\r\n compressed: false,\r\n encoding: 'utf-8',\r\n originalSize,\r\n compressedSize: originalSize,\r\n compressionRatio: 1,\r\n };\r\n }\r\n\r\n /**\r\n * Stream export to a file for large graphs.\r\n *\r\n * Uses StreamingExporter to write entities and relations incrementally\r\n * to avoid loading the entire export content into memory.\r\n *\r\n * @param format - Export format\r\n * @param graph - Knowledge graph to export\r\n * @param options - Export options with required outputPath\r\n * @returns Export result with streaming metadata\r\n * @private\r\n */\r\n private async streamExport(\r\n format: ExportFormat,\r\n graph: ReadonlyKnowledgeGraph,\r\n options: ExportOptions & { outputPath: string }\r\n ): Promise<ExportResult> {\r\n // Validate path to prevent path traversal attacks (defense in depth)\r\n const validatedOutputPath = validateFilePath(options.outputPath);\r\n const exporter = new StreamingExporter(validatedOutputPath);\r\n let result: StreamResult;\r\n\r\n switch (format) {\r\n case 'json':\r\n // Use JSONL format for streaming (line-delimited JSON)\r\n result = await exporter.streamJSONL(graph);\r\n break;\r\n case 'csv':\r\n result = await exporter.streamCSV(graph);\r\n break;\r\n default:\r\n // Fallback to in-memory export for unsupported streaming formats\r\n const content = this.exportGraph(graph, format);\r\n await fs.writeFile(validatedOutputPath, content);\r\n result = {\r\n bytesWritten: Buffer.byteLength(content, 'utf-8'),\r\n entitiesWritten: graph.entities.length,\r\n relationsWritten: graph.relations.length,\r\n durationMs: 0,\r\n };\r\n }\r\n\r\n return {\r\n format,\r\n content: `Streamed to ${validatedOutputPath}`,\r\n entityCount: result.entitiesWritten,\r\n relationCount: result.relationsWritten,\r\n compressed: false,\r\n encoding: 'utf-8',\r\n originalSize: result.bytesWritten,\r\n compressedSize: result.bytesWritten,\r\n compressionRatio: 1,\r\n streamed: true,\r\n outputPath: validatedOutputPath,\r\n };\r\n }\r\n\r\n private exportAsJson(graph: ReadonlyKnowledgeGraph): string {\r\n return JSON.stringify(graph, null, 2);\r\n }\r\n\r\n private exportAsCsv(graph: ReadonlyKnowledgeGraph): string {\r\n const lines: string[] = [];\r\n\r\n const escapeCsvField = (field: string | undefined | null): string => {\r\n if (field === undefined || field === null) return '';\r\n // First protect against CSV formula injection\r\n let str = escapeCsvFormula(String(field));\r\n // Then handle CSV special characters\r\n if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\r\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\r\n }\r\n return str;\r\n };\r\n\r\n lines.push('# ENTITIES');\r\n lines.push('name,entityType,observations,createdAt,lastModified,tags,importance');\r\n\r\n for (const entity of graph.entities) {\r\n const observationsStr = entity.observations.join('; ');\r\n const tagsStr = entity.tags ? entity.tags.join('; ') : '';\r\n const importanceStr = entity.importance !== undefined ? String(entity.importance) : '';\r\n\r\n lines.push(\r\n [\r\n escapeCsvField(entity.name),\r\n escapeCsvField(entity.entityType),\r\n escapeCsvField(observationsStr),\r\n escapeCsvField(entity.createdAt),\r\n escapeCsvField(entity.lastModified),\r\n escapeCsvField(tagsStr),\r\n escapeCsvField(importanceStr),\r\n ].join(',')\r\n );\r\n }\r\n\r\n lines.push('');\r\n lines.push('# RELATIONS');\r\n lines.push('from,to,relationType,createdAt,lastModified');\r\n\r\n for (const relation of graph.relations) {\r\n lines.push(\r\n [\r\n escapeCsvField(relation.from),\r\n escapeCsvField(relation.to),\r\n escapeCsvField(relation.relationType),\r\n escapeCsvField(relation.createdAt),\r\n escapeCsvField(relation.lastModified),\r\n ].join(',')\r\n );\r\n }\r\n\r\n return lines.join('\\n');\r\n }\r\n\r\n private exportAsGraphML(graph: ReadonlyKnowledgeGraph): string {\r\n const lines: string[] = [];\r\n\r\n const escapeXml = (str: string | undefined | null): string => {\r\n if (str === undefined || str === null) return '';\r\n return String(str)\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&apos;');\r\n };\r\n\r\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\r\n lines.push('<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">');\r\n lines.push(' <key id=\"d0\" for=\"node\" attr.name=\"entityType\" attr.type=\"string\"/>');\r\n lines.push(' <key id=\"d1\" for=\"node\" attr.name=\"observations\" attr.type=\"string\"/>');\r\n lines.push(' <key id=\"d2\" for=\"node\" attr.name=\"createdAt\" attr.type=\"string\"/>');\r\n lines.push(' <key id=\"d3\" for=\"node\" attr.name=\"lastModified\" attr.type=\"string\"/>');\r\n lines.push(' <key id=\"d4\" for=\"node\" attr.name=\"tags\" attr.type=\"string\"/>');\r\n lines.push(' <key id=\"d5\" for=\"node\" attr.name=\"importance\" attr.type=\"double\"/>');\r\n lines.push(' <key id=\"e0\" for=\"edge\" attr.name=\"relationType\" attr.type=\"string\"/>');\r\n lines.push(' <key id=\"e1\" for=\"edge\" attr.name=\"createdAt\" attr.type=\"string\"/>');\r\n lines.push(' <key id=\"e2\" for=\"edge\" attr.name=\"lastModified\" attr.type=\"string\"/>');\r\n lines.push(' <graph id=\"G\" edgedefault=\"directed\">');\r\n\r\n for (const entity of graph.entities) {\r\n const nodeId = escapeXml(entity.name);\r\n lines.push(` <node id=\"${nodeId}\">`);\r\n lines.push(` <data key=\"d0\">${escapeXml(entity.entityType)}</data>`);\r\n lines.push(` <data key=\"d1\">${escapeXml(entity.observations.join('; '))}</data>`);\r\n if (entity.createdAt) lines.push(` <data key=\"d2\">${escapeXml(entity.createdAt)}</data>`);\r\n if (entity.lastModified) lines.push(` <data key=\"d3\">${escapeXml(entity.lastModified)}</data>`);\r\n if (entity.tags?.length) lines.push(` <data key=\"d4\">${escapeXml(entity.tags.join('; '))}</data>`);\r\n if (entity.importance !== undefined) lines.push(` <data key=\"d5\">${entity.importance}</data>`);\r\n lines.push(' </node>');\r\n }\r\n\r\n let edgeId = 0;\r\n for (const relation of graph.relations) {\r\n const sourceId = escapeXml(relation.from);\r\n const targetId = escapeXml(relation.to);\r\n lines.push(` <edge id=\"e${edgeId}\" source=\"${sourceId}\" target=\"${targetId}\">`);\r\n lines.push(` <data key=\"e0\">${escapeXml(relation.relationType)}</data>`);\r\n if (relation.createdAt) lines.push(` <data key=\"e1\">${escapeXml(relation.createdAt)}</data>`);\r\n if (relation.lastModified) lines.push(` <data key=\"e2\">${escapeXml(relation.lastModified)}</data>`);\r\n lines.push(' </edge>');\r\n edgeId++;\r\n }\r\n\r\n lines.push(' </graph>');\r\n lines.push('</graphml>');\r\n return lines.join('\\n');\r\n }\r\n\r\n private exportAsGEXF(graph: ReadonlyKnowledgeGraph): string {\r\n const lines: string[] = [];\r\n\r\n const escapeXml = (str: string | undefined | null): string => {\r\n if (str === undefined || str === null) return '';\r\n return String(str)\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&apos;');\r\n };\r\n\r\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\r\n lines.push('<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">');\r\n lines.push(' <meta>');\r\n lines.push(' <creator>Memory MCP Server</creator>');\r\n lines.push(' </meta>');\r\n lines.push(' <graph mode=\"static\" defaultedgetype=\"directed\">');\r\n lines.push(' <attributes class=\"node\">');\r\n lines.push(' <attribute id=\"0\" title=\"entityType\" type=\"string\"/>');\r\n lines.push(' <attribute id=\"1\" title=\"observations\" type=\"string\"/>');\r\n lines.push(' </attributes>');\r\n lines.push(' <nodes>');\r\n\r\n for (const entity of graph.entities) {\r\n const nodeId = escapeXml(entity.name);\r\n lines.push(` <node id=\"${nodeId}\" label=\"${nodeId}\">`);\r\n lines.push(' <attvalues>');\r\n lines.push(` <attvalue for=\"0\" value=\"${escapeXml(entity.entityType)}\"/>`);\r\n lines.push(` <attvalue for=\"1\" value=\"${escapeXml(entity.observations.join('; '))}\"/>`);\r\n lines.push(' </attvalues>');\r\n lines.push(' </node>');\r\n }\r\n\r\n lines.push(' </nodes>');\r\n lines.push(' <edges>');\r\n\r\n let edgeId = 0;\r\n for (const relation of graph.relations) {\r\n const sourceId = escapeXml(relation.from);\r\n const targetId = escapeXml(relation.to);\r\n const label = escapeXml(relation.relationType);\r\n lines.push(` <edge id=\"${edgeId}\" source=\"${sourceId}\" target=\"${targetId}\" label=\"${label}\"/>`);\r\n edgeId++;\r\n }\r\n\r\n lines.push(' </edges>');\r\n lines.push(' </graph>');\r\n lines.push('</gexf>');\r\n return lines.join('\\n');\r\n }\r\n\r\n private exportAsDOT(graph: ReadonlyKnowledgeGraph): string {\r\n const lines: string[] = [];\r\n\r\n const escapeDot = (str: string): string => {\r\n return '\"' + str.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n') + '\"';\r\n };\r\n\r\n lines.push('digraph KnowledgeGraph {');\r\n lines.push(' rankdir=LR;');\r\n lines.push(' node [shape=box, style=rounded];');\r\n lines.push('');\r\n\r\n for (const entity of graph.entities) {\r\n const nodeId = escapeDot(entity.name);\r\n const label = [`${entity.name}`, `Type: ${entity.entityType}`];\r\n if (entity.tags?.length) label.push(`Tags: ${entity.tags.join(', ')}`);\r\n const labelStr = escapeDot(label.join('\\\\n'));\r\n lines.push(` ${nodeId} [label=${labelStr}];`);\r\n }\r\n\r\n lines.push('');\r\n\r\n for (const relation of graph.relations) {\r\n const fromId = escapeDot(relation.from);\r\n const toId = escapeDot(relation.to);\r\n const label = escapeDot(relation.relationType);\r\n lines.push(` ${fromId} -> ${toId} [label=${label}];`);\r\n }\r\n\r\n lines.push('}');\r\n return lines.join('\\n');\r\n }\r\n\r\n private exportAsMarkdown(graph: ReadonlyKnowledgeGraph): string {\r\n const lines: string[] = [];\r\n\r\n lines.push('# Knowledge Graph Export');\r\n lines.push('');\r\n lines.push(`**Exported:** ${new Date().toISOString()}`);\r\n lines.push(`**Entities:** ${graph.entities.length}`);\r\n lines.push(`**Relations:** ${graph.relations.length}`);\r\n lines.push('');\r\n lines.push('## Entities');\r\n lines.push('');\r\n\r\n for (const entity of graph.entities) {\r\n lines.push(`### ${entity.name}`);\r\n lines.push('');\r\n lines.push(`- **Type:** ${entity.entityType}`);\r\n if (entity.tags?.length) lines.push(`- **Tags:** ${entity.tags.map(t => `\\`${t}\\``).join(', ')}`);\r\n if (entity.importance !== undefined) lines.push(`- **Importance:** ${entity.importance}/10`);\r\n if (entity.observations.length > 0) {\r\n lines.push('');\r\n lines.push('**Observations:**');\r\n for (const obs of entity.observations) {\r\n lines.push(`- ${obs}`);\r\n }\r\n }\r\n lines.push('');\r\n }\r\n\r\n if (graph.relations.length > 0) {\r\n lines.push('## Relations');\r\n lines.push('');\r\n for (const relation of graph.relations) {\r\n lines.push(`- **${relation.from}** → *${relation.relationType}* → **${relation.to}**`);\r\n }\r\n lines.push('');\r\n }\r\n\r\n return lines.join('\\n');\r\n }\r\n\r\n private exportAsMermaid(graph: ReadonlyKnowledgeGraph): string {\r\n const lines: string[] = [];\r\n\r\n const sanitizeId = (str: string): string => str.replace(/[^a-zA-Z0-9_]/g, '_');\r\n const escapeLabel = (str: string): string => str.replace(/\"/g, '#quot;');\r\n\r\n lines.push('graph LR');\r\n lines.push(' %% Knowledge Graph');\r\n lines.push('');\r\n\r\n const nodeIds = new Map<string, string>();\r\n for (const entity of graph.entities) {\r\n nodeIds.set(entity.name, sanitizeId(entity.name));\r\n }\r\n\r\n for (const entity of graph.entities) {\r\n const nodeId = nodeIds.get(entity.name)!;\r\n const labelParts: string[] = [entity.name, `Type: ${entity.entityType}`];\r\n if (entity.tags?.length) labelParts.push(`Tags: ${entity.tags.join(', ')}`);\r\n const label = escapeLabel(labelParts.join('<br/>'));\r\n lines.push(` ${nodeId}[\"${label}\"]`);\r\n }\r\n\r\n lines.push('');\r\n\r\n for (const relation of graph.relations) {\r\n const fromId = nodeIds.get(relation.from);\r\n const toId = nodeIds.get(relation.to);\r\n if (fromId && toId) {\r\n const label = escapeLabel(relation.relationType);\r\n lines.push(` ${fromId} -->|\"${label}\"| ${toId}`);\r\n }\r\n }\r\n\r\n return lines.join('\\n');\r\n }\r\n\r\n // ============================================================\r\n // IMPORT OPERATIONS\r\n // ============================================================\r\n\r\n /**\r\n * Import graph from formatted data.\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.\r\n *\r\n * @param format - Import format\r\n * @param data - Import data string\r\n * @param mergeStrategy - How to handle conflicts\r\n * @param dryRun - If true, preview changes without applying\r\n * @param options - Optional progress/cancellation options (Phase 9B)\r\n * @returns Import result with statistics\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n */\r\n async importGraph(\r\n format: ImportFormat,\r\n data: string,\r\n mergeStrategy: MergeStrategy = 'skip',\r\n dryRun: boolean = false,\r\n options?: LongRunningOperationOptions\r\n ): Promise<ImportResult> {\r\n // Check for early cancellation\r\n checkCancellation(options?.signal, 'importGraph');\r\n\r\n // Setup progress reporter\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n reportProgress?.(createProgress(0, 100, 'importGraph'));\r\n\r\n let importedGraph: KnowledgeGraph;\r\n\r\n try {\r\n // Parsing phase (0-20% progress)\r\n reportProgress?.(createProgress(5, 100, 'parsing data'));\r\n checkCancellation(options?.signal, 'importGraph');\r\n\r\n switch (format) {\r\n case 'json':\r\n importedGraph = this.parseJsonImport(data);\r\n break;\r\n case 'csv':\r\n importedGraph = this.parseCsvImport(data);\r\n break;\r\n case 'graphml':\r\n importedGraph = this.parseGraphMLImport(data);\r\n break;\r\n default:\r\n throw new Error(`Unsupported import format: ${format}`);\r\n }\r\n\r\n reportProgress?.(createProgress(20, 100, 'parsing complete'));\r\n } catch (error) {\r\n return {\r\n entitiesAdded: 0,\r\n entitiesSkipped: 0,\r\n entitiesUpdated: 0,\r\n relationsAdded: 0,\r\n relationsSkipped: 0,\r\n errors: [`Failed to parse ${format} data: ${error instanceof Error ? error.message : String(error)}`],\r\n };\r\n }\r\n\r\n // Merging phase (20-100% progress)\r\n return await this.mergeImportedGraph(importedGraph, mergeStrategy, dryRun, options);\r\n }\r\n\r\n private parseJsonImport(data: string): KnowledgeGraph {\r\n // Security: Limit input size to prevent DoS (10MB max)\r\n const MAX_IMPORT_SIZE = 10 * 1024 * 1024;\r\n if (data.length > MAX_IMPORT_SIZE) {\r\n throw new FileOperationError(\r\n `JSON import data exceeds maximum size of ${MAX_IMPORT_SIZE / (1024 * 1024)}MB`,\r\n 'json-import'\r\n );\r\n }\r\n\r\n const parsed = JSON.parse(data);\r\n\r\n if (!parsed.entities || !Array.isArray(parsed.entities)) {\r\n throw new Error('Invalid JSON: missing or invalid entities array');\r\n }\r\n if (!parsed.relations || !Array.isArray(parsed.relations)) {\r\n throw new Error('Invalid JSON: missing or invalid relations array');\r\n }\r\n\r\n // Security: Limit maximum number of entities/relations\r\n const MAX_ITEMS = 100000;\r\n if (parsed.entities.length > MAX_ITEMS) {\r\n throw new FileOperationError(\r\n `JSON import exceeds maximum entity count of ${MAX_ITEMS}`,\r\n 'json-import'\r\n );\r\n }\r\n if (parsed.relations.length > MAX_ITEMS) {\r\n throw new FileOperationError(\r\n `JSON import exceeds maximum relation count of ${MAX_ITEMS}`,\r\n 'json-import'\r\n );\r\n }\r\n\r\n return {\r\n entities: parsed.entities as Entity[],\r\n relations: parsed.relations as Relation[],\r\n };\r\n }\r\n\r\n private parseCsvImport(data: string): KnowledgeGraph {\r\n // Security: Limit input size to prevent DoS (10MB max)\r\n const MAX_IMPORT_SIZE = 10 * 1024 * 1024;\r\n if (data.length > MAX_IMPORT_SIZE) {\r\n throw new FileOperationError(\r\n `CSV import data exceeds maximum size of ${MAX_IMPORT_SIZE / (1024 * 1024)}MB`,\r\n 'csv-import'\r\n );\r\n }\r\n\r\n // Security: Limit maximum number of entities/relations\r\n const MAX_ITEMS = 100000;\r\n\r\n const lines = data\r\n .split('\\n')\r\n .map(line => line.trim())\r\n .filter(line => line);\r\n const entities: Entity[] = [];\r\n const relations: Relation[] = [];\r\n\r\n let section: 'entities' | 'relations' | null = null;\r\n let headerParsed = false;\r\n\r\n const parseCsvLine = (line: string): string[] => {\r\n const fields: string[] = [];\r\n let current = '';\r\n let inQuotes = false;\r\n\r\n for (let i = 0; i < line.length; i++) {\r\n const char = line[i];\r\n\r\n if (char === '\"') {\r\n if (inQuotes && line[i + 1] === '\"') {\r\n current += '\"';\r\n i++;\r\n } else {\r\n inQuotes = !inQuotes;\r\n }\r\n } else if (char === ',' && !inQuotes) {\r\n fields.push(current);\r\n current = '';\r\n } else {\r\n current += char;\r\n }\r\n }\r\n\r\n fields.push(current);\r\n return fields;\r\n };\r\n\r\n for (const line of lines) {\r\n if (line.startsWith('# ENTITIES')) {\r\n section = 'entities';\r\n headerParsed = false;\r\n continue;\r\n } else if (line.startsWith('# RELATIONS')) {\r\n section = 'relations';\r\n headerParsed = false;\r\n continue;\r\n }\r\n\r\n if (line.startsWith('#')) continue;\r\n\r\n if (section === 'entities') {\r\n if (!headerParsed) {\r\n headerParsed = true;\r\n continue;\r\n }\r\n\r\n const fields = parseCsvLine(line);\r\n if (fields.length >= 2) {\r\n // Security: Check entity limit\r\n if (entities.length >= MAX_ITEMS) {\r\n throw new FileOperationError(\r\n `CSV import exceeds maximum entity count of ${MAX_ITEMS}`,\r\n 'csv-import'\r\n );\r\n }\r\n const entity: Entity = {\r\n name: fields[0],\r\n entityType: fields[1],\r\n observations: fields[2]\r\n ? fields[2]\r\n .split(';')\r\n .map(s => s.trim())\r\n .filter(s => s)\r\n : [],\r\n createdAt: fields[3] || undefined,\r\n lastModified: fields[4] || undefined,\r\n tags: fields[5]\r\n ? fields[5]\r\n .split(';')\r\n .map(s => s.trim().toLowerCase())\r\n .filter(s => s)\r\n : undefined,\r\n importance: fields[6] ? parseFloat(fields[6]) : undefined,\r\n };\r\n entities.push(entity);\r\n }\r\n } else if (section === 'relations') {\r\n if (!headerParsed) {\r\n headerParsed = true;\r\n continue;\r\n }\r\n\r\n const fields = parseCsvLine(line);\r\n if (fields.length >= 3) {\r\n // Security: Check relation limit\r\n if (relations.length >= MAX_ITEMS) {\r\n throw new FileOperationError(\r\n `CSV import exceeds maximum relation count of ${MAX_ITEMS}`,\r\n 'csv-import'\r\n );\r\n }\r\n const relation: Relation = {\r\n from: fields[0],\r\n to: fields[1],\r\n relationType: fields[2],\r\n createdAt: fields[3] || undefined,\r\n lastModified: fields[4] || undefined,\r\n };\r\n relations.push(relation);\r\n }\r\n }\r\n }\r\n\r\n return { entities, relations };\r\n }\r\n\r\n private parseGraphMLImport(data: string): KnowledgeGraph {\r\n const entities: Entity[] = [];\r\n const relations: Relation[] = [];\r\n\r\n // Security: Limit input size to prevent ReDoS attacks (10MB max)\r\n const MAX_IMPORT_SIZE = 10 * 1024 * 1024;\r\n if (data.length > MAX_IMPORT_SIZE) {\r\n throw new FileOperationError(\r\n `GraphML import data exceeds maximum size of ${MAX_IMPORT_SIZE / (1024 * 1024)}MB`,\r\n 'graphml-import'\r\n );\r\n }\r\n\r\n // Security: Limit maximum number of entities/relations to prevent infinite loops\r\n const MAX_ITEMS = 100000;\r\n let nodeCount = 0;\r\n let relationCount = 0;\r\n\r\n // Use non-greedy patterns with character class restrictions\r\n const nodeRegex = /<node\\s+id=\"([^\"]+)\"[^>]*>([\\s\\S]*?)<\\/node>/g;\r\n let nodeMatch;\r\n\r\n while ((nodeMatch = nodeRegex.exec(data)) !== null) {\r\n // Security: Limit iterations to prevent ReDoS\r\n if (++nodeCount > MAX_ITEMS) {\r\n throw new FileOperationError(\r\n `GraphML import exceeds maximum entity count of ${MAX_ITEMS}`,\r\n 'graphml-import'\r\n );\r\n }\r\n const nodeId = nodeMatch[1];\r\n const nodeContent = nodeMatch[2];\r\n\r\n const getDataValue = (key: string): string | undefined => {\r\n const dataRegex = new RegExp(`<data\\\\s+key=\"${key}\">([^<]*)<\\/data>`);\r\n const match = dataRegex.exec(nodeContent);\r\n return match ? match[1] : undefined;\r\n };\r\n\r\n const entity: Entity = {\r\n name: nodeId,\r\n entityType: getDataValue('d0') || getDataValue('entityType') || 'unknown',\r\n observations: (getDataValue('d1') || getDataValue('observations') || '')\r\n .split(';')\r\n .map(s => s.trim())\r\n .filter(s => s),\r\n createdAt: getDataValue('d2') || getDataValue('createdAt'),\r\n lastModified: getDataValue('d3') || getDataValue('lastModified'),\r\n tags: (getDataValue('d4') || getDataValue('tags') || '')\r\n .split(';')\r\n .map(s => s.trim().toLowerCase())\r\n .filter(s => s),\r\n importance: getDataValue('d5') || getDataValue('importance') ? parseFloat(getDataValue('d5') || getDataValue('importance') || '0') : undefined,\r\n };\r\n\r\n entities.push(entity);\r\n }\r\n\r\n const edgeRegex = /<edge\\s+[^>]*source=\"([^\"]+)\"\\s+target=\"([^\"]+)\"[^>]*>([\\s\\S]*?)<\\/edge>/g;\r\n let edgeMatch;\r\n\r\n while ((edgeMatch = edgeRegex.exec(data)) !== null) {\r\n // Security: Limit iterations to prevent ReDoS\r\n if (++relationCount > MAX_ITEMS) {\r\n throw new FileOperationError(\r\n `GraphML import exceeds maximum relation count of ${MAX_ITEMS}`,\r\n 'graphml-import'\r\n );\r\n }\r\n const source = edgeMatch[1];\r\n const target = edgeMatch[2];\r\n const edgeContent = edgeMatch[3];\r\n\r\n const getDataValue = (key: string): string | undefined => {\r\n const dataRegex = new RegExp(`<data\\\\s+key=\"${key}\">([^<]*)<\\/data>`);\r\n const match = dataRegex.exec(edgeContent);\r\n return match ? match[1] : undefined;\r\n };\r\n\r\n const relation: Relation = {\r\n from: source,\r\n to: target,\r\n relationType: getDataValue('e0') || getDataValue('relationType') || 'related_to',\r\n createdAt: getDataValue('e1') || getDataValue('createdAt'),\r\n lastModified: getDataValue('e2') || getDataValue('lastModified'),\r\n };\r\n\r\n relations.push(relation);\r\n }\r\n\r\n return { entities, relations };\r\n }\r\n\r\n private async mergeImportedGraph(\r\n importedGraph: KnowledgeGraph,\r\n mergeStrategy: MergeStrategy,\r\n dryRun: boolean,\r\n options?: LongRunningOperationOptions\r\n ): Promise<ImportResult> {\r\n // Check for cancellation\r\n checkCancellation(options?.signal, 'importGraph');\r\n\r\n // Setup progress reporter (we're at 20% from parsing, need to go to 100%)\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n\r\n const existingGraph = await this.storage.getGraphForMutation();\r\n const result: ImportResult = {\r\n entitiesAdded: 0,\r\n entitiesSkipped: 0,\r\n entitiesUpdated: 0,\r\n relationsAdded: 0,\r\n relationsSkipped: 0,\r\n errors: [],\r\n };\r\n\r\n const existingEntitiesMap = new Map<string, Entity>();\r\n for (const entity of existingGraph.entities) {\r\n existingEntitiesMap.set(entity.name, entity);\r\n }\r\n\r\n const existingRelationsSet = new Set<string>();\r\n for (const relation of existingGraph.relations) {\r\n existingRelationsSet.add(`${relation.from}|${relation.to}|${relation.relationType}`);\r\n }\r\n\r\n // Process entities (20-60% progress)\r\n const totalEntities = importedGraph.entities.length;\r\n const totalRelations = importedGraph.relations.length;\r\n let processedEntities = 0;\r\n\r\n for (const importedEntity of importedGraph.entities) {\r\n // Check for cancellation periodically\r\n checkCancellation(options?.signal, 'importGraph');\r\n\r\n const existing = existingEntitiesMap.get(importedEntity.name);\r\n\r\n if (!existing) {\r\n result.entitiesAdded++;\r\n if (!dryRun) {\r\n existingGraph.entities.push(importedEntity);\r\n existingEntitiesMap.set(importedEntity.name, importedEntity);\r\n }\r\n } else {\r\n switch (mergeStrategy) {\r\n case 'replace':\r\n result.entitiesUpdated++;\r\n if (!dryRun) {\r\n // Sanitize imported entity to prevent prototype pollution\r\n Object.assign(existing, sanitizeObject(importedEntity as unknown as Record<string, unknown>));\r\n }\r\n break;\r\n\r\n case 'skip':\r\n result.entitiesSkipped++;\r\n break;\r\n\r\n case 'merge':\r\n result.entitiesUpdated++;\r\n if (!dryRun) {\r\n existing.observations = [\r\n ...new Set([...existing.observations, ...importedEntity.observations]),\r\n ];\r\n if (importedEntity.tags) {\r\n existing.tags = existing.tags || [];\r\n existing.tags = [...new Set([...existing.tags, ...importedEntity.tags])];\r\n }\r\n if (importedEntity.importance !== undefined) {\r\n existing.importance = importedEntity.importance;\r\n }\r\n existing.lastModified = new Date().toISOString();\r\n }\r\n break;\r\n\r\n case 'fail':\r\n result.errors.push(`Entity \"${importedEntity.name}\" already exists`);\r\n break;\r\n }\r\n }\r\n\r\n processedEntities++;\r\n // Map entity progress (0-100%) to overall progress (20-60%)\r\n const entityProgress = totalEntities > 0 ? Math.round(20 + (processedEntities / totalEntities) * 40) : 60;\r\n reportProgress?.(createProgress(entityProgress, 100, 'importing entities'));\r\n }\r\n\r\n reportProgress?.(createProgress(60, 100, 'importing relations'));\r\n\r\n // Process relations (60-95% progress)\r\n let processedRelations = 0;\r\n\r\n for (const importedRelation of importedGraph.relations) {\r\n // Check for cancellation periodically\r\n checkCancellation(options?.signal, 'importGraph');\r\n\r\n const relationKey = `${importedRelation.from}|${importedRelation.to}|${importedRelation.relationType}`;\r\n\r\n if (!existingEntitiesMap.has(importedRelation.from)) {\r\n result.errors.push(`Relation source entity \"${importedRelation.from}\" does not exist`);\r\n processedRelations++;\r\n continue;\r\n }\r\n if (!existingEntitiesMap.has(importedRelation.to)) {\r\n result.errors.push(`Relation target entity \"${importedRelation.to}\" does not exist`);\r\n processedRelations++;\r\n continue;\r\n }\r\n\r\n if (!existingRelationsSet.has(relationKey)) {\r\n result.relationsAdded++;\r\n if (!dryRun) {\r\n existingGraph.relations.push(importedRelation);\r\n existingRelationsSet.add(relationKey);\r\n }\r\n } else {\r\n if (mergeStrategy === 'fail') {\r\n result.errors.push(`Relation \"${relationKey}\" already exists`);\r\n } else {\r\n result.relationsSkipped++;\r\n }\r\n }\r\n\r\n processedRelations++;\r\n // Map relation progress (0-100%) to overall progress (60-95%)\r\n const relationProgress = totalRelations > 0 ? Math.round(60 + (processedRelations / totalRelations) * 35) : 95;\r\n reportProgress?.(createProgress(relationProgress, 100, 'importing relations'));\r\n }\r\n\r\n // Check for cancellation before final save\r\n checkCancellation(options?.signal, 'importGraph');\r\n reportProgress?.(createProgress(95, 100, 'saving graph'));\r\n\r\n if (!dryRun && (mergeStrategy !== 'fail' || result.errors.length === 0)) {\r\n await this.storage.saveGraph(existingGraph);\r\n }\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(100, 100, 'importGraph'));\r\n\r\n return result;\r\n }\r\n\r\n // ============================================================\r\n // BACKUP OPERATIONS\r\n // ============================================================\r\n\r\n /**\r\n * Ensure backup directory exists.\r\n */\r\n private async ensureBackupDir(): Promise<void> {\r\n try {\r\n await fs.mkdir(this.backupDir, { recursive: true });\r\n } catch (error) {\r\n throw new FileOperationError('create backup directory', this.backupDir, error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * Generate backup file name with timestamp.\r\n * @param compressed - Whether the backup will be compressed (affects extension)\r\n */\r\n private generateBackupFileName(compressed: boolean = true): string {\r\n const now = new Date();\r\n const timestamp = now.toISOString()\r\n .replace(/:/g, '-')\r\n .replace(/\\./g, '-')\r\n .replace('T', '_')\r\n .replace('Z', '');\r\n const extension = compressed ? '.jsonl.br' : '.jsonl';\r\n return `backup_${timestamp}${extension}`;\r\n }\r\n\r\n /**\r\n * Create a backup of the current knowledge graph.\r\n *\r\n * By default, backups are compressed with brotli for 50-70% space reduction.\r\n * Use `options.compress = false` to create uncompressed backups.\r\n *\r\n * @param options - Backup options (compress, description) or legacy description string\r\n * @returns Promise resolving to BackupResult with compression statistics\r\n *\r\n * @example\r\n * ```typescript\r\n * // Compressed backup (default)\r\n * const result = await manager.createBackup({ description: 'Pre-migration backup' });\r\n * console.log(`Compressed from ${result.originalSize} to ${result.compressedSize} bytes`);\r\n *\r\n * // Uncompressed backup\r\n * const result = await manager.createBackup({ compress: false });\r\n * ```\r\n */\r\n async createBackup(options?: BackupOptions | string): Promise<BackupResult> {\r\n await this.ensureBackupDir();\r\n\r\n // Handle legacy string argument (backward compatibility)\r\n const opts: BackupOptions = typeof options === 'string'\r\n ? { description: options, compress: COMPRESSION_CONFIG.AUTO_COMPRESS_BACKUP }\r\n : { compress: COMPRESSION_CONFIG.AUTO_COMPRESS_BACKUP, ...options };\r\n\r\n const shouldCompress = opts.compress ?? COMPRESSION_CONFIG.AUTO_COMPRESS_BACKUP;\r\n const graph = await this.storage.loadGraph();\r\n const timestamp = new Date().toISOString();\r\n const fileName = this.generateBackupFileName(shouldCompress);\r\n const backupPath = join(this.backupDir, fileName);\r\n\r\n try {\r\n const originalPath = this.storage.getFilePath();\r\n let fileContent: string;\r\n\r\n try {\r\n fileContent = await fs.readFile(originalPath, 'utf-8');\r\n } catch {\r\n // If file doesn't exist, generate content from graph\r\n const lines = [\r\n ...graph.entities.map(e => JSON.stringify({ type: 'entity', ...e })),\r\n ...graph.relations.map(r => JSON.stringify({ type: 'relation', ...r })),\r\n ];\r\n fileContent = lines.join('\\n');\r\n }\r\n\r\n const originalSize = Buffer.byteLength(fileContent, 'utf-8');\r\n let compressedSize = originalSize;\r\n let compressionRatio = 1;\r\n\r\n if (shouldCompress) {\r\n // Compress with maximum quality for backups (archive quality)\r\n const compressionResult = await compress(fileContent, {\r\n quality: COMPRESSION_CONFIG.BROTLI_QUALITY_ARCHIVE,\r\n mode: 'text',\r\n });\r\n\r\n await fs.writeFile(backupPath, compressionResult.compressed);\r\n compressedSize = compressionResult.compressedSize;\r\n compressionRatio = compressionResult.ratio;\r\n } else {\r\n // Write uncompressed backup\r\n await fs.writeFile(backupPath, fileContent);\r\n }\r\n\r\n const stats = await fs.stat(backupPath);\r\n\r\n const metadata: BackupMetadata = {\r\n timestamp,\r\n entityCount: graph.entities.length,\r\n relationCount: graph.relations.length,\r\n fileSize: stats.size,\r\n description: opts.description,\r\n compressed: shouldCompress,\r\n originalSize,\r\n compressionRatio: shouldCompress ? compressionRatio : undefined,\r\n compressionFormat: shouldCompress ? 'brotli' : 'none',\r\n };\r\n\r\n const metadataPath = `${backupPath}.meta.json`;\r\n await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));\r\n\r\n return {\r\n path: backupPath,\r\n timestamp,\r\n entityCount: graph.entities.length,\r\n relationCount: graph.relations.length,\r\n compressed: shouldCompress,\r\n originalSize,\r\n compressedSize,\r\n compressionRatio,\r\n description: opts.description,\r\n };\r\n } catch (error) {\r\n throw new FileOperationError('create backup', backupPath, error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * List all available backups, sorted by timestamp (newest first).\r\n *\r\n * Detects both compressed (.jsonl.br) and uncompressed (.jsonl) backups.\r\n *\r\n * @returns Promise resolving to array of backup information with compression details\r\n */\r\n async listBackups(): Promise<BackupInfo[]> {\r\n try {\r\n try {\r\n await fs.access(this.backupDir);\r\n } catch {\r\n return [];\r\n }\r\n\r\n const files = await fs.readdir(this.backupDir);\r\n // Match both .jsonl and .jsonl.br backup files, exclude metadata files\r\n const backupFiles = files.filter(f =>\r\n f.startsWith('backup_') &&\r\n (f.endsWith('.jsonl') || f.endsWith('.jsonl.br')) &&\r\n !f.endsWith('.meta.json')\r\n );\r\n\r\n const backups: BackupInfo[] = [];\r\n\r\n for (const fileName of backupFiles) {\r\n const filePath = join(this.backupDir, fileName);\r\n const isCompressed = hasBrotliExtension(fileName);\r\n\r\n // Try to read metadata file (handles both .jsonl.meta.json and .jsonl.br.meta.json)\r\n const metadataPath = `${filePath}.meta.json`;\r\n\r\n try {\r\n const [metadataContent, stats] = await Promise.all([\r\n fs.readFile(metadataPath, 'utf-8'),\r\n fs.stat(filePath),\r\n ]);\r\n const metadata: BackupMetadata = JSON.parse(metadataContent);\r\n\r\n // Ensure compression fields are present (backward compatibility)\r\n if (metadata.compressed === undefined) {\r\n metadata.compressed = isCompressed;\r\n }\r\n if (metadata.compressionFormat === undefined) {\r\n metadata.compressionFormat = isCompressed ? 'brotli' : 'none';\r\n }\r\n\r\n backups.push({\r\n fileName,\r\n filePath,\r\n metadata,\r\n compressed: isCompressed,\r\n size: stats.size,\r\n });\r\n } catch {\r\n // Skip backups without valid metadata\r\n continue;\r\n }\r\n }\r\n\r\n backups.sort((a, b) =>\r\n new Date(b.metadata.timestamp).getTime() - new Date(a.metadata.timestamp).getTime()\r\n );\r\n\r\n return backups;\r\n } catch (error) {\r\n throw new FileOperationError('list backups', this.backupDir, error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * Restore the knowledge graph from a backup file.\r\n *\r\n * Automatically detects and decompresses brotli-compressed backups (.br extension).\r\n * Maintains backward compatibility with uncompressed backups.\r\n *\r\n * @param backupPath - Path to the backup file to restore from\r\n * @returns Promise resolving to RestoreResult with restoration details\r\n *\r\n * @example\r\n * ```typescript\r\n * // Restore from compressed backup\r\n * const result = await manager.restoreFromBackup('/path/to/backup.jsonl.br');\r\n * console.log(`Restored ${result.entityCount} entities from compressed backup`);\r\n *\r\n * // Restore from uncompressed backup (legacy)\r\n * const result = await manager.restoreFromBackup('/path/to/backup.jsonl');\r\n * ```\r\n */\r\n async restoreFromBackup(backupPath: string): Promise<RestoreResult> {\r\n try {\r\n await fs.access(backupPath);\r\n\r\n const isCompressed = hasBrotliExtension(backupPath);\r\n const backupBuffer = await fs.readFile(backupPath);\r\n\r\n let backupContent: string;\r\n if (isCompressed) {\r\n // Decompress the backup\r\n const decompressedBuffer = await decompress(backupBuffer);\r\n backupContent = decompressedBuffer.toString('utf-8');\r\n } else {\r\n // Read as plain text\r\n backupContent = backupBuffer.toString('utf-8');\r\n }\r\n\r\n const mainPath = this.storage.getFilePath();\r\n await fs.writeFile(mainPath, backupContent);\r\n\r\n this.storage.clearCache();\r\n\r\n // Load the restored graph to get counts\r\n const graph = await this.storage.loadGraph();\r\n\r\n return {\r\n entityCount: graph.entities.length,\r\n relationCount: graph.relations.length,\r\n restoredFrom: backupPath,\r\n wasCompressed: isCompressed,\r\n };\r\n } catch (error) {\r\n throw new FileOperationError('restore from backup', backupPath, error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * Delete a specific backup file.\r\n *\r\n * @param backupPath - Path to the backup file to delete\r\n */\r\n async deleteBackup(backupPath: string): Promise<void> {\r\n try {\r\n await fs.unlink(backupPath);\r\n\r\n try {\r\n await fs.unlink(`${backupPath}.meta.json`);\r\n } catch {\r\n // Metadata file doesn't exist - that's ok\r\n }\r\n } catch (error) {\r\n throw new FileOperationError('delete backup', backupPath, error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * Clean old backups, keeping only the most recent N backups.\r\n *\r\n * @param keepCount - Number of recent backups to keep (default: 10)\r\n * @returns Promise resolving to number of backups deleted\r\n */\r\n async cleanOldBackups(keepCount: number = 10): Promise<number> {\r\n const backups = await this.listBackups();\r\n\r\n if (backups.length <= keepCount) {\r\n return 0;\r\n }\r\n\r\n const backupsToDelete = backups.slice(keepCount);\r\n let deletedCount = 0;\r\n\r\n for (const backup of backupsToDelete) {\r\n try {\r\n await this.deleteBackup(backup.filePath);\r\n deletedCount++;\r\n } catch {\r\n continue;\r\n }\r\n }\r\n\r\n return deletedCount;\r\n }\r\n\r\n /**\r\n * Get the path to the backup directory.\r\n */\r\n getBackupDir(): string {\r\n return this.backupDir;\r\n }\r\n}\r\n","/**\r\n * Transaction Manager\r\n *\r\n * Provides atomic transaction support for knowledge graph operations.\r\n * Ensures data consistency by allowing multiple operations to be\r\n * grouped together and committed atomically, with automatic rollback on failure.\r\n *\r\n * @module core/TransactionManager\r\n */\r\n\r\nimport type {\r\n Entity,\r\n Relation,\r\n KnowledgeGraph,\r\n LongRunningOperationOptions,\r\n BatchOperation,\r\n BatchResult,\r\n BatchOptions,\r\n} from '../types/index.js';\r\nimport type { GraphStorage } from './GraphStorage.js';\r\nimport { IOManager } from '../features/IOManager.js';\r\nimport { KnowledgeGraphError } from '../utils/errors.js';\r\nimport { checkCancellation, createProgressReporter, createProgress, sanitizeObject } from '../utils/index.js';\r\n\r\n/**\r\n * Types of operations that can be performed in a transaction.\r\n */\r\nexport enum OperationType {\r\n CREATE_ENTITY = 'CREATE_ENTITY',\r\n UPDATE_ENTITY = 'UPDATE_ENTITY',\r\n DELETE_ENTITY = 'DELETE_ENTITY',\r\n CREATE_RELATION = 'CREATE_RELATION',\r\n DELETE_RELATION = 'DELETE_RELATION',\r\n}\r\n\r\n/**\r\n * Represents a single operation in a transaction using discriminated union.\r\n */\r\nexport type TransactionOperation =\r\n | {\r\n type: OperationType.CREATE_ENTITY;\r\n data: Omit<Entity, 'createdAt' | 'lastModified'>;\r\n }\r\n | {\r\n type: OperationType.UPDATE_ENTITY;\r\n data: { name: string; updates: Partial<Entity> };\r\n }\r\n | {\r\n type: OperationType.DELETE_ENTITY;\r\n data: { name: string };\r\n }\r\n | {\r\n type: OperationType.CREATE_RELATION;\r\n data: Omit<Relation, 'createdAt' | 'lastModified'>;\r\n }\r\n | {\r\n type: OperationType.DELETE_RELATION;\r\n data: { from: string; to: string; relationType: string };\r\n };\r\n\r\n/**\r\n * Transaction execution result.\r\n */\r\nexport interface TransactionResult {\r\n /** Whether the transaction was successful */\r\n success: boolean;\r\n /** Number of operations executed */\r\n operationsExecuted: number;\r\n /** Error message if transaction failed */\r\n error?: string;\r\n /** Path to rollback backup if created */\r\n rollbackBackup?: string;\r\n}\r\n\r\n/**\r\n * Manages atomic transactions for knowledge graph operations.\r\n *\r\n * Provides ACID-like guarantees:\r\n * - Atomicity: All operations succeed or all fail\r\n * - Consistency: Graph is always in a valid state\r\n * - Isolation: Each transaction operates on a snapshot\r\n * - Durability: Changes are persisted to disk\r\n *\r\n * @example\r\n * ```typescript\r\n * const storage = new GraphStorage('/data/memory.jsonl');\r\n * const txManager = new TransactionManager(storage);\r\n *\r\n * // Begin transaction\r\n * txManager.begin();\r\n *\r\n * // Stage operations\r\n * txManager.createEntity({ name: 'Alice', entityType: 'person', observations: [] });\r\n * txManager.createRelation({ from: 'Alice', to: 'Bob', relationType: 'knows' });\r\n *\r\n * // Commit atomically (or rollback on error)\r\n * const result = await txManager.commit();\r\n * if (result.success) {\r\n * console.log(`Transaction completed: ${result.operationsExecuted} operations`);\r\n * }\r\n * ```\r\n */\r\nexport class TransactionManager {\r\n private operations: TransactionOperation[] = [];\r\n private inTransaction: boolean = false;\r\n private ioManager: IOManager;\r\n private transactionBackup?: string;\r\n\r\n constructor(private storage: GraphStorage) {\r\n this.ioManager = new IOManager(storage);\r\n }\r\n\r\n /**\r\n * Begin a new transaction.\r\n *\r\n * Creates a backup of the current state for rollback purposes.\r\n * Only one transaction can be active at a time.\r\n *\r\n * @throws {KnowledgeGraphError} If a transaction is already in progress\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * // ... stage operations ...\r\n * await txManager.commit();\r\n * ```\r\n */\r\n begin(): void {\r\n if (this.inTransaction) {\r\n throw new KnowledgeGraphError('Transaction already in progress', 'TRANSACTION_ACTIVE');\r\n }\r\n\r\n this.operations = [];\r\n this.inTransaction = true;\r\n }\r\n\r\n /**\r\n * Stage a create entity operation.\r\n *\r\n * @param entity - Entity to create (without timestamps)\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * txManager.createEntity({\r\n * name: 'Alice',\r\n * entityType: 'person',\r\n * observations: ['Software engineer'],\r\n * importance: 8\r\n * });\r\n * ```\r\n */\r\n createEntity(entity: Omit<Entity, 'createdAt' | 'lastModified'>): void {\r\n this.ensureInTransaction();\r\n this.operations.push({\r\n type: OperationType.CREATE_ENTITY,\r\n data: entity,\r\n });\r\n }\r\n\r\n /**\r\n * Stage an update entity operation.\r\n *\r\n * @param name - Name of entity to update\r\n * @param updates - Partial entity updates\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * txManager.updateEntity('Alice', {\r\n * importance: 9,\r\n * observations: ['Lead software engineer']\r\n * });\r\n * ```\r\n */\r\n updateEntity(name: string, updates: Partial<Entity>): void {\r\n this.ensureInTransaction();\r\n this.operations.push({\r\n type: OperationType.UPDATE_ENTITY,\r\n data: { name, updates },\r\n });\r\n }\r\n\r\n /**\r\n * Stage a delete entity operation.\r\n *\r\n * @param name - Name of entity to delete\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * txManager.deleteEntity('OldEntity');\r\n * ```\r\n */\r\n deleteEntity(name: string): void {\r\n this.ensureInTransaction();\r\n this.operations.push({\r\n type: OperationType.DELETE_ENTITY,\r\n data: { name },\r\n });\r\n }\r\n\r\n /**\r\n * Stage a create relation operation.\r\n *\r\n * @param relation - Relation to create (without timestamps)\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * txManager.createRelation({\r\n * from: 'Alice',\r\n * to: 'Bob',\r\n * relationType: 'mentors'\r\n * });\r\n * ```\r\n */\r\n createRelation(relation: Omit<Relation, 'createdAt' | 'lastModified'>): void {\r\n this.ensureInTransaction();\r\n this.operations.push({\r\n type: OperationType.CREATE_RELATION,\r\n data: relation,\r\n });\r\n }\r\n\r\n /**\r\n * Stage a delete relation operation.\r\n *\r\n * @param from - Source entity name\r\n * @param to - Target entity name\r\n * @param relationType - Type of relation\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * txManager.deleteRelation('Alice', 'Bob', 'mentors');\r\n * ```\r\n */\r\n deleteRelation(from: string, to: string, relationType: string): void {\r\n this.ensureInTransaction();\r\n this.operations.push({\r\n type: OperationType.DELETE_RELATION,\r\n data: { from, to, relationType },\r\n });\r\n }\r\n\r\n /**\r\n * Commit the transaction, applying all staged operations atomically.\r\n *\r\n * Creates a backup before applying changes. If any operation fails,\r\n * automatically rolls back to the pre-transaction state.\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.\r\n *\r\n * @param options - Optional progress/cancellation options (Phase 9B)\r\n * @returns Promise resolving to transaction result\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * txManager.createEntity({ name: 'Alice', entityType: 'person', observations: [] });\r\n * txManager.createRelation({ from: 'Alice', to: 'Bob', relationType: 'knows' });\r\n *\r\n * const result = await txManager.commit();\r\n * if (result.success) {\r\n * console.log(`Committed ${result.operationsExecuted} operations`);\r\n * } else {\r\n * console.error(`Transaction failed: ${result.error}`);\r\n * }\r\n *\r\n * // With progress tracking (Phase 9B)\r\n * const result = await txManager.commit({\r\n * onProgress: (p) => console.log(`${p.percentage}% complete`),\r\n * });\r\n * ```\r\n */\r\n async commit(options?: LongRunningOperationOptions): Promise<TransactionResult> {\r\n this.ensureInTransaction();\r\n\r\n // Setup progress reporter\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n const totalOperations = this.operations.length;\r\n reportProgress?.(createProgress(0, 100, 'commit'));\r\n\r\n try {\r\n // Check for early cancellation (inside try block to handle gracefully)\r\n checkCancellation(options?.signal, 'commit');\r\n // Phase 1: Create backup for rollback (0-20% progress)\r\n reportProgress?.(createProgress(5, 100, 'creating backup'));\r\n const backupResult = await this.ioManager.createBackup({\r\n description: 'Transaction backup (auto-created)',\r\n });\r\n this.transactionBackup = backupResult.path;\r\n\r\n // Check for cancellation after backup\r\n checkCancellation(options?.signal, 'commit');\r\n reportProgress?.(createProgress(20, 100, 'backup created'));\r\n\r\n // Phase 2: Load graph (20-30% progress)\r\n reportProgress?.(createProgress(25, 100, 'loading graph'));\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n reportProgress?.(createProgress(30, 100, 'graph loaded'));\r\n\r\n // Phase 3: Apply all operations (30-80% progress)\r\n let operationsExecuted = 0;\r\n for (const operation of this.operations) {\r\n // Check for cancellation between operations\r\n checkCancellation(options?.signal, 'commit');\r\n\r\n this.applyOperation(graph, operation, timestamp);\r\n operationsExecuted++;\r\n\r\n // Map operation progress (0-100%) to overall progress (30-80%)\r\n const opProgress = totalOperations > 0 ? Math.round(30 + (operationsExecuted / totalOperations) * 50) : 80;\r\n reportProgress?.(createProgress(opProgress, 100, 'applying operations'));\r\n }\r\n\r\n // Check for cancellation before save\r\n checkCancellation(options?.signal, 'commit');\r\n\r\n // Phase 4: Save the modified graph (80-95% progress)\r\n reportProgress?.(createProgress(85, 100, 'saving graph'));\r\n await this.storage.saveGraph(graph);\r\n reportProgress?.(createProgress(95, 100, 'graph saved'));\r\n\r\n // Clean up transaction state\r\n this.inTransaction = false;\r\n this.operations = [];\r\n\r\n // Delete the transaction backup (no longer needed)\r\n if (this.transactionBackup) {\r\n await this.ioManager.deleteBackup(this.transactionBackup);\r\n this.transactionBackup = undefined;\r\n }\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(100, 100, 'commit'));\r\n\r\n return {\r\n success: true,\r\n operationsExecuted,\r\n };\r\n } catch (error) {\r\n // Rollback on error\r\n const rollbackResult = await this.rollback();\r\n\r\n return {\r\n success: false,\r\n operationsExecuted: 0,\r\n error: error instanceof Error ? error.message : String(error),\r\n rollbackBackup: rollbackResult.backupUsed,\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Rollback the current transaction.\r\n *\r\n * Restores the graph to the pre-transaction state using the backup.\r\n * Automatically called by commit() on failure.\r\n *\r\n * @returns Promise resolving to rollback result\r\n *\r\n * @example\r\n * ```typescript\r\n * txManager.begin();\r\n * txManager.createEntity({ name: 'Test', entityType: 'temp', observations: [] });\r\n *\r\n * // Explicit rollback (e.g., user cancellation)\r\n * const result = await txManager.rollback();\r\n * console.log(`Rolled back, restored from: ${result.backupUsed}`);\r\n * ```\r\n */\r\n async rollback(): Promise<{ success: boolean; backupUsed?: string }> {\r\n if (!this.transactionBackup) {\r\n this.inTransaction = false;\r\n this.operations = [];\r\n return { success: false };\r\n }\r\n\r\n try {\r\n // Restore from backup\r\n await this.ioManager.restoreFromBackup(this.transactionBackup);\r\n\r\n // Clean up\r\n const backupUsed = this.transactionBackup;\r\n await this.ioManager.deleteBackup(this.transactionBackup);\r\n\r\n this.inTransaction = false;\r\n this.operations = [];\r\n this.transactionBackup = undefined;\r\n\r\n return { success: true, backupUsed };\r\n } catch (error) {\r\n // Rollback failed - keep backup for manual recovery\r\n this.inTransaction = false;\r\n this.operations = [];\r\n\r\n return { success: false, backupUsed: this.transactionBackup };\r\n }\r\n }\r\n\r\n /**\r\n * Check if a transaction is currently in progress.\r\n *\r\n * @returns True if transaction is active\r\n */\r\n isInTransaction(): boolean {\r\n return this.inTransaction;\r\n }\r\n\r\n /**\r\n * Get the number of staged operations in the current transaction.\r\n *\r\n * @returns Number of operations staged\r\n */\r\n getOperationCount(): number {\r\n return this.operations.length;\r\n }\r\n\r\n /**\r\n * Ensure a transaction is in progress, or throw an error.\r\n *\r\n * @private\r\n */\r\n private ensureInTransaction(): void {\r\n if (!this.inTransaction) {\r\n throw new KnowledgeGraphError('No transaction in progress. Call begin() first.', 'NO_TRANSACTION');\r\n }\r\n }\r\n\r\n /**\r\n * Apply a single operation to the graph.\r\n *\r\n * @private\r\n */\r\n private applyOperation(graph: KnowledgeGraph, operation: TransactionOperation, timestamp: string): void {\r\n switch (operation.type) {\r\n case OperationType.CREATE_ENTITY: {\r\n const entity: Entity = {\r\n ...operation.data,\r\n createdAt: timestamp,\r\n lastModified: timestamp,\r\n };\r\n // Check for duplicates\r\n if (graph.entities.some(e => e.name === entity.name)) {\r\n throw new KnowledgeGraphError(`Entity \"${entity.name}\" already exists`, 'DUPLICATE_ENTITY');\r\n }\r\n graph.entities.push(entity);\r\n break;\r\n }\r\n\r\n case OperationType.UPDATE_ENTITY: {\r\n const { name, updates } = operation.data;\r\n const entity = graph.entities.find(e => e.name === name);\r\n if (!entity) {\r\n throw new KnowledgeGraphError(`Entity \"${name}\" not found`, 'ENTITY_NOT_FOUND');\r\n }\r\n // Sanitize updates to prevent prototype pollution\r\n Object.assign(entity, sanitizeObject(updates as Record<string, unknown>));\r\n entity.lastModified = timestamp;\r\n break;\r\n }\r\n\r\n case OperationType.DELETE_ENTITY: {\r\n const { name } = operation.data;\r\n const index = graph.entities.findIndex(e => e.name === name);\r\n if (index === -1) {\r\n throw new KnowledgeGraphError(`Entity \"${name}\" not found`, 'ENTITY_NOT_FOUND');\r\n }\r\n graph.entities.splice(index, 1);\r\n // Delete related relations\r\n graph.relations = graph.relations.filter(r => r.from !== name && r.to !== name);\r\n break;\r\n }\r\n\r\n case OperationType.CREATE_RELATION: {\r\n const relation: Relation = {\r\n ...operation.data,\r\n createdAt: timestamp,\r\n lastModified: timestamp,\r\n };\r\n // Check for duplicates\r\n const exists = graph.relations.some(\r\n r => r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType\r\n );\r\n if (exists) {\r\n throw new KnowledgeGraphError(\r\n `Relation \"${relation.from}\" -> \"${relation.to}\" (${relation.relationType}) already exists`,\r\n 'DUPLICATE_RELATION'\r\n );\r\n }\r\n graph.relations.push(relation);\r\n break;\r\n }\r\n\r\n case OperationType.DELETE_RELATION: {\r\n const { from, to, relationType } = operation.data;\r\n const index = graph.relations.findIndex(\r\n r => r.from === from && r.to === to && r.relationType === relationType\r\n );\r\n if (index === -1) {\r\n throw new KnowledgeGraphError(\r\n `Relation \"${from}\" -> \"${to}\" (${relationType}) not found`,\r\n 'RELATION_NOT_FOUND'\r\n );\r\n }\r\n graph.relations.splice(index, 1);\r\n break;\r\n }\r\n\r\n default: {\r\n // Exhaustiveness check - TypeScript will error if we miss a case\r\n const _exhaustiveCheck: never = operation;\r\n throw new KnowledgeGraphError(`Unknown operation type: ${(_exhaustiveCheck as TransactionOperation).type}`, 'UNKNOWN_OPERATION');\r\n }\r\n }\r\n }\r\n}\r\n\r\n// ==================== Phase 10 Sprint 1: BatchTransaction ====================\r\n\r\n/**\r\n * Phase 10 Sprint 1: Fluent API for building and executing batch transactions.\r\n *\r\n * BatchTransaction provides a builder pattern for accumulating multiple\r\n * graph operations and executing them atomically in a single transaction.\r\n * This reduces I/O overhead and ensures consistency across related changes.\r\n *\r\n * @example\r\n * ```typescript\r\n * const storage = new GraphStorage('/data/memory.jsonl');\r\n * const batch = new BatchTransaction(storage);\r\n *\r\n * // Build the batch with fluent API\r\n * const result = await batch\r\n * .createEntity({ name: 'Alice', entityType: 'person', observations: ['Developer'] })\r\n * .createEntity({ name: 'Bob', entityType: 'person', observations: ['Designer'] })\r\n * .createRelation({ from: 'Alice', to: 'Bob', relationType: 'knows' })\r\n * .updateEntity('Alice', { importance: 8 })\r\n * .execute();\r\n *\r\n * if (result.success) {\r\n * console.log(`Batch completed: ${result.operationsExecuted} operations in ${result.executionTimeMs}ms`);\r\n * }\r\n * ```\r\n */\r\nexport class BatchTransaction {\r\n private operations: BatchOperation[] = [];\r\n private storage: GraphStorage;\r\n\r\n /**\r\n * Create a new BatchTransaction instance.\r\n *\r\n * @param storage - GraphStorage instance to execute operations against\r\n */\r\n constructor(storage: GraphStorage) {\r\n this.storage = storage;\r\n }\r\n\r\n /**\r\n * Add a create entity operation to the batch.\r\n *\r\n * @param entity - Entity to create (without timestamps)\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.createEntity({\r\n * name: 'Alice',\r\n * entityType: 'person',\r\n * observations: ['Software engineer'],\r\n * importance: 8\r\n * });\r\n * ```\r\n */\r\n createEntity(entity: Omit<Entity, 'createdAt' | 'lastModified'>): this {\r\n this.operations.push({ type: 'createEntity', data: entity });\r\n return this;\r\n }\r\n\r\n /**\r\n * Add an update entity operation to the batch.\r\n *\r\n * @param name - Name of entity to update\r\n * @param updates - Partial entity updates\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.updateEntity('Alice', { importance: 9 });\r\n * ```\r\n */\r\n updateEntity(name: string, updates: Partial<Entity>): this {\r\n this.operations.push({ type: 'updateEntity', data: { name, updates } });\r\n return this;\r\n }\r\n\r\n /**\r\n * Add a delete entity operation to the batch.\r\n *\r\n * @param name - Name of entity to delete\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.deleteEntity('OldEntity');\r\n * ```\r\n */\r\n deleteEntity(name: string): this {\r\n this.operations.push({ type: 'deleteEntity', data: { name } });\r\n return this;\r\n }\r\n\r\n /**\r\n * Add a create relation operation to the batch.\r\n *\r\n * @param relation - Relation to create (without timestamps)\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.createRelation({\r\n * from: 'Alice',\r\n * to: 'Bob',\r\n * relationType: 'mentors'\r\n * });\r\n * ```\r\n */\r\n createRelation(relation: Omit<Relation, 'createdAt' | 'lastModified'>): this {\r\n this.operations.push({ type: 'createRelation', data: relation });\r\n return this;\r\n }\r\n\r\n /**\r\n * Add a delete relation operation to the batch.\r\n *\r\n * @param from - Source entity name\r\n * @param to - Target entity name\r\n * @param relationType - Type of relation\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.deleteRelation('Alice', 'Bob', 'mentors');\r\n * ```\r\n */\r\n deleteRelation(from: string, to: string, relationType: string): this {\r\n this.operations.push({ type: 'deleteRelation', data: { from, to, relationType } });\r\n return this;\r\n }\r\n\r\n /**\r\n * Add observations to an existing entity.\r\n *\r\n * @param name - Name of entity to add observations to\r\n * @param observations - Observations to add\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.addObservations('Alice', ['Knows TypeScript', 'Leads team']);\r\n * ```\r\n */\r\n addObservations(name: string, observations: string[]): this {\r\n this.operations.push({ type: 'addObservations', data: { name, observations } });\r\n return this;\r\n }\r\n\r\n /**\r\n * Delete observations from an existing entity.\r\n *\r\n * @param name - Name of entity to delete observations from\r\n * @param observations - Observations to delete\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.deleteObservations('Alice', ['Old fact']);\r\n * ```\r\n */\r\n deleteObservations(name: string, observations: string[]): this {\r\n this.operations.push({ type: 'deleteObservations', data: { name, observations } });\r\n return this;\r\n }\r\n\r\n /**\r\n * Add multiple operations from an array.\r\n *\r\n * @param operations - Array of batch operations\r\n * @returns This BatchTransaction for chaining\r\n *\r\n * @example\r\n * ```typescript\r\n * batch.addOperations([\r\n * { type: 'createEntity', data: { name: 'A', entityType: 'x', observations: [] } },\r\n * { type: 'createEntity', data: { name: 'B', entityType: 'x', observations: [] } }\r\n * ]);\r\n * ```\r\n */\r\n addOperations(operations: BatchOperation[]): this {\r\n this.operations.push(...operations);\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the number of operations in this batch.\r\n *\r\n * @returns Number of operations queued\r\n */\r\n size(): number {\r\n return this.operations.length;\r\n }\r\n\r\n /**\r\n * Clear all operations from the batch.\r\n *\r\n * @returns This BatchTransaction for chaining\r\n */\r\n clear(): this {\r\n this.operations = [];\r\n return this;\r\n }\r\n\r\n /**\r\n * Get a copy of the operations in this batch.\r\n *\r\n * @returns Array of batch operations\r\n */\r\n getOperations(): BatchOperation[] {\r\n return [...this.operations];\r\n }\r\n\r\n /**\r\n * Execute all operations in the batch atomically.\r\n *\r\n * All operations are applied within a single transaction. If any\r\n * operation fails, all changes are rolled back (when stopOnError is true).\r\n *\r\n * @param options - Batch execution options\r\n * @returns Promise resolving to batch result\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = await batch.execute();\r\n * if (result.success) {\r\n * console.log(`Created ${result.entitiesCreated} entities`);\r\n * } else {\r\n * console.error(`Failed at operation ${result.failedOperationIndex}: ${result.error}`);\r\n * }\r\n * ```\r\n */\r\n async execute(options: BatchOptions = {}): Promise<BatchResult> {\r\n const startTime = Date.now();\r\n const { stopOnError = true, validateBeforeExecute = true } = options;\r\n\r\n const result: BatchResult = {\r\n success: true,\r\n operationsExecuted: 0,\r\n entitiesCreated: 0,\r\n entitiesUpdated: 0,\r\n entitiesDeleted: 0,\r\n relationsCreated: 0,\r\n relationsDeleted: 0,\r\n executionTimeMs: 0,\r\n };\r\n\r\n if (this.operations.length === 0) {\r\n result.executionTimeMs = Date.now() - startTime;\r\n return result;\r\n }\r\n\r\n // Load graph for mutation\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n\r\n // Optional: Validate all operations before executing\r\n if (validateBeforeExecute) {\r\n const validationError = this.validateOperations(graph);\r\n if (validationError) {\r\n return {\r\n ...result,\r\n success: false,\r\n error: validationError.message,\r\n failedOperationIndex: validationError.index,\r\n executionTimeMs: Date.now() - startTime,\r\n };\r\n }\r\n }\r\n\r\n // Execute operations\r\n for (let i = 0; i < this.operations.length; i++) {\r\n const operation = this.operations[i];\r\n\r\n try {\r\n this.applyBatchOperation(graph, operation, timestamp, result);\r\n result.operationsExecuted++;\r\n } catch (error) {\r\n result.success = false;\r\n result.error = error instanceof Error ? error.message : String(error);\r\n result.failedOperationIndex = i;\r\n\r\n if (stopOnError) {\r\n result.executionTimeMs = Date.now() - startTime;\r\n return result;\r\n }\r\n }\r\n }\r\n\r\n // Save the modified graph if successful (or if stopOnError is false)\r\n if (result.success || !stopOnError) {\r\n try {\r\n await this.storage.saveGraph(graph);\r\n } catch (error) {\r\n result.success = false;\r\n result.error = `Failed to save graph: ${error instanceof Error ? error.message : String(error)}`;\r\n }\r\n }\r\n\r\n result.executionTimeMs = Date.now() - startTime;\r\n return result;\r\n }\r\n\r\n /**\r\n * Validate all operations before executing.\r\n * @private\r\n */\r\n private validateOperations(\r\n graph: KnowledgeGraph\r\n ): { message: string; index: number } | null {\r\n const entityNames = new Set(graph.entities.map(e => e.name));\r\n const pendingCreates = new Set<string>();\r\n const pendingDeletes = new Set<string>();\r\n\r\n for (let i = 0; i < this.operations.length; i++) {\r\n const op = this.operations[i];\r\n\r\n switch (op.type) {\r\n case 'createEntity': {\r\n const name = op.data.name;\r\n if (entityNames.has(name) && !pendingDeletes.has(name)) {\r\n return { message: `Entity \"${name}\" already exists`, index: i };\r\n }\r\n if (pendingCreates.has(name)) {\r\n return { message: `Duplicate create for entity \"${name}\" in batch`, index: i };\r\n }\r\n pendingCreates.add(name);\r\n break;\r\n }\r\n\r\n case 'updateEntity':\r\n case 'addObservations':\r\n case 'deleteObservations': {\r\n const name = op.data.name;\r\n const exists = (entityNames.has(name) || pendingCreates.has(name)) && !pendingDeletes.has(name);\r\n if (!exists) {\r\n return { message: `Entity \"${name}\" not found`, index: i };\r\n }\r\n break;\r\n }\r\n\r\n case 'deleteEntity': {\r\n const name = op.data.name;\r\n const exists = (entityNames.has(name) || pendingCreates.has(name)) && !pendingDeletes.has(name);\r\n if (!exists) {\r\n return { message: `Entity \"${name}\" not found for deletion`, index: i };\r\n }\r\n pendingDeletes.add(name);\r\n break;\r\n }\r\n\r\n case 'createRelation': {\r\n const { from, to } = op.data;\r\n const fromExists = (entityNames.has(from) || pendingCreates.has(from)) && !pendingDeletes.has(from);\r\n const toExists = (entityNames.has(to) || pendingCreates.has(to)) && !pendingDeletes.has(to);\r\n if (!fromExists) {\r\n return { message: `Source entity \"${from}\" not found for relation`, index: i };\r\n }\r\n if (!toExists) {\r\n return { message: `Target entity \"${to}\" not found for relation`, index: i };\r\n }\r\n break;\r\n }\r\n\r\n case 'deleteRelation': {\r\n // Relations are validated at execution time\r\n break;\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Apply a single batch operation to the graph.\r\n * @private\r\n */\r\n private applyBatchOperation(\r\n graph: KnowledgeGraph,\r\n operation: BatchOperation,\r\n timestamp: string,\r\n result: BatchResult\r\n ): void {\r\n switch (operation.type) {\r\n case 'createEntity': {\r\n const entity: Entity = {\r\n ...operation.data,\r\n createdAt: timestamp,\r\n lastModified: timestamp,\r\n };\r\n if (graph.entities.some(e => e.name === entity.name)) {\r\n throw new KnowledgeGraphError(`Entity \"${entity.name}\" already exists`, 'DUPLICATE_ENTITY');\r\n }\r\n graph.entities.push(entity);\r\n result.entitiesCreated++;\r\n break;\r\n }\r\n\r\n case 'updateEntity': {\r\n const { name, updates } = operation.data;\r\n const entity = graph.entities.find(e => e.name === name);\r\n if (!entity) {\r\n throw new KnowledgeGraphError(`Entity \"${name}\" not found`, 'ENTITY_NOT_FOUND');\r\n }\r\n // Sanitize updates to prevent prototype pollution\r\n Object.assign(entity, sanitizeObject(updates as Record<string, unknown>));\r\n entity.lastModified = timestamp;\r\n result.entitiesUpdated++;\r\n break;\r\n }\r\n\r\n case 'deleteEntity': {\r\n const { name } = operation.data;\r\n const index = graph.entities.findIndex(e => e.name === name);\r\n if (index === -1) {\r\n throw new KnowledgeGraphError(`Entity \"${name}\" not found`, 'ENTITY_NOT_FOUND');\r\n }\r\n graph.entities.splice(index, 1);\r\n // Delete related relations\r\n graph.relations = graph.relations.filter(r => r.from !== name && r.to !== name);\r\n result.entitiesDeleted++;\r\n break;\r\n }\r\n\r\n case 'createRelation': {\r\n const relation: Relation = {\r\n ...operation.data,\r\n createdAt: timestamp,\r\n lastModified: timestamp,\r\n };\r\n const exists = graph.relations.some(\r\n r => r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType\r\n );\r\n if (exists) {\r\n throw new KnowledgeGraphError(\r\n `Relation \"${relation.from}\" -> \"${relation.to}\" (${relation.relationType}) already exists`,\r\n 'DUPLICATE_RELATION'\r\n );\r\n }\r\n graph.relations.push(relation);\r\n result.relationsCreated++;\r\n break;\r\n }\r\n\r\n case 'deleteRelation': {\r\n const { from, to, relationType } = operation.data;\r\n const index = graph.relations.findIndex(\r\n r => r.from === from && r.to === to && r.relationType === relationType\r\n );\r\n if (index === -1) {\r\n throw new KnowledgeGraphError(\r\n `Relation \"${from}\" -> \"${to}\" (${relationType}) not found`,\r\n 'RELATION_NOT_FOUND'\r\n );\r\n }\r\n graph.relations.splice(index, 1);\r\n result.relationsDeleted++;\r\n break;\r\n }\r\n\r\n case 'addObservations': {\r\n const { name, observations } = operation.data;\r\n const entity = graph.entities.find(e => e.name === name);\r\n if (!entity) {\r\n throw new KnowledgeGraphError(`Entity \"${name}\" not found`, 'ENTITY_NOT_FOUND');\r\n }\r\n // Add only new observations\r\n const existingSet = new Set(entity.observations);\r\n const newObs = observations.filter((o: string) => !existingSet.has(o));\r\n entity.observations.push(...newObs);\r\n entity.lastModified = timestamp;\r\n result.entitiesUpdated++;\r\n break;\r\n }\r\n\r\n case 'deleteObservations': {\r\n const { name, observations } = operation.data;\r\n const entity = graph.entities.find(e => e.name === name);\r\n if (!entity) {\r\n throw new KnowledgeGraphError(`Entity \"${name}\" not found`, 'ENTITY_NOT_FOUND');\r\n }\r\n const toDelete = new Set(observations);\r\n entity.observations = entity.observations.filter((o: string) => !toDelete.has(o));\r\n entity.lastModified = timestamp;\r\n result.entitiesUpdated++;\r\n break;\r\n }\r\n\r\n default: {\r\n const _exhaustiveCheck: never = operation;\r\n throw new KnowledgeGraphError(\r\n `Unknown batch operation type: ${(_exhaustiveCheck as BatchOperation).type}`,\r\n 'UNKNOWN_OPERATION'\r\n );\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * Graph Event Emitter\r\n *\r\n * Phase 10 Sprint 2: Provides event-based notifications for graph changes.\r\n * Enables loose coupling between graph operations and dependent systems\r\n * like search indexes, analytics, and external integrations.\r\n *\r\n * @module core/GraphEventEmitter\r\n */\r\n\r\nimport type {\r\n GraphEventType,\r\n GraphEvent,\r\n GraphEventListener,\r\n GraphEventMap,\r\n Entity,\r\n Relation,\r\n EntityCreatedEvent,\r\n EntityUpdatedEvent,\r\n EntityDeletedEvent,\r\n RelationCreatedEvent,\r\n RelationDeletedEvent,\r\n ObservationAddedEvent,\r\n ObservationDeletedEvent,\r\n GraphSavedEvent,\r\n GraphLoadedEvent,\r\n} from '../types/index.js';\r\n\r\n/**\r\n * Phase 10 Sprint 2: Event emitter for graph change notifications.\r\n *\r\n * Provides a type-safe event system for subscribing to and emitting\r\n * graph change events. Supports wildcard listeners for all events.\r\n *\r\n * @example\r\n * ```typescript\r\n * const emitter = new GraphEventEmitter();\r\n *\r\n * // Listen to specific event types\r\n * emitter.on('entity:created', (event) => {\r\n * console.log(`Entity ${event.entity.name} created`);\r\n * });\r\n *\r\n * // Listen to all events\r\n * emitter.onAny((event) => {\r\n * console.log(`Event: ${event.type}`);\r\n * });\r\n *\r\n * // Emit an event\r\n * emitter.emitEntityCreated(entity);\r\n *\r\n * // Remove listener\r\n * const unsubscribe = emitter.on('entity:deleted', handler);\r\n * unsubscribe();\r\n * ```\r\n */\r\nexport class GraphEventEmitter {\r\n /**\r\n * Map of event types to their registered listeners.\r\n */\r\n private listeners: Map<GraphEventType, Set<GraphEventListener<any>>> = new Map();\r\n\r\n /**\r\n * Listeners that receive all events regardless of type.\r\n */\r\n private wildcardListeners: Set<GraphEventListener<GraphEvent>> = new Set();\r\n\r\n /**\r\n * Whether to suppress errors from listeners (default: true).\r\n * When true, listener errors are logged but don't stop event propagation.\r\n */\r\n private suppressListenerErrors: boolean = true;\r\n\r\n /**\r\n * Create a new GraphEventEmitter instance.\r\n *\r\n * @param options - Optional configuration\r\n */\r\n constructor(options?: { suppressListenerErrors?: boolean }) {\r\n if (options?.suppressListenerErrors !== undefined) {\r\n this.suppressListenerErrors = options.suppressListenerErrors;\r\n }\r\n }\r\n\r\n // ==================== Subscription Methods ====================\r\n\r\n /**\r\n * Subscribe to a specific event type.\r\n *\r\n * @template K - The event type key\r\n * @param eventType - The event type to listen for\r\n * @param listener - Callback function to invoke when event occurs\r\n * @returns Unsubscribe function to remove the listener\r\n *\r\n * @example\r\n * ```typescript\r\n * const unsubscribe = emitter.on('entity:created', (event) => {\r\n * console.log(`Created: ${event.entity.name}`);\r\n * });\r\n *\r\n * // Later: unsubscribe();\r\n * ```\r\n */\r\n on<K extends GraphEventType>(\r\n eventType: K,\r\n listener: GraphEventListener<GraphEventMap[K]>\r\n ): () => void {\r\n if (!this.listeners.has(eventType)) {\r\n this.listeners.set(eventType, new Set());\r\n }\r\n this.listeners.get(eventType)!.add(listener);\r\n\r\n // Return unsubscribe function\r\n return () => {\r\n this.off(eventType, listener);\r\n };\r\n }\r\n\r\n /**\r\n * Unsubscribe from a specific event type.\r\n *\r\n * @template K - The event type key\r\n * @param eventType - The event type to unsubscribe from\r\n * @param listener - The listener function to remove\r\n */\r\n off<K extends GraphEventType>(\r\n eventType: K,\r\n listener: GraphEventListener<GraphEventMap[K]>\r\n ): void {\r\n const listeners = this.listeners.get(eventType);\r\n if (listeners) {\r\n listeners.delete(listener);\r\n }\r\n }\r\n\r\n /**\r\n * Subscribe to all event types.\r\n *\r\n * @param listener - Callback function to invoke for any event\r\n * @returns Unsubscribe function to remove the listener\r\n *\r\n * @example\r\n * ```typescript\r\n * emitter.onAny((event) => {\r\n * console.log(`Event: ${event.type} at ${event.timestamp}`);\r\n * });\r\n * ```\r\n */\r\n onAny(listener: GraphEventListener<GraphEvent>): () => void {\r\n this.wildcardListeners.add(listener);\r\n return () => {\r\n this.offAny(listener);\r\n };\r\n }\r\n\r\n /**\r\n * Unsubscribe from all events.\r\n *\r\n * @param listener - The listener function to remove\r\n */\r\n offAny(listener: GraphEventListener<GraphEvent>): void {\r\n this.wildcardListeners.delete(listener);\r\n }\r\n\r\n /**\r\n * Subscribe to an event type, but only receive the first occurrence.\r\n *\r\n * @template K - The event type key\r\n * @param eventType - The event type to listen for once\r\n * @param listener - Callback function to invoke once\r\n * @returns Unsubscribe function to cancel before event occurs\r\n */\r\n once<K extends GraphEventType>(\r\n eventType: K,\r\n listener: GraphEventListener<GraphEventMap[K]>\r\n ): () => void {\r\n const wrappedListener = ((event: GraphEventMap[K]) => {\r\n this.off(eventType, wrappedListener);\r\n listener(event);\r\n }) as GraphEventListener<GraphEventMap[K]>;\r\n\r\n return this.on(eventType, wrappedListener);\r\n }\r\n\r\n /**\r\n * Remove all listeners for all event types.\r\n */\r\n removeAllListeners(): void {\r\n this.listeners.clear();\r\n this.wildcardListeners.clear();\r\n }\r\n\r\n /**\r\n * Get the count of listeners for a specific event type.\r\n *\r\n * @param eventType - The event type to count listeners for\r\n * @returns Number of listeners registered\r\n */\r\n listenerCount(eventType?: GraphEventType): number {\r\n if (eventType) {\r\n return (this.listeners.get(eventType)?.size ?? 0) + this.wildcardListeners.size;\r\n }\r\n // Count all listeners\r\n let count = this.wildcardListeners.size;\r\n for (const listeners of this.listeners.values()) {\r\n count += listeners.size;\r\n }\r\n return count;\r\n }\r\n\r\n // ==================== Emit Methods ====================\r\n\r\n /**\r\n * Emit an event to all registered listeners.\r\n *\r\n * @param event - The event to emit\r\n */\r\n emit(event: GraphEvent): void {\r\n // Notify type-specific listeners\r\n const typeListeners = this.listeners.get(event.type);\r\n if (typeListeners) {\r\n for (const listener of typeListeners) {\r\n this.invokeListener(listener, event);\r\n }\r\n }\r\n\r\n // Notify wildcard listeners\r\n for (const listener of this.wildcardListeners) {\r\n this.invokeListener(listener, event);\r\n }\r\n }\r\n\r\n /**\r\n * Emit an entity:created event.\r\n *\r\n * @param entity - The entity that was created\r\n */\r\n emitEntityCreated(entity: Entity): void {\r\n const event: EntityCreatedEvent = {\r\n type: 'entity:created',\r\n timestamp: new Date().toISOString(),\r\n entity,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit an entity:updated event.\r\n *\r\n * @param entityName - Name of the updated entity\r\n * @param changes - The changes that were applied\r\n * @param previousValues - Optional previous values before update\r\n */\r\n emitEntityUpdated(\r\n entityName: string,\r\n changes: Partial<Entity>,\r\n previousValues?: Partial<Entity>\r\n ): void {\r\n const event: EntityUpdatedEvent = {\r\n type: 'entity:updated',\r\n timestamp: new Date().toISOString(),\r\n entityName,\r\n changes,\r\n previousValues,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit an entity:deleted event.\r\n *\r\n * @param entityName - Name of the deleted entity\r\n * @param entity - Optional entity data before deletion\r\n */\r\n emitEntityDeleted(entityName: string, entity?: Entity): void {\r\n const event: EntityDeletedEvent = {\r\n type: 'entity:deleted',\r\n timestamp: new Date().toISOString(),\r\n entityName,\r\n entity,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit a relation:created event.\r\n *\r\n * @param relation - The relation that was created\r\n */\r\n emitRelationCreated(relation: Relation): void {\r\n const event: RelationCreatedEvent = {\r\n type: 'relation:created',\r\n timestamp: new Date().toISOString(),\r\n relation,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit a relation:deleted event.\r\n *\r\n * @param from - Source entity name\r\n * @param to - Target entity name\r\n * @param relationType - Type of the deleted relation\r\n */\r\n emitRelationDeleted(from: string, to: string, relationType: string): void {\r\n const event: RelationDeletedEvent = {\r\n type: 'relation:deleted',\r\n timestamp: new Date().toISOString(),\r\n from,\r\n to,\r\n relationType,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit an observation:added event.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param observations - Observations that were added\r\n */\r\n emitObservationAdded(entityName: string, observations: string[]): void {\r\n if (observations.length === 0) return;\r\n\r\n const event: ObservationAddedEvent = {\r\n type: 'observation:added',\r\n timestamp: new Date().toISOString(),\r\n entityName,\r\n observations,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit an observation:deleted event.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param observations - Observations that were deleted\r\n */\r\n emitObservationDeleted(entityName: string, observations: string[]): void {\r\n if (observations.length === 0) return;\r\n\r\n const event: ObservationDeletedEvent = {\r\n type: 'observation:deleted',\r\n timestamp: new Date().toISOString(),\r\n entityName,\r\n observations,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit a graph:saved event.\r\n *\r\n * @param entityCount - Number of entities in the saved graph\r\n * @param relationCount - Number of relations in the saved graph\r\n */\r\n emitGraphSaved(entityCount: number, relationCount: number): void {\r\n const event: GraphSavedEvent = {\r\n type: 'graph:saved',\r\n timestamp: new Date().toISOString(),\r\n entityCount,\r\n relationCount,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n /**\r\n * Emit a graph:loaded event.\r\n *\r\n * @param entityCount - Number of entities in the loaded graph\r\n * @param relationCount - Number of relations in the loaded graph\r\n */\r\n emitGraphLoaded(entityCount: number, relationCount: number): void {\r\n const event: GraphLoadedEvent = {\r\n type: 'graph:loaded',\r\n timestamp: new Date().toISOString(),\r\n entityCount,\r\n relationCount,\r\n };\r\n this.emit(event);\r\n }\r\n\r\n // ==================== Helper Methods ====================\r\n\r\n /**\r\n * Safely invoke a listener, optionally catching errors.\r\n * @private\r\n */\r\n private invokeListener(listener: GraphEventListener<any>, event: GraphEvent): void {\r\n if (this.suppressListenerErrors) {\r\n try {\r\n listener(event);\r\n } catch (error) {\r\n // Log but don't propagate errors from listeners\r\n console.error(`GraphEventEmitter: Listener error for ${event.type}:`, error);\r\n }\r\n } else {\r\n listener(event);\r\n }\r\n }\r\n}\r\n","/**\r\n * Graph Storage\r\n *\r\n * Handles file I/O operations for the knowledge graph using JSONL format.\r\n * Implements IGraphStorage interface for storage abstraction.\r\n *\r\n * @module core/GraphStorage\r\n */\r\n\r\nimport { promises as fs } from 'fs';\r\nimport { Mutex } from 'async-mutex';\r\nimport type { KnowledgeGraph, Entity, Relation, ReadonlyKnowledgeGraph, IGraphStorage, LowercaseData } from '../types/index.js';\r\nimport { clearAllSearchCaches } from '../utils/searchCache.js';\r\nimport { NameIndex, TypeIndex, LowercaseCache, RelationIndex, ObservationIndex } from '../utils/indexes.js';\r\nimport { sanitizeObject, validateFilePath } from '../utils/index.js';\r\nimport { BatchTransaction } from './TransactionManager.js';\r\nimport { GraphEventEmitter } from './GraphEventEmitter.js';\r\n\r\n/**\r\n * GraphStorage manages persistence of the knowledge graph to disk.\r\n *\r\n * Uses JSONL (JSON Lines) format where each line is a separate JSON object\r\n * representing either an entity or a relation.\r\n *\r\n * OPTIMIZED: Implements in-memory caching to avoid repeated disk reads.\r\n * Cache is invalidated on every write operation to ensure consistency.\r\n *\r\n * @example\r\n * ```typescript\r\n * const storage = new GraphStorage('/path/to/memory.jsonl');\r\n * const graph = await storage.loadGraph();\r\n * graph.entities.push(newEntity);\r\n * await storage.saveGraph(graph);\r\n * ```\r\n */\r\nexport class GraphStorage implements IGraphStorage {\r\n /**\r\n * Mutex for thread-safe access to storage operations.\r\n * Prevents concurrent writes from corrupting the file or cache.\r\n */\r\n private mutex = new Mutex();\r\n\r\n /**\r\n * In-memory cache of the knowledge graph.\r\n * Null when cache is empty or invalidated.\r\n */\r\n private cache: KnowledgeGraph | null = null;\r\n\r\n /**\r\n * Number of pending append operations since last compaction.\r\n * Used to trigger automatic compaction when threshold is reached.\r\n */\r\n private pendingAppends: number = 0;\r\n\r\n /**\r\n * Dynamic threshold for automatic compaction.\r\n *\r\n * Returns the larger of 100 or 10% of the current entity count.\r\n * This scales with graph size to avoid too-frequent compaction on large graphs\r\n * while maintaining a reasonable minimum for small graphs.\r\n *\r\n * @returns Compaction threshold value\r\n */\r\n private get compactionThreshold(): number {\r\n return Math.max(100, Math.floor((this.cache?.entities.length ?? 0) * 0.1));\r\n }\r\n\r\n /**\r\n * O(1) entity lookup by name.\r\n */\r\n private nameIndex: NameIndex = new NameIndex();\r\n\r\n /**\r\n * O(1) entity lookup by type.\r\n */\r\n private typeIndex: TypeIndex = new TypeIndex();\r\n\r\n /**\r\n * Pre-computed lowercase strings for search optimization.\r\n */\r\n private lowercaseCache: LowercaseCache = new LowercaseCache();\r\n\r\n /**\r\n * O(1) relation lookup by entity name.\r\n */\r\n private relationIndex: RelationIndex = new RelationIndex();\r\n\r\n /**\r\n * O(1) observation word lookup by entity.\r\n * Maps words in observations to entity names.\r\n */\r\n private observationIndex: ObservationIndex = new ObservationIndex();\r\n\r\n /**\r\n * Phase 10 Sprint 2: Event emitter for graph change notifications.\r\n * Allows external systems to subscribe to graph changes.\r\n */\r\n private eventEmitter: GraphEventEmitter = new GraphEventEmitter();\r\n\r\n /**\r\n * Validated file path (after path traversal checks).\r\n */\r\n private memoryFilePath: string;\r\n\r\n /**\r\n * Create a new GraphStorage instance.\r\n *\r\n * @param memoryFilePath - Absolute path to the JSONL file\r\n * @throws {FileOperationError} If path traversal is detected\r\n */\r\n constructor(memoryFilePath: string) {\r\n // Security: Validate path to prevent path traversal attacks\r\n this.memoryFilePath = validateFilePath(memoryFilePath);\r\n }\r\n\r\n // ==================== Phase 10 Sprint 2: Event Emitter Access ====================\r\n\r\n /**\r\n * Get the event emitter for subscribing to graph changes.\r\n *\r\n * @returns GraphEventEmitter instance\r\n *\r\n * @example\r\n * ```typescript\r\n * const storage = new GraphStorage('/data/memory.jsonl');\r\n *\r\n * // Subscribe to entity creation events\r\n * storage.events.on('entity:created', (event) => {\r\n * console.log(`Entity ${event.entity.name} created`);\r\n * });\r\n *\r\n * // Subscribe to all events\r\n * storage.events.onAny((event) => {\r\n * console.log(`Graph event: ${event.type}`);\r\n * });\r\n * ```\r\n */\r\n get events(): GraphEventEmitter {\r\n return this.eventEmitter;\r\n }\r\n\r\n // ==================== Durable File Operations ====================\r\n\r\n /**\r\n * Write content to file with fsync for durability.\r\n *\r\n * @param content - Content to write\r\n */\r\n private async durableWriteFile(content: string): Promise<void> {\r\n const fd = await fs.open(this.memoryFilePath, 'w');\r\n try {\r\n await fd.write(content);\r\n await fd.sync();\r\n } finally {\r\n await fd.close();\r\n }\r\n }\r\n\r\n /**\r\n * Append content to file with fsync for durability.\r\n *\r\n * @param content - Content to append\r\n * @param prependNewline - Whether to prepend a newline\r\n */\r\n private async durableAppendFile(content: string, prependNewline: boolean): Promise<void> {\r\n const fd = await fs.open(this.memoryFilePath, 'a');\r\n try {\r\n const dataToWrite = prependNewline ? '\\n' + content : content;\r\n await fd.write(dataToWrite);\r\n await fd.sync();\r\n } finally {\r\n await fd.close();\r\n }\r\n }\r\n\r\n /**\r\n * Load the knowledge graph from disk (read-only access).\r\n *\r\n * OPTIMIZED: Returns cached reference directly without copying.\r\n * This is O(1) regardless of graph size. For mutation operations,\r\n * use getGraphForMutation() instead.\r\n *\r\n * @returns Promise resolving to read-only knowledge graph reference\r\n * @throws Error if file exists but cannot be read or parsed\r\n */\r\n async loadGraph(): Promise<ReadonlyKnowledgeGraph> {\r\n // Return cached graph directly (no copying - O(1))\r\n if (this.cache !== null) {\r\n return this.cache;\r\n }\r\n\r\n // Cache miss - load from disk\r\n await this.loadFromDisk();\r\n return this.cache!;\r\n }\r\n\r\n /**\r\n * Get a mutable copy of the graph for write operations.\r\n *\r\n * Creates deep copies of entity and relation arrays to allow\r\n * safe mutation without affecting the cached data.\r\n *\r\n * @returns Promise resolving to mutable knowledge graph copy\r\n */\r\n async getGraphForMutation(): Promise<KnowledgeGraph> {\r\n await this.ensureLoaded();\r\n return {\r\n entities: this.cache!.entities.map(e => ({\r\n ...e,\r\n observations: [...e.observations],\r\n tags: e.tags ? [...e.tags] : undefined,\r\n })),\r\n relations: this.cache!.relations.map(r => ({ ...r })),\r\n };\r\n }\r\n\r\n /**\r\n * Ensure the cache is loaded from disk.\r\n *\r\n * @returns Promise resolving when cache is populated\r\n */\r\n async ensureLoaded(): Promise<void> {\r\n if (this.cache === null) {\r\n await this.loadFromDisk();\r\n }\r\n }\r\n\r\n /**\r\n * Internal method to load graph from disk into cache.\r\n */\r\n private async loadFromDisk(): Promise<void> {\r\n try {\r\n const data = await fs.readFile(this.memoryFilePath, 'utf-8');\r\n const lines = data.split('\\n').filter((line: string) => line.trim() !== '');\r\n\r\n // Use Maps to deduplicate - later entries override earlier ones\r\n // This supports append-only updates where new versions are appended\r\n const entityMap = new Map<string, Entity>();\r\n const relationMap = new Map<string, Relation>();\r\n\r\n for (const line of lines) {\r\n const item = JSON.parse(line);\r\n\r\n if (item.type === 'entity') {\r\n // Add createdAt if missing for backward compatibility\r\n if (!item.createdAt) item.createdAt = new Date().toISOString();\r\n // Add lastModified if missing for backward compatibility\r\n if (!item.lastModified) item.lastModified = item.createdAt;\r\n\r\n // Use name as key - later entries override earlier ones\r\n entityMap.set(item.name, item as Entity);\r\n }\r\n\r\n if (item.type === 'relation') {\r\n // Add createdAt if missing for backward compatibility\r\n if (!item.createdAt) item.createdAt = new Date().toISOString();\r\n // Add lastModified if missing for backward compatibility\r\n if (!item.lastModified) item.lastModified = item.createdAt;\r\n\r\n // Use composite key for relations\r\n const key = `${item.from}:${item.to}:${item.relationType}`;\r\n relationMap.set(key, item as Relation);\r\n }\r\n }\r\n\r\n // Convert maps to arrays\r\n const graph: KnowledgeGraph = {\r\n entities: Array.from(entityMap.values()),\r\n relations: Array.from(relationMap.values()),\r\n };\r\n\r\n // Populate cache\r\n this.cache = graph;\r\n\r\n // Build indexes from loaded data\r\n this.buildEntityIndexes(graph.entities);\r\n this.buildRelationIndex(graph.relations);\r\n\r\n // Phase 10 Sprint 2: Emit graph:loaded event\r\n this.eventEmitter.emitGraphLoaded(graph.entities.length, graph.relations.length);\r\n } catch (error) {\r\n // File doesn't exist - create empty graph\r\n if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {\r\n this.cache = { entities: [], relations: [] };\r\n this.clearIndexes();\r\n\r\n // Phase 10 Sprint 2: Emit graph:loaded event for empty graph\r\n this.eventEmitter.emitGraphLoaded(0, 0);\r\n return;\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Build all entity indexes from entity array.\r\n */\r\n private buildEntityIndexes(entities: Entity[]): void {\r\n this.nameIndex.build(entities);\r\n this.typeIndex.build(entities);\r\n this.lowercaseCache.build(entities);\r\n\r\n // Build observation index\r\n this.observationIndex.clear();\r\n for (const entity of entities) {\r\n this.observationIndex.add(entity.name, entity.observations);\r\n }\r\n }\r\n\r\n /**\r\n * Build relation index from relation array.\r\n */\r\n private buildRelationIndex(relations: Relation[]): void {\r\n this.relationIndex.build(relations);\r\n }\r\n\r\n /**\r\n * Clear all indexes.\r\n */\r\n private clearIndexes(): void {\r\n this.nameIndex.clear();\r\n this.typeIndex.clear();\r\n this.lowercaseCache.clear();\r\n this.relationIndex.clear();\r\n this.observationIndex.clear();\r\n }\r\n\r\n /**\r\n * Save the knowledge graph to disk.\r\n *\r\n * OPTIMIZED: Updates cache directly after write to avoid re-reading.\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n *\r\n * Writes the graph to JSONL format, with one JSON object per line.\r\n *\r\n * @param graph - The knowledge graph to save\r\n * @returns Promise resolving when save is complete\r\n * @throws Error if file cannot be written\r\n */\r\n async saveGraph(graph: KnowledgeGraph): Promise<void> {\r\n return this.mutex.runExclusive(async () => {\r\n await this.saveGraphInternal(graph);\r\n });\r\n }\r\n\r\n /**\r\n * Append a single entity to the file (O(1) write operation).\r\n *\r\n * OPTIMIZED: Uses file append instead of full rewrite.\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n * Updates cache in-place and triggers compaction when threshold is reached.\r\n *\r\n * @param entity - The entity to append\r\n * @returns Promise resolving when append is complete\r\n */\r\n async appendEntity(entity: Entity): Promise<void> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n const entityData: Record<string, unknown> = {\r\n type: 'entity',\r\n name: entity.name,\r\n entityType: entity.entityType,\r\n observations: entity.observations,\r\n createdAt: entity.createdAt,\r\n lastModified: entity.lastModified,\r\n };\r\n\r\n // Only include optional fields if they exist\r\n if (entity.tags !== undefined) entityData.tags = entity.tags;\r\n if (entity.importance !== undefined) entityData.importance = entity.importance;\r\n if (entity.parentId !== undefined) entityData.parentId = entity.parentId;\r\n\r\n const line = JSON.stringify(entityData);\r\n\r\n // Append to file with fsync for durability (write FIRST, then update cache)\r\n try {\r\n const stat = await fs.stat(this.memoryFilePath);\r\n await this.durableAppendFile(line, stat.size > 0);\r\n } catch (error) {\r\n // File doesn't exist - create it\r\n if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {\r\n await this.durableWriteFile(line);\r\n } else {\r\n throw error;\r\n }\r\n }\r\n\r\n // Update cache in-place (after successful file write)\r\n this.cache!.entities.push(entity);\r\n\r\n // Update indexes\r\n this.nameIndex.add(entity);\r\n this.typeIndex.add(entity);\r\n this.lowercaseCache.set(entity);\r\n this.observationIndex.add(entity.name, entity.observations);\r\n\r\n this.pendingAppends++;\r\n\r\n // Clear search caches\r\n clearAllSearchCaches();\r\n\r\n // Phase 10 Sprint 2: Emit entity:created event\r\n this.eventEmitter.emitEntityCreated(entity);\r\n\r\n // Trigger compaction if threshold reached\r\n if (this.pendingAppends >= this.compactionThreshold) {\r\n await this.compactInternal();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Append a single relation to the file (O(1) write operation).\r\n *\r\n * OPTIMIZED: Uses file append instead of full rewrite.\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n * Updates cache in-place and triggers compaction when threshold is reached.\r\n *\r\n * @param relation - The relation to append\r\n * @returns Promise resolving when append is complete\r\n */\r\n async appendRelation(relation: Relation): Promise<void> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n // Serialize relation with all fields (Phase 1 Sprint 5: Metadata support)\r\n const serialized: Record<string, unknown> = {\r\n type: 'relation',\r\n from: relation.from,\r\n to: relation.to,\r\n relationType: relation.relationType,\r\n createdAt: relation.createdAt,\r\n lastModified: relation.lastModified,\r\n };\r\n // Only include optional metadata fields if present\r\n if (relation.weight !== undefined) serialized.weight = relation.weight;\r\n if (relation.confidence !== undefined) serialized.confidence = relation.confidence;\r\n if (relation.properties) serialized.properties = relation.properties;\r\n if (relation.metadata) serialized.metadata = relation.metadata;\r\n const line = JSON.stringify(serialized);\r\n\r\n // Append to file with fsync for durability (write FIRST, then update cache)\r\n try {\r\n const stat = await fs.stat(this.memoryFilePath);\r\n await this.durableAppendFile(line, stat.size > 0);\r\n } catch (error) {\r\n // File doesn't exist - create it\r\n if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {\r\n await this.durableWriteFile(line);\r\n } else {\r\n throw error;\r\n }\r\n }\r\n\r\n // Update cache in-place (after successful file write)\r\n this.cache!.relations.push(relation);\r\n\r\n // Update relation index\r\n this.relationIndex.add(relation);\r\n\r\n this.pendingAppends++;\r\n\r\n // Clear search caches\r\n clearAllSearchCaches();\r\n\r\n // Phase 10 Sprint 2: Emit relation:created event\r\n this.eventEmitter.emitRelationCreated(relation);\r\n\r\n // Trigger compaction if threshold reached\r\n if (this.pendingAppends >= this.compactionThreshold) {\r\n await this.compactInternal();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Compact the file by rewriting it with only current cache contents.\r\n *\r\n * THREAD-SAFE: Uses mutex to prevent concurrent operations.\r\n * Removes duplicate entries and cleans up the file.\r\n * Resets pending appends counter.\r\n *\r\n * @returns Promise resolving when compaction is complete\r\n */\r\n async compact(): Promise<void> {\r\n return this.mutex.runExclusive(async () => {\r\n await this.compactInternal();\r\n });\r\n }\r\n\r\n /**\r\n * Internal compact implementation (must be called within mutex).\r\n *\r\n * @returns Promise resolving when compaction is complete\r\n */\r\n private async compactInternal(): Promise<void> {\r\n if (this.cache === null) {\r\n return;\r\n }\r\n\r\n // Rewrite file with current cache (removes duplicates/updates)\r\n await this.saveGraphInternal(this.cache);\r\n this.pendingAppends = 0;\r\n }\r\n\r\n /**\r\n * Internal saveGraph implementation (must be called within mutex).\r\n *\r\n * @param graph - The knowledge graph to save\r\n * @returns Promise resolving when save is complete\r\n */\r\n private async saveGraphInternal(graph: KnowledgeGraph): Promise<void> {\r\n const lines = [\r\n ...graph.entities.map(e => {\r\n const entityData: Record<string, unknown> = {\r\n type: 'entity',\r\n name: e.name,\r\n entityType: e.entityType,\r\n observations: e.observations,\r\n createdAt: e.createdAt,\r\n lastModified: e.lastModified,\r\n };\r\n\r\n // Only include optional fields if they exist\r\n if (e.tags !== undefined) entityData.tags = e.tags;\r\n if (e.importance !== undefined) entityData.importance = e.importance;\r\n if (e.parentId !== undefined) entityData.parentId = e.parentId;\r\n\r\n return JSON.stringify(entityData);\r\n }),\r\n // Serialize relations with metadata (Phase 1 Sprint 5)\r\n ...graph.relations.map(r => {\r\n const relationData: Record<string, unknown> = {\r\n type: 'relation',\r\n from: r.from,\r\n to: r.to,\r\n relationType: r.relationType,\r\n createdAt: r.createdAt,\r\n lastModified: r.lastModified,\r\n };\r\n // Only include optional metadata fields if they exist\r\n if (r.weight !== undefined) relationData.weight = r.weight;\r\n if (r.confidence !== undefined) relationData.confidence = r.confidence;\r\n if (r.properties) relationData.properties = r.properties;\r\n if (r.metadata) relationData.metadata = r.metadata;\r\n return JSON.stringify(relationData);\r\n }),\r\n ];\r\n\r\n await this.durableWriteFile(lines.join('\\n'));\r\n\r\n // Update cache directly with the saved graph (avoid re-reading from disk)\r\n this.cache = graph;\r\n\r\n // Rebuild indexes with new graph data\r\n this.buildEntityIndexes(graph.entities);\r\n this.buildRelationIndex(graph.relations);\r\n\r\n // Reset pending appends since file is now clean\r\n this.pendingAppends = 0;\r\n\r\n // Clear all search caches since graph data has changed\r\n clearAllSearchCaches();\r\n\r\n // Phase 10 Sprint 2: Emit graph:saved event\r\n this.eventEmitter.emitGraphSaved(graph.entities.length, graph.relations.length);\r\n }\r\n\r\n /**\r\n * Get the current pending appends count.\r\n *\r\n * Useful for testing compaction behavior.\r\n *\r\n * @returns Number of pending appends since last compaction\r\n */\r\n getPendingAppends(): number {\r\n return this.pendingAppends;\r\n }\r\n\r\n /**\r\n * Update an entity in-place in the cache and append to file.\r\n *\r\n * OPTIMIZED: Modifies cache directly and appends updated version to file.\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n * Does not rewrite the entire file - compaction handles deduplication later.\r\n *\r\n * @param entityName - Name of the entity to update\r\n * @param updates - Partial entity updates to apply\r\n * @returns Promise resolving to true if entity was found and updated, false otherwise\r\n */\r\n async updateEntity(entityName: string, updates: Partial<Entity>): Promise<boolean> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n const entityIndex = this.cache!.entities.findIndex(e => e.name === entityName);\r\n if (entityIndex === -1) {\r\n return false;\r\n }\r\n\r\n const entity = this.cache!.entities[entityIndex];\r\n const oldType = entity.entityType;\r\n const timestamp = new Date().toISOString();\r\n\r\n // Phase 10 Sprint 2: Capture previous values for event\r\n const previousValues: Partial<Entity> = {};\r\n for (const key of Object.keys(updates) as Array<keyof Entity>) {\r\n if (key in entity) {\r\n previousValues[key] = entity[key] as any;\r\n }\r\n }\r\n\r\n // Build the updated entity data for file write BEFORE modifying cache\r\n // This ensures cache consistency if file write fails\r\n const updatedEntity = {\r\n ...entity,\r\n ...updates,\r\n lastModified: timestamp,\r\n };\r\n\r\n const entityData: Record<string, unknown> = {\r\n type: 'entity',\r\n name: updatedEntity.name,\r\n entityType: updatedEntity.entityType,\r\n observations: updatedEntity.observations,\r\n createdAt: updatedEntity.createdAt,\r\n lastModified: updatedEntity.lastModified,\r\n };\r\n\r\n if (updatedEntity.tags !== undefined) entityData.tags = updatedEntity.tags;\r\n if (updatedEntity.importance !== undefined) entityData.importance = updatedEntity.importance;\r\n if (updatedEntity.parentId !== undefined) entityData.parentId = updatedEntity.parentId;\r\n\r\n const line = JSON.stringify(entityData);\r\n\r\n // Write to file FIRST with durability - if this fails, cache remains consistent\r\n try {\r\n const stat = await fs.stat(this.memoryFilePath);\r\n await this.durableAppendFile(line, stat.size > 0);\r\n } catch (error) {\r\n if (error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT') {\r\n await this.durableWriteFile(line);\r\n } else {\r\n throw error;\r\n }\r\n }\r\n\r\n // File write succeeded - NOW update cache in-place (sanitized to prevent prototype pollution)\r\n Object.assign(entity, sanitizeObject(updates as Record<string, unknown>));\r\n entity.lastModified = timestamp;\r\n\r\n // Update indexes\r\n this.nameIndex.add(entity); // Update reference\r\n if (updates.entityType && updates.entityType !== oldType) {\r\n this.typeIndex.updateType(entityName, oldType, updates.entityType);\r\n }\r\n this.lowercaseCache.set(entity); // Recompute lowercase\r\n if (updates.observations) {\r\n this.observationIndex.remove(entityName); // Remove old observations\r\n this.observationIndex.add(entityName, entity.observations); // Add new observations\r\n }\r\n\r\n this.pendingAppends++;\r\n\r\n // Clear search caches\r\n clearAllSearchCaches();\r\n\r\n // Phase 10 Sprint 2: Emit entity:updated event\r\n this.eventEmitter.emitEntityUpdated(entityName, updates, previousValues);\r\n\r\n // Trigger compaction if threshold reached\r\n if (this.pendingAppends >= this.compactionThreshold) {\r\n await this.compactInternal();\r\n }\r\n\r\n return true;\r\n });\r\n }\r\n\r\n /**\r\n * Manually clear the cache.\r\n *\r\n * Useful for testing or when external processes modify the file.\r\n *\r\n * @returns void\r\n */\r\n clearCache(): void {\r\n this.cache = null;\r\n this.clearIndexes();\r\n }\r\n\r\n /**\r\n * Get the file path being used for storage.\r\n *\r\n * @returns The memory file path\r\n */\r\n getFilePath(): string {\r\n return this.memoryFilePath;\r\n }\r\n\r\n // ==================== Index Accessors ====================\r\n\r\n /**\r\n * Get an entity by name in O(1) time.\r\n *\r\n * OPTIMIZED: Uses NameIndex for constant-time lookup.\r\n *\r\n * @param name - Entity name to look up\r\n * @returns Entity if found, undefined otherwise\r\n */\r\n getEntityByName(name: string): Entity | undefined {\r\n return this.nameIndex.get(name);\r\n }\r\n\r\n /**\r\n * Check if an entity exists by name in O(1) time.\r\n *\r\n * @param name - Entity name to check\r\n * @returns True if entity exists\r\n */\r\n hasEntity(name: string): boolean {\r\n return this.nameIndex.has(name);\r\n }\r\n\r\n /**\r\n * Get all entities of a given type in O(1) time.\r\n *\r\n * OPTIMIZED: Uses TypeIndex for constant-time lookup of entity names,\r\n * then uses NameIndex for O(1) entity retrieval.\r\n *\r\n * @param entityType - Entity type to filter by (case-insensitive)\r\n * @returns Array of entities with the given type\r\n */\r\n getEntitiesByType(entityType: string): Entity[] {\r\n const names = this.typeIndex.getNames(entityType);\r\n const entities: Entity[] = [];\r\n for (const name of names) {\r\n const entity = this.nameIndex.get(name);\r\n if (entity) {\r\n entities.push(entity);\r\n }\r\n }\r\n return entities;\r\n }\r\n\r\n /**\r\n * Get pre-computed lowercase data for an entity.\r\n *\r\n * OPTIMIZED: Avoids repeated toLowerCase() calls during search.\r\n *\r\n * @param entityName - Entity name to get lowercase data for\r\n * @returns LowercaseData if entity exists, undefined otherwise\r\n */\r\n getLowercased(entityName: string): LowercaseData | undefined {\r\n return this.lowercaseCache.get(entityName);\r\n }\r\n\r\n /**\r\n * Get all unique entity types in the graph.\r\n *\r\n * @returns Array of unique entity types (lowercase)\r\n */\r\n getEntityTypes(): string[] {\r\n return this.typeIndex.getTypes();\r\n }\r\n\r\n // ==================== Relation Index Accessors ====================\r\n\r\n /**\r\n * Get all relations where the entity is the source (outgoing relations) in O(1) time.\r\n *\r\n * OPTIMIZED: Uses RelationIndex for constant-time lookup.\r\n *\r\n * @param entityName - Entity name to look up outgoing relations for\r\n * @returns Array of relations where entity is the source\r\n */\r\n getRelationsFrom(entityName: string): Relation[] {\r\n return this.relationIndex.getRelationsFrom(entityName);\r\n }\r\n\r\n /**\r\n * Get all relations where the entity is the target (incoming relations) in O(1) time.\r\n *\r\n * OPTIMIZED: Uses RelationIndex for constant-time lookup.\r\n *\r\n * @param entityName - Entity name to look up incoming relations for\r\n * @returns Array of relations where entity is the target\r\n */\r\n getRelationsTo(entityName: string): Relation[] {\r\n return this.relationIndex.getRelationsTo(entityName);\r\n }\r\n\r\n /**\r\n * Get all relations involving the entity (both incoming and outgoing) in O(1) time.\r\n *\r\n * OPTIMIZED: Uses RelationIndex for constant-time lookup.\r\n *\r\n * @param entityName - Entity name to look up all relations for\r\n * @returns Array of all relations involving the entity\r\n */\r\n getRelationsFor(entityName: string): Relation[] {\r\n return this.relationIndex.getRelationsFor(entityName);\r\n }\r\n\r\n /**\r\n * Check if an entity has any relations.\r\n *\r\n * @param entityName - Entity name to check\r\n * @returns True if entity has any relations\r\n */\r\n hasRelations(entityName: string): boolean {\r\n return this.relationIndex.hasRelations(entityName);\r\n }\r\n\r\n // ==================== Observation Index Accessors ====================\r\n\r\n /**\r\n * Get entities that have observations containing the given word.\r\n * Uses the observation index for O(1) lookup.\r\n *\r\n * OPTIMIZED: Uses ObservationIndex for constant-time lookup instead of\r\n * linear scan through all entities and their observations.\r\n *\r\n * @param word - Word to search for in observations\r\n * @returns Set of entity names\r\n */\r\n getEntitiesByObservationWord(word: string): Set<string> {\r\n return this.observationIndex.getEntitiesWithWord(word);\r\n }\r\n\r\n /**\r\n * Get entities that have observations containing ANY of the given words (union).\r\n * Uses the observation index for O(1) lookup per word.\r\n *\r\n * OPTIMIZED: Uses ObservationIndex for constant-time lookups.\r\n *\r\n * @param words - Array of words to search for\r\n * @returns Set of entity names containing any of the words\r\n */\r\n getEntitiesByAnyObservationWord(words: string[]): Set<string> {\r\n return this.observationIndex.getEntitiesWithAnyWord(words);\r\n }\r\n\r\n /**\r\n * Get entities that have observations containing ALL of the given words (intersection).\r\n * Uses the observation index for O(1) lookup per word.\r\n *\r\n * OPTIMIZED: Uses ObservationIndex for constant-time lookups and set intersection.\r\n *\r\n * @param words - Array of words that must all be present\r\n * @returns Set of entity names containing all of the words\r\n */\r\n getEntitiesByAllObservationWords(words: string[]): Set<string> {\r\n return this.observationIndex.getEntitiesWithAllWords(words);\r\n }\r\n\r\n /**\r\n * Get statistics about the observation index.\r\n *\r\n * @returns Object with wordCount and entityCount\r\n */\r\n getObservationIndexStats(): { wordCount: number; entityCount: number } {\r\n return this.observationIndex.getStats();\r\n }\r\n\r\n // ==================== Phase 10 Sprint 1: Transaction Factory ====================\r\n\r\n /**\r\n * Create a new batch transaction for atomic operations.\r\n *\r\n * Returns a BatchTransaction instance that can be used to queue multiple\r\n * operations and execute them atomically with a single save operation.\r\n *\r\n * @returns A new BatchTransaction instance\r\n *\r\n * @example\r\n * ```typescript\r\n * const storage = new GraphStorage('/data/memory.jsonl');\r\n *\r\n * // Create and execute a batch transaction\r\n * const result = await storage.transaction()\r\n * .createEntity({ name: 'Alice', entityType: 'person', observations: ['Developer'] })\r\n * .createEntity({ name: 'Bob', entityType: 'person', observations: ['Designer'] })\r\n * .createRelation({ from: 'Alice', to: 'Bob', relationType: 'knows' })\r\n * .execute();\r\n *\r\n * console.log(`Batch completed: ${result.operationsExecuted} operations`);\r\n * ```\r\n */\r\n transaction(): BatchTransaction {\r\n return new BatchTransaction(this);\r\n }\r\n}\r\n","/**\r\n * SQLite Storage\r\n *\r\n * Handles storage operations for the knowledge graph using better-sqlite3 (native SQLite).\r\n * Implements IGraphStorage interface for storage abstraction.\r\n *\r\n * Benefits over sql.js (WASM):\r\n * - 3-10x faster than WASM-based SQLite\r\n * - Native FTS5 full-text search support\r\n * - ACID transactions with proper durability\r\n * - Concurrent read access support\r\n * - No memory overhead from WASM runtime\r\n * - Direct disk I/O (no manual export/import)\r\n *\r\n * Features:\r\n * - Built-in indexes for O(1) lookups\r\n * - Referential integrity with ON DELETE CASCADE\r\n * - FTS5 full-text search on entity names and observations\r\n *\r\n * @module core/SQLiteStorage\r\n */\r\n\r\nimport Database from 'better-sqlite3';\r\nimport type { Database as DatabaseType } from 'better-sqlite3';\r\nimport { Mutex } from 'async-mutex';\r\nimport type { KnowledgeGraph, Entity, Relation, ReadonlyKnowledgeGraph, IGraphStorage, LowercaseData } from '../types/index.js';\r\nimport { clearAllSearchCaches } from '../utils/searchCache.js';\r\nimport { NameIndex, TypeIndex } from '../utils/indexes.js';\r\nimport { sanitizeObject, validateFilePath } from '../utils/index.js';\r\n\r\n/**\r\n * SQLiteStorage manages persistence of the knowledge graph using native SQLite.\r\n *\r\n * Uses better-sqlite3 for native SQLite bindings with full FTS5 support,\r\n * referential integrity, and proper ACID transactions.\r\n *\r\n * @example\r\n * ```typescript\r\n * const storage = new SQLiteStorage('/path/to/memory.db');\r\n * await storage.ensureLoaded();\r\n * const graph = await storage.loadGraph();\r\n * ```\r\n */\r\nexport class SQLiteStorage implements IGraphStorage {\r\n /**\r\n * Mutex for thread-safe access to storage operations.\r\n * Prevents concurrent writes from corrupting the cache.\r\n * Note: SQLite itself handles file-level locking, but we need\r\n * to protect our in-memory cache and index operations.\r\n */\r\n private mutex = new Mutex();\r\n\r\n /**\r\n * SQLite database instance.\r\n */\r\n private db: DatabaseType | null = null;\r\n\r\n /**\r\n * Whether the database has been initialized.\r\n */\r\n private initialized: boolean = false;\r\n\r\n /**\r\n * In-memory cache for fast read operations.\r\n * Synchronized with SQLite on writes.\r\n */\r\n private cache: KnowledgeGraph | null = null;\r\n\r\n /**\r\n * O(1) entity lookup by name.\r\n */\r\n private nameIndex: NameIndex = new NameIndex();\r\n\r\n /**\r\n * O(1) entity lookup by type.\r\n */\r\n private typeIndex: TypeIndex = new TypeIndex();\r\n\r\n /**\r\n * Pre-computed lowercase data for search optimization.\r\n */\r\n private lowercaseCache: Map<string, LowercaseData> = new Map();\r\n\r\n /**\r\n * Pending changes counter for batching disk writes.\r\n * Note: better-sqlite3 writes to disk immediately, but we track for API compatibility.\r\n */\r\n private pendingChanges: number = 0;\r\n\r\n /**\r\n * Phase 4 Sprint 1: Bidirectional relation cache for O(1) repeated lookups.\r\n * Maps entity name -> all relations involving that entity (both incoming and outgoing).\r\n */\r\n private bidirectionalRelationCache: Map<string, Relation[]> = new Map();\r\n\r\n /**\r\n * Validated database file path (after path traversal checks).\r\n */\r\n private readonly validatedDbFilePath: string;\r\n\r\n /**\r\n * Create a new SQLiteStorage instance.\r\n *\r\n * @param dbFilePath - Absolute path to the SQLite database file\r\n * @throws {FileOperationError} If path traversal is detected\r\n */\r\n constructor(dbFilePath: string) {\r\n // Security: Validate path to prevent path traversal attacks\r\n this.validatedDbFilePath = validateFilePath(dbFilePath);\r\n }\r\n\r\n /**\r\n * Initialize the database connection and schema.\r\n */\r\n private initialize(): void {\r\n if (this.initialized) return;\r\n\r\n // Open database (creates file if it doesn't exist)\r\n this.db = new Database(this.validatedDbFilePath);\r\n\r\n // Enable foreign keys and WAL mode for better performance\r\n this.db.pragma('foreign_keys = ON');\r\n this.db.pragma('journal_mode = WAL');\r\n\r\n // Create tables and indexes\r\n this.createTables();\r\n\r\n // Load cache from database\r\n this.loadCache();\r\n\r\n this.initialized = true;\r\n }\r\n\r\n /**\r\n * Create database tables, indexes, and FTS5 virtual table.\r\n */\r\n private createTables(): void {\r\n if (!this.db) throw new Error('Database not initialized');\r\n\r\n // Entities table with referential integrity for parentId\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS entities (\r\n name TEXT PRIMARY KEY,\r\n entityType TEXT NOT NULL,\r\n observations TEXT NOT NULL,\r\n tags TEXT,\r\n importance INTEGER,\r\n parentId TEXT REFERENCES entities(name) ON DELETE SET NULL,\r\n createdAt TEXT NOT NULL,\r\n lastModified TEXT NOT NULL\r\n )\r\n `);\r\n\r\n // Relations table with referential integrity (CASCADE delete)\r\n // Phase 1 Sprint 5: Added weight, confidence, properties, metadata columns\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS relations (\r\n fromEntity TEXT NOT NULL REFERENCES entities(name) ON DELETE CASCADE,\r\n toEntity TEXT NOT NULL REFERENCES entities(name) ON DELETE CASCADE,\r\n relationType TEXT NOT NULL,\r\n createdAt TEXT NOT NULL,\r\n lastModified TEXT NOT NULL,\r\n weight REAL,\r\n confidence REAL,\r\n properties TEXT,\r\n metadata TEXT,\r\n PRIMARY KEY (fromEntity, toEntity, relationType)\r\n )\r\n `);\r\n\r\n // Schema migration for existing DBs (Phase 1 Sprint 5)\r\n this.migrateRelationsTable();\r\n\r\n // Indexes for fast lookups\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_entity_type ON entities(entityType)`);\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_entity_parent ON entities(parentId)`);\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_relation_from ON relations(fromEntity)`);\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_relation_to ON relations(toEntity)`);\r\n\r\n // Phase 4 Sprint 1: Additional indexes for range queries (O(n) -> O(log n))\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_entity_importance ON entities(importance)`);\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_entity_lastmodified ON entities(lastModified)`);\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_entity_createdat ON entities(createdAt)`);\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_relation_type ON relations(relationType)`);\r\n\r\n // Phase 1 Sprint 5: Indexes for relation metadata queries\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_relation_weight ON relations(weight)`);\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_relation_confidence ON relations(confidence)`);\r\n\r\n // Composite index for common query patterns (type + importance filtering)\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_entity_type_importance ON entities(entityType, importance)`);\r\n\r\n // FTS5 virtual table for full-text search\r\n // content='' makes it an external content table (we manage content ourselves)\r\n this.db.exec(`\r\n CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(\r\n name,\r\n entityType,\r\n observations,\r\n tags,\r\n content='entities',\r\n content_rowid='rowid'\r\n )\r\n `);\r\n\r\n // Triggers to keep FTS5 index in sync with entities table\r\n this.db.exec(`\r\n CREATE TRIGGER IF NOT EXISTS entities_ai AFTER INSERT ON entities BEGIN\r\n INSERT INTO entities_fts(rowid, name, entityType, observations, tags)\r\n VALUES (NEW.rowid, NEW.name, NEW.entityType, NEW.observations, NEW.tags);\r\n END\r\n `);\r\n\r\n this.db.exec(`\r\n CREATE TRIGGER IF NOT EXISTS entities_ad AFTER DELETE ON entities BEGIN\r\n INSERT INTO entities_fts(entities_fts, rowid, name, entityType, observations, tags)\r\n VALUES ('delete', OLD.rowid, OLD.name, OLD.entityType, OLD.observations, OLD.tags);\r\n END\r\n `);\r\n\r\n this.db.exec(`\r\n CREATE TRIGGER IF NOT EXISTS entities_au AFTER UPDATE ON entities BEGIN\r\n INSERT INTO entities_fts(entities_fts, rowid, name, entityType, observations, tags)\r\n VALUES ('delete', OLD.rowid, OLD.name, OLD.entityType, OLD.observations, OLD.tags);\r\n INSERT INTO entities_fts(rowid, name, entityType, observations, tags)\r\n VALUES (NEW.rowid, NEW.name, NEW.entityType, NEW.observations, NEW.tags);\r\n END\r\n `);\r\n }\r\n\r\n /**\r\n * Phase 1 Sprint 5: Migrate relations table to add metadata columns.\r\n * Checks if columns exist and adds them if not for backward compatibility.\r\n */\r\n private migrateRelationsTable(): void {\r\n if (!this.db) return;\r\n\r\n // Get current column names\r\n const columns = this.db.pragma('table_info(relations)') as Array<{ name: string }>;\r\n const columnNames = new Set(columns.map(c => c.name));\r\n\r\n // Add missing columns for metadata support\r\n if (!columnNames.has('weight')) {\r\n this.db.exec('ALTER TABLE relations ADD COLUMN weight REAL');\r\n }\r\n if (!columnNames.has('confidence')) {\r\n this.db.exec('ALTER TABLE relations ADD COLUMN confidence REAL');\r\n }\r\n if (!columnNames.has('properties')) {\r\n this.db.exec('ALTER TABLE relations ADD COLUMN properties TEXT');\r\n }\r\n if (!columnNames.has('metadata')) {\r\n this.db.exec('ALTER TABLE relations ADD COLUMN metadata TEXT');\r\n }\r\n }\r\n\r\n /**\r\n * Load all data from SQLite into memory cache.\r\n */\r\n private loadCache(): void {\r\n if (!this.db) throw new Error('Database not initialized');\r\n\r\n const entities: Entity[] = [];\r\n const relations: Relation[] = [];\r\n\r\n // Load entities\r\n const entityRows = this.db.prepare(`SELECT * FROM entities`).all() as EntityRow[];\r\n for (const row of entityRows) {\r\n const entity = this.rowToEntity(row);\r\n entities.push(entity);\r\n this.updateLowercaseCache(entity);\r\n }\r\n\r\n // Load relations\r\n const relationRows = this.db.prepare(`SELECT * FROM relations`).all() as RelationRow[];\r\n for (const row of relationRows) {\r\n relations.push(this.rowToRelation(row));\r\n }\r\n\r\n this.cache = { entities, relations };\r\n\r\n // Build indexes for O(1) lookups\r\n this.nameIndex.build(entities);\r\n this.typeIndex.build(entities);\r\n }\r\n\r\n /**\r\n * Convert a database row to an Entity object.\r\n */\r\n private rowToEntity(row: EntityRow): Entity {\r\n return {\r\n name: row.name,\r\n entityType: row.entityType,\r\n observations: JSON.parse(row.observations),\r\n tags: row.tags ? JSON.parse(row.tags) : undefined,\r\n importance: row.importance ?? undefined,\r\n parentId: row.parentId ?? undefined,\r\n createdAt: row.createdAt,\r\n lastModified: row.lastModified,\r\n };\r\n }\r\n\r\n /**\r\n * Convert a database row to a Relation object.\r\n * Phase 1 Sprint 5: Includes metadata fields.\r\n */\r\n private rowToRelation(row: RelationRow): Relation {\r\n const relation: Relation = {\r\n from: row.fromEntity,\r\n to: row.toEntity,\r\n relationType: row.relationType,\r\n createdAt: row.createdAt,\r\n lastModified: row.lastModified,\r\n };\r\n\r\n // Only include optional metadata fields if present\r\n if (row.weight !== null) relation.weight = row.weight;\r\n if (row.confidence !== null) relation.confidence = row.confidence;\r\n if (row.properties !== null) relation.properties = JSON.parse(row.properties);\r\n if (row.metadata !== null) relation.metadata = JSON.parse(row.metadata);\r\n\r\n return relation;\r\n }\r\n\r\n /**\r\n * Update lowercase cache for an entity.\r\n */\r\n private updateLowercaseCache(entity: Entity): void {\r\n this.lowercaseCache.set(entity.name, {\r\n name: entity.name.toLowerCase(),\r\n entityType: entity.entityType.toLowerCase(),\r\n observations: entity.observations.map(o => o.toLowerCase()),\r\n tags: entity.tags?.map(t => t.toLowerCase()) || [],\r\n });\r\n }\r\n\r\n // ==================== IGraphStorage Implementation ====================\r\n\r\n /**\r\n * Load the knowledge graph (read-only access).\r\n *\r\n * @returns Promise resolving to read-only knowledge graph reference\r\n */\r\n async loadGraph(): Promise<ReadonlyKnowledgeGraph> {\r\n await this.ensureLoaded();\r\n return this.cache!;\r\n }\r\n\r\n /**\r\n * Get a mutable copy of the graph for write operations.\r\n *\r\n * @returns Promise resolving to mutable knowledge graph copy\r\n */\r\n async getGraphForMutation(): Promise<KnowledgeGraph> {\r\n await this.ensureLoaded();\r\n return {\r\n entities: this.cache!.entities.map(e => ({\r\n ...e,\r\n observations: [...e.observations],\r\n tags: e.tags ? [...e.tags] : undefined,\r\n })),\r\n relations: this.cache!.relations.map(r => ({ ...r })),\r\n };\r\n }\r\n\r\n /**\r\n * Ensure the storage is loaded/initialized.\r\n *\r\n * @returns Promise resolving when ready\r\n */\r\n async ensureLoaded(): Promise<void> {\r\n if (!this.initialized) {\r\n this.initialize();\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 1: Invalidate bidirectional relation cache for an entity.\r\n *\r\n * @param entityName - Entity name to invalidate cache for\r\n */\r\n private invalidateBidirectionalCache(entityName: string): void {\r\n this.bidirectionalRelationCache.delete(entityName);\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 1: Clear the entire bidirectional relation cache.\r\n */\r\n private clearBidirectionalCache(): void {\r\n this.bidirectionalRelationCache.clear();\r\n }\r\n\r\n /**\r\n * Save the entire knowledge graph to storage.\r\n *\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n *\r\n * @param graph - The knowledge graph to save\r\n * @returns Promise resolving when save is complete\r\n */\r\n async saveGraph(graph: KnowledgeGraph): Promise<void> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n if (!this.db) throw new Error('Database not initialized');\r\n\r\n // Disable foreign keys for bulk replace operation\r\n // This allows inserting entities with parentId references that may not exist\r\n // and relations with dangling references (which matches the original JSONL behavior)\r\n this.db.pragma('foreign_keys = OFF');\r\n\r\n // Use transaction for atomicity\r\n const transaction = this.db.transaction(() => {\r\n // Clear existing data\r\n this.db!.exec('DELETE FROM relations');\r\n this.db!.exec('DELETE FROM entities');\r\n\r\n // Insert all entities\r\n const entityStmt = this.db!.prepare(`\r\n INSERT INTO entities (name, entityType, observations, tags, importance, parentId, createdAt, lastModified)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\r\n `);\r\n\r\n for (const entity of graph.entities) {\r\n entityStmt.run(\r\n entity.name,\r\n entity.entityType,\r\n JSON.stringify(entity.observations),\r\n entity.tags ? JSON.stringify(entity.tags) : null,\r\n entity.importance ?? null,\r\n entity.parentId ?? null,\r\n entity.createdAt || new Date().toISOString(),\r\n entity.lastModified || new Date().toISOString(),\r\n );\r\n }\r\n\r\n // Insert all relations (Phase 1 Sprint 5: with metadata)\r\n const relationStmt = this.db!.prepare(`\r\n INSERT INTO relations (fromEntity, toEntity, relationType, createdAt, lastModified, weight, confidence, properties, metadata)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\r\n `);\r\n\r\n for (const relation of graph.relations) {\r\n relationStmt.run(\r\n relation.from,\r\n relation.to,\r\n relation.relationType,\r\n relation.createdAt || new Date().toISOString(),\r\n relation.lastModified || new Date().toISOString(),\r\n relation.weight ?? null,\r\n relation.confidence ?? null,\r\n relation.properties ? JSON.stringify(relation.properties) : null,\r\n relation.metadata ? JSON.stringify(relation.metadata) : null,\r\n );\r\n }\r\n });\r\n\r\n transaction();\r\n\r\n // Re-enable foreign keys for future operations\r\n this.db.pragma('foreign_keys = ON');\r\n\r\n // Update cache\r\n this.cache = graph;\r\n this.lowercaseCache.clear();\r\n for (const entity of graph.entities) {\r\n this.updateLowercaseCache(entity);\r\n }\r\n\r\n // Rebuild indexes\r\n this.nameIndex.build(graph.entities);\r\n this.typeIndex.build(graph.entities);\r\n\r\n this.pendingChanges = 0;\r\n\r\n // Clear search caches\r\n clearAllSearchCaches();\r\n\r\n // Phase 4 Sprint 1: Clear bidirectional relation cache on full save\r\n this.clearBidirectionalCache();\r\n });\r\n }\r\n\r\n /**\r\n * Append a single entity to storage.\r\n *\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n *\r\n * @param entity - The entity to append\r\n * @returns Promise resolving when append is complete\r\n */\r\n async appendEntity(entity: Entity): Promise<void> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n if (!this.db) throw new Error('Database not initialized');\r\n\r\n // Use INSERT OR REPLACE to handle updates\r\n const stmt = this.db.prepare(`\r\n INSERT OR REPLACE INTO entities (name, entityType, observations, tags, importance, parentId, createdAt, lastModified)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\r\n `);\r\n\r\n stmt.run(\r\n entity.name,\r\n entity.entityType,\r\n JSON.stringify(entity.observations),\r\n entity.tags ? JSON.stringify(entity.tags) : null,\r\n entity.importance ?? null,\r\n entity.parentId ?? null,\r\n entity.createdAt || new Date().toISOString(),\r\n entity.lastModified || new Date().toISOString(),\r\n );\r\n\r\n // Update cache\r\n const existingIndex = this.cache!.entities.findIndex(e => e.name === entity.name);\r\n if (existingIndex >= 0) {\r\n this.cache!.entities[existingIndex] = entity;\r\n } else {\r\n this.cache!.entities.push(entity);\r\n }\r\n\r\n // Update indexes\r\n this.nameIndex.add(entity);\r\n this.typeIndex.add(entity);\r\n this.updateLowercaseCache(entity);\r\n clearAllSearchCaches();\r\n\r\n this.pendingChanges++;\r\n });\r\n }\r\n\r\n /**\r\n * Append a single relation to storage.\r\n *\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n *\r\n * @param relation - The relation to append\r\n * @returns Promise resolving when append is complete\r\n */\r\n async appendRelation(relation: Relation): Promise<void> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n if (!this.db) throw new Error('Database not initialized');\r\n\r\n // Use INSERT OR REPLACE to handle updates (Phase 1 Sprint 5: with metadata)\r\n const stmt = this.db.prepare(`\r\n INSERT OR REPLACE INTO relations (fromEntity, toEntity, relationType, createdAt, lastModified, weight, confidence, properties, metadata)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\r\n `);\r\n\r\n stmt.run(\r\n relation.from,\r\n relation.to,\r\n relation.relationType,\r\n relation.createdAt || new Date().toISOString(),\r\n relation.lastModified || new Date().toISOString(),\r\n relation.weight ?? null,\r\n relation.confidence ?? null,\r\n relation.properties ? JSON.stringify(relation.properties) : null,\r\n relation.metadata ? JSON.stringify(relation.metadata) : null,\r\n );\r\n\r\n // Update cache\r\n const existingIndex = this.cache!.relations.findIndex(\r\n r => r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType\r\n );\r\n if (existingIndex >= 0) {\r\n this.cache!.relations[existingIndex] = relation;\r\n } else {\r\n this.cache!.relations.push(relation);\r\n }\r\n\r\n clearAllSearchCaches();\r\n\r\n // Phase 4 Sprint 1: Invalidate bidirectional cache for both entities\r\n this.invalidateBidirectionalCache(relation.from);\r\n this.invalidateBidirectionalCache(relation.to);\r\n\r\n this.pendingChanges++;\r\n });\r\n }\r\n\r\n /**\r\n * Update an entity in storage.\r\n *\r\n * THREAD-SAFE: Uses mutex to prevent concurrent write operations.\r\n *\r\n * @param entityName - Name of the entity to update\r\n * @param updates - Partial entity updates to apply\r\n * @returns Promise resolving to true if found and updated\r\n */\r\n async updateEntity(entityName: string, updates: Partial<Entity>): Promise<boolean> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n if (!this.db) throw new Error('Database not initialized');\r\n\r\n // Find entity in cache using index (O(1))\r\n const entity = this.nameIndex.get(entityName);\r\n if (!entity) {\r\n return false;\r\n }\r\n\r\n // Track old type for index update\r\n const oldType = entity.entityType;\r\n\r\n // Apply updates to cached entity (sanitized to prevent prototype pollution)\r\n Object.assign(entity, sanitizeObject(updates as Record<string, unknown>));\r\n entity.lastModified = new Date().toISOString();\r\n\r\n // Update in database\r\n const stmt = this.db.prepare(`\r\n UPDATE entities SET\r\n entityType = ?,\r\n observations = ?,\r\n tags = ?,\r\n importance = ?,\r\n parentId = ?,\r\n lastModified = ?\r\n WHERE name = ?\r\n `);\r\n\r\n stmt.run(\r\n entity.entityType,\r\n JSON.stringify(entity.observations),\r\n entity.tags ? JSON.stringify(entity.tags) : null,\r\n entity.importance ?? null,\r\n entity.parentId ?? null,\r\n entity.lastModified,\r\n entityName,\r\n );\r\n\r\n // Update indexes\r\n this.nameIndex.add(entity); // Update reference\r\n if (updates.entityType && updates.entityType !== oldType) {\r\n this.typeIndex.updateType(entityName, oldType, updates.entityType);\r\n }\r\n this.updateLowercaseCache(entity);\r\n clearAllSearchCaches();\r\n\r\n this.pendingChanges++;\r\n\r\n return true;\r\n });\r\n }\r\n\r\n /**\r\n * Compact the storage (runs VACUUM to reclaim space).\r\n *\r\n * THREAD-SAFE: Uses mutex to prevent concurrent operations.\r\n *\r\n * @returns Promise resolving when compaction is complete\r\n */\r\n async compact(): Promise<void> {\r\n await this.ensureLoaded();\r\n\r\n return this.mutex.runExclusive(async () => {\r\n if (!this.db) return;\r\n\r\n // Run SQLite VACUUM to reclaim space and defragment\r\n this.db.exec('VACUUM');\r\n\r\n // Rebuild FTS index for optimal search performance\r\n this.db.exec(`INSERT INTO entities_fts(entities_fts) VALUES('rebuild')`);\r\n\r\n this.pendingChanges = 0;\r\n });\r\n }\r\n\r\n /**\r\n * Clear any in-memory cache.\r\n */\r\n clearCache(): void {\r\n this.cache = null;\r\n this.nameIndex.clear();\r\n this.typeIndex.clear();\r\n this.lowercaseCache.clear();\r\n // Phase 4 Sprint 1: Clear bidirectional relation cache\r\n this.bidirectionalRelationCache.clear();\r\n this.initialized = false;\r\n if (this.db) {\r\n this.db.close();\r\n this.db = null;\r\n }\r\n }\r\n\r\n // ==================== Index Operations ====================\r\n\r\n /**\r\n * Get an entity by name in O(1) time.\r\n *\r\n * OPTIMIZED: Uses NameIndex for constant-time lookup.\r\n *\r\n * @param name - Entity name to look up\r\n * @returns Entity if found, undefined otherwise\r\n */\r\n getEntityByName(name: string): Entity | undefined {\r\n return this.nameIndex.get(name);\r\n }\r\n\r\n /**\r\n * Check if an entity exists by name in O(1) time.\r\n *\r\n * @param name - Entity name to check\r\n * @returns True if entity exists\r\n */\r\n hasEntity(name: string): boolean {\r\n return this.nameIndex.has(name);\r\n }\r\n\r\n /**\r\n * Get all entities of a given type in O(1) time.\r\n *\r\n * OPTIMIZED: Uses TypeIndex for constant-time lookup of entity names,\r\n * then uses NameIndex for O(1) entity retrieval.\r\n *\r\n * @param entityType - Entity type to filter by (case-insensitive)\r\n * @returns Array of entities with the given type\r\n */\r\n getEntitiesByType(entityType: string): Entity[] {\r\n const names = this.typeIndex.getNames(entityType);\r\n const entities: Entity[] = [];\r\n for (const name of names) {\r\n const entity = this.nameIndex.get(name);\r\n if (entity) {\r\n entities.push(entity);\r\n }\r\n }\r\n return entities;\r\n }\r\n\r\n /**\r\n * Get all unique entity types in the graph.\r\n *\r\n * @returns Array of unique entity types (lowercase)\r\n */\r\n getEntityTypes(): string[] {\r\n return this.typeIndex.getTypes();\r\n }\r\n\r\n /**\r\n * Get pre-computed lowercase data for an entity.\r\n *\r\n * @param entityName - Entity name to get lowercase data for\r\n * @returns LowercaseData if entity exists, undefined otherwise\r\n */\r\n getLowercased(entityName: string): LowercaseData | undefined {\r\n return this.lowercaseCache.get(entityName);\r\n }\r\n\r\n // ==================== FTS5 Full-Text Search ====================\r\n\r\n /**\r\n * Perform full-text search using FTS5.\r\n *\r\n * @param query - Search query (supports FTS5 query syntax)\r\n * @returns Array of matching entity names with relevance scores\r\n */\r\n fullTextSearch(query: string): Array<{ name: string; score: number }> {\r\n if (!this.db || !this.initialized) return [];\r\n\r\n try {\r\n // Use FTS5 MATCH for full-text search with BM25 ranking\r\n const stmt = this.db.prepare(`\r\n SELECT name, bm25(entities_fts, 10, 5, 3, 1) as score\r\n FROM entities_fts\r\n WHERE entities_fts MATCH ?\r\n ORDER BY score\r\n LIMIT 100\r\n `);\r\n\r\n const results = stmt.all(query) as Array<{ name: string; score: number }>;\r\n return results;\r\n } catch {\r\n // If FTS query fails (invalid syntax), fall back to empty results\r\n return [];\r\n }\r\n }\r\n\r\n /**\r\n * Perform a simple text search (LIKE-based, case-insensitive).\r\n *\r\n * @param searchTerm - Term to search for\r\n * @returns Array of matching entity names\r\n */\r\n simpleSearch(searchTerm: string): string[] {\r\n if (!this.db || !this.initialized) return [];\r\n\r\n const pattern = `%${searchTerm}%`;\r\n const stmt = this.db.prepare(`\r\n SELECT name FROM entities\r\n WHERE name LIKE ? COLLATE NOCASE\r\n OR entityType LIKE ? COLLATE NOCASE\r\n OR observations LIKE ? COLLATE NOCASE\r\n OR tags LIKE ? COLLATE NOCASE\r\n `);\r\n\r\n const results = stmt.all(pattern, pattern, pattern, pattern) as Array<{ name: string }>;\r\n return results.map(r => r.name);\r\n }\r\n\r\n // ==================== Utility Operations ====================\r\n\r\n /**\r\n * Get the storage path/location.\r\n *\r\n * @returns The storage path\r\n */\r\n getFilePath(): string {\r\n return this.validatedDbFilePath;\r\n }\r\n\r\n /**\r\n * Get the current pending changes count.\r\n *\r\n * @returns Number of pending changes since last reset\r\n */\r\n getPendingAppends(): number {\r\n return this.pendingChanges;\r\n }\r\n\r\n /**\r\n * Force persistence to disk (no-op for better-sqlite3 as it writes immediately).\r\n *\r\n * @returns Promise resolving when persistence is complete\r\n */\r\n async flush(): Promise<void> {\r\n // better-sqlite3 writes to disk immediately, but we run a checkpoint for WAL mode\r\n if (this.db) {\r\n this.db.pragma('wal_checkpoint(TRUNCATE)');\r\n }\r\n this.pendingChanges = 0;\r\n }\r\n\r\n /**\r\n * Close the database connection.\r\n */\r\n close(): void {\r\n if (this.db) {\r\n this.db.close();\r\n this.db = null;\r\n }\r\n this.initialized = false;\r\n }\r\n\r\n // ==================== Relation Index Operations ====================\r\n\r\n /**\r\n * Get all relations where the entity is the source (outgoing relations).\r\n *\r\n * OPTIMIZED: Uses SQLite index on fromEntity for O(log n) lookup.\r\n *\r\n * @param entityName - Entity name to look up outgoing relations for\r\n * @returns Array of relations where entity is the source\r\n */\r\n getRelationsFrom(entityName: string): Relation[] {\r\n // Check cache first\r\n if (this.cache) {\r\n return this.cache.relations.filter(r => r.from === entityName);\r\n }\r\n\r\n // Fall back to database query (Phase 1 Sprint 5: SELECT * for metadata)\r\n if (!this.db || !this.initialized) return [];\r\n const stmt = this.db.prepare('SELECT * FROM relations WHERE fromEntity = ?');\r\n const rows = stmt.all(entityName) as RelationRow[];\r\n return rows.map(row => this.rowToRelation(row));\r\n }\r\n\r\n /**\r\n * Get all relations where the entity is the target (incoming relations).\r\n *\r\n * OPTIMIZED: Uses SQLite index on toEntity for O(log n) lookup.\r\n *\r\n * @param entityName - Entity name to look up incoming relations for\r\n * @returns Array of relations where entity is the target\r\n */\r\n getRelationsTo(entityName: string): Relation[] {\r\n // Check cache first\r\n if (this.cache) {\r\n return this.cache.relations.filter(r => r.to === entityName);\r\n }\r\n\r\n // Fall back to database query (Phase 1 Sprint 5: SELECT * for metadata)\r\n if (!this.db || !this.initialized) return [];\r\n const stmt = this.db.prepare('SELECT * FROM relations WHERE toEntity = ?');\r\n const rows = stmt.all(entityName) as RelationRow[];\r\n return rows.map(row => this.rowToRelation(row));\r\n }\r\n\r\n /**\r\n * Get all relations involving the entity (both incoming and outgoing).\r\n *\r\n * OPTIMIZED: Phase 4 Sprint 1 - Uses bidirectional cache for O(1) repeated lookups.\r\n *\r\n * @param entityName - Entity name to look up all relations for\r\n * @returns Array of all relations involving the entity\r\n */\r\n getRelationsFor(entityName: string): Relation[] {\r\n // Phase 4 Sprint 1: Check bidirectional cache first for O(1) repeated lookups\r\n const cached = this.bidirectionalRelationCache.get(entityName);\r\n if (cached !== undefined) {\r\n return cached;\r\n }\r\n\r\n // Check main cache and compute result\r\n let relations: Relation[];\r\n if (this.cache) {\r\n relations = this.cache.relations.filter(r => r.from === entityName || r.to === entityName);\r\n } else if (this.db && this.initialized) {\r\n // Fall back to database query (Phase 1 Sprint 5: SELECT * for metadata)\r\n const stmt = this.db.prepare('SELECT * FROM relations WHERE fromEntity = ? OR toEntity = ?');\r\n const rows = stmt.all(entityName, entityName) as RelationRow[];\r\n relations = rows.map(row => this.rowToRelation(row));\r\n } else {\r\n return [];\r\n }\r\n\r\n // Cache the result for O(1) subsequent lookups\r\n this.bidirectionalRelationCache.set(entityName, relations);\r\n return relations;\r\n }\r\n\r\n /**\r\n * Check if an entity has any relations.\r\n *\r\n * @param entityName - Entity name to check\r\n * @returns True if entity has any relations\r\n */\r\n hasRelations(entityName: string): boolean {\r\n // Check cache first\r\n if (this.cache) {\r\n return this.cache.relations.some(r => r.from === entityName || r.to === entityName);\r\n }\r\n\r\n // Fall back to database query\r\n if (!this.db || !this.initialized) return false;\r\n const stmt = this.db.prepare(\r\n 'SELECT 1 FROM relations WHERE fromEntity = ? OR toEntity = ? LIMIT 1'\r\n );\r\n const row = stmt.get(entityName, entityName);\r\n return row !== undefined;\r\n }\r\n\r\n // ==================== Embedding Storage (Phase 4 Sprint 11) ====================\r\n\r\n /**\r\n * Phase 4 Sprint 11: Ensure embeddings table exists.\r\n *\r\n * Creates the embeddings table if it doesn't exist.\r\n * Separate table from entities to avoid schema migration complexity.\r\n */\r\n private ensureEmbeddingsTable(): void {\r\n if (!this.db) throw new Error('Database not initialized');\r\n\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS embeddings (\r\n entityName TEXT PRIMARY KEY REFERENCES entities(name) ON DELETE CASCADE,\r\n embedding BLOB NOT NULL,\r\n embeddingModel TEXT NOT NULL,\r\n embeddingUpdatedAt TEXT NOT NULL,\r\n dimensions INTEGER NOT NULL\r\n )\r\n `);\r\n\r\n // Index for quick lookup by model\r\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_embedding_model ON embeddings(embeddingModel)`);\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 11: Store an embedding for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param vector - Embedding vector\r\n * @param model - Model name used for the embedding\r\n */\r\n storeEmbedding(entityName: string, vector: number[], model: string): void {\r\n if (!this.db || !this.initialized) {\r\n throw new Error('Database not initialized');\r\n }\r\n\r\n this.ensureEmbeddingsTable();\r\n\r\n // Convert to Float32Array for efficient storage\r\n const buffer = Buffer.from(new Float32Array(vector).buffer);\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT OR REPLACE INTO embeddings (entityName, embedding, embeddingModel, embeddingUpdatedAt, dimensions)\r\n VALUES (?, ?, ?, ?, ?)\r\n `);\r\n\r\n stmt.run(entityName, buffer, model, new Date().toISOString(), vector.length);\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 11: Get an embedding for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @returns Embedding vector if found, null otherwise\r\n */\r\n getEmbedding(entityName: string): number[] | null {\r\n if (!this.db || !this.initialized) return null;\r\n\r\n try {\r\n this.ensureEmbeddingsTable();\r\n\r\n const stmt = this.db.prepare(`SELECT embedding FROM embeddings WHERE entityName = ?`);\r\n const row = stmt.get(entityName) as { embedding: Buffer } | undefined;\r\n\r\n if (!row) return null;\r\n\r\n // Convert from Buffer to number array\r\n const float32Array = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.length / 4);\r\n return Array.from(float32Array);\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 11: Load all embeddings from storage.\r\n *\r\n * @returns Array of [entityName, vector] pairs\r\n */\r\n async loadAllEmbeddings(): Promise<[string, number[]][]> {\r\n if (!this.db || !this.initialized) return [];\r\n\r\n try {\r\n this.ensureEmbeddingsTable();\r\n\r\n const stmt = this.db.prepare(`SELECT entityName, embedding FROM embeddings`);\r\n const rows = stmt.all() as Array<{ entityName: string; embedding: Buffer }>;\r\n\r\n return rows.map(row => {\r\n const float32Array = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.length / 4);\r\n return [row.entityName, Array.from(float32Array)] as [string, number[]];\r\n });\r\n } catch {\r\n return [];\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 11: Remove an embedding for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n */\r\n removeEmbedding(entityName: string): void {\r\n if (!this.db || !this.initialized) return;\r\n\r\n try {\r\n this.ensureEmbeddingsTable();\r\n const stmt = this.db.prepare(`DELETE FROM embeddings WHERE entityName = ?`);\r\n stmt.run(entityName);\r\n } catch {\r\n // Ignore errors if table doesn't exist\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 11: Clear all embeddings from storage.\r\n */\r\n clearAllEmbeddings(): void {\r\n if (!this.db || !this.initialized) return;\r\n\r\n try {\r\n this.ensureEmbeddingsTable();\r\n this.db.exec(`DELETE FROM embeddings`);\r\n } catch {\r\n // Ignore errors if table doesn't exist\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 11: Check if an entity has an embedding.\r\n *\r\n * @param entityName - Name of the entity\r\n * @returns True if embedding exists\r\n */\r\n hasEmbedding(entityName: string): boolean {\r\n if (!this.db || !this.initialized) return false;\r\n\r\n try {\r\n this.ensureEmbeddingsTable();\r\n const stmt = this.db.prepare(`SELECT 1 FROM embeddings WHERE entityName = ? LIMIT 1`);\r\n const row = stmt.get(entityName);\r\n return row !== undefined;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 11: Get embedding statistics.\r\n *\r\n * @returns Stats about stored embeddings\r\n */\r\n getEmbeddingStats(): { count: number; models: string[] } {\r\n if (!this.db || !this.initialized) {\r\n return { count: 0, models: [] };\r\n }\r\n\r\n try {\r\n this.ensureEmbeddingsTable();\r\n\r\n const countRow = this.db.prepare(`SELECT COUNT(*) as count FROM embeddings`).get() as { count: number };\r\n const modelRows = this.db.prepare(`SELECT DISTINCT embeddingModel FROM embeddings`).all() as Array<{ embeddingModel: string }>;\r\n\r\n return {\r\n count: countRow.count,\r\n models: modelRows.map(r => r.embeddingModel),\r\n };\r\n } catch {\r\n return { count: 0, models: [] };\r\n }\r\n }\r\n}\r\n\r\n// ==================== Type Definitions for Database Rows ====================\r\n\r\ninterface EntityRow {\r\n name: string;\r\n entityType: string;\r\n observations: string;\r\n tags: string | null;\r\n importance: number | null;\r\n parentId: string | null;\r\n createdAt: string;\r\n lastModified: string;\r\n}\r\n\r\ninterface RelationRow {\r\n fromEntity: string;\r\n toEntity: string;\r\n relationType: string;\r\n createdAt: string;\r\n lastModified: string;\r\n // Phase 1 Sprint 5: Metadata columns\r\n weight: number | null;\r\n confidence: number | null;\r\n properties: string | null;\r\n metadata: string | null;\r\n}\r\n","/**\r\n * Storage Factory\r\n *\r\n * Factory for creating IGraphStorage implementations.\r\n * Supports different storage backends based on configuration.\r\n *\r\n * Supported storage types:\r\n * - 'jsonl': JSONL file-based storage (default) - simple, human-readable\r\n * - 'sqlite': SQLite database storage (better-sqlite3 native) - indexed, ACID transactions, FTS5\r\n *\r\n * @module core/StorageFactory\r\n */\r\n\r\nimport { GraphStorage } from './GraphStorage.js';\r\nimport { SQLiteStorage } from './SQLiteStorage.js';\r\nimport type { IGraphStorage, StorageConfig } from '../types/index.js';\r\n\r\n/**\r\n * Default storage type when not specified.\r\n */\r\nconst DEFAULT_STORAGE_TYPE = 'jsonl';\r\n\r\n/**\r\n * Create a storage instance based on configuration.\r\n *\r\n * Uses environment variable MEMORY_STORAGE_TYPE to override default.\r\n *\r\n * @param config - Storage configuration\r\n * @returns IGraphStorage implementation\r\n * @throws Error if storage type is not supported\r\n *\r\n * @example\r\n * ```typescript\r\n * // Create default JSONL storage\r\n * const storage = createStorage({ type: 'jsonl', path: './memory.jsonl' });\r\n *\r\n * // Create SQLite storage\r\n * const storage = createStorage({ type: 'sqlite', path: './memory.db' });\r\n *\r\n * // Or use path-only shorthand (uses MEMORY_STORAGE_TYPE env var or defaults to jsonl)\r\n * const storage = createStorageFromPath('./memory.jsonl');\r\n * ```\r\n */\r\nexport function createStorage(config: StorageConfig): IGraphStorage {\r\n // Allow environment override\r\n const storageType = process.env.MEMORY_STORAGE_TYPE || config.type || DEFAULT_STORAGE_TYPE;\r\n\r\n switch (storageType) {\r\n case 'jsonl':\r\n return new GraphStorage(config.path);\r\n\r\n case 'sqlite':\r\n return new SQLiteStorage(config.path);\r\n\r\n default:\r\n throw new Error(\r\n `Unknown storage type: ${storageType}. ` +\r\n `Supported types: jsonl, sqlite`\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Create a storage instance from a file path.\r\n *\r\n * Uses default storage type (jsonl) or MEMORY_STORAGE_TYPE env var.\r\n *\r\n * @param path - Path to storage file\r\n * @returns IGraphStorage implementation\r\n */\r\nexport function createStorageFromPath(path: string): IGraphStorage {\r\n const storageType = (process.env.MEMORY_STORAGE_TYPE as 'jsonl' | 'sqlite') || DEFAULT_STORAGE_TYPE;\r\n return createStorage({ type: storageType, path });\r\n}\r\n","/**\r\n * Entity Manager\r\n *\r\n * Handles CRUD operations for entities in the knowledge graph.\r\n * Focused on core entity and tag operations only (Phase 4: Consolidate God Objects).\r\n *\r\n * @module core/EntityManager\r\n */\r\n\r\nimport type { Entity, LongRunningOperationOptions, AccessContext } from '../types/index.js';\r\nimport type { GraphStorage } from './GraphStorage.js';\r\nimport type { AccessTracker } from '../agent/AccessTracker.js';\r\nimport { EntityNotFoundError, InvalidImportanceError, ValidationError } from '../utils/errors.js';\r\n\r\n/**\r\n * Options for entity retrieval with access tracking support.\r\n */\r\nexport interface GetEntityOptions {\r\n /** Enable access tracking for retrieved entity */\r\n trackAccess?: boolean;\r\n /** Session ID for access context */\r\n sessionId?: string;\r\n /** Task ID for access context */\r\n taskId?: string;\r\n}\r\nimport {\r\n BatchCreateEntitiesSchema,\r\n UpdateEntitySchema,\r\n EntityNamesSchema,\r\n checkCancellation,\r\n createProgressReporter,\r\n createProgress,\r\n sanitizeObject,\r\n} from '../utils/index.js';\r\nimport { GRAPH_LIMITS } from '../utils/constants.js';\r\n\r\n/**\r\n * Minimum importance value (least important).\r\n * Note: Use IMPORTANCE_RANGE from constants.ts for external access.\r\n */\r\nconst MIN_IMPORTANCE = 0;\r\n\r\n/**\r\n * Maximum importance value (most important).\r\n * Note: Use IMPORTANCE_RANGE from constants.ts for external access.\r\n */\r\nconst MAX_IMPORTANCE = 10;\r\n\r\n/**\r\n * Manages entity operations with automatic timestamp handling.\r\n */\r\nexport class EntityManager {\r\n private accessTracker?: AccessTracker;\r\n\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Set the AccessTracker for optional access tracking.\r\n * When set, getEntity can track access to retrieved entities.\r\n *\r\n * @param tracker - AccessTracker instance\r\n */\r\n setAccessTracker(tracker: AccessTracker): void {\r\n this.accessTracker = tracker;\r\n }\r\n\r\n /**\r\n * Create multiple entities in a single batch operation.\r\n *\r\n * This method performs the following operations:\r\n * - Filters out entities that already exist (duplicate names)\r\n * - Automatically adds createdAt and lastModified timestamps\r\n * - Normalizes all tags to lowercase for consistent searching\r\n * - Validates importance values (must be between 0-10)\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.\r\n *\r\n * @param entities - Array of entities to create. Each entity must have a unique name.\r\n * @param options - Optional progress/cancellation options (Phase 9B)\r\n * @returns Promise resolving to array of newly created entities (excludes duplicates)\r\n * @throws {InvalidImportanceError} If any entity has importance outside the valid range [0-10]\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new EntityManager(storage);\r\n *\r\n * // Create single entity\r\n * const results = await manager.createEntities([{\r\n * name: 'Alice',\r\n * entityType: 'person',\r\n * observations: ['Works as engineer', 'Lives in Seattle'],\r\n * importance: 7,\r\n * tags: ['Team', 'Engineering']\r\n * }]);\r\n *\r\n * // Create multiple entities at once\r\n * const users = await manager.createEntities([\r\n * { name: 'Bob', entityType: 'person', observations: [] },\r\n * { name: 'Charlie', entityType: 'person', observations: [] }\r\n * ]);\r\n *\r\n * // With progress tracking and cancellation (Phase 9B)\r\n * const controller = new AbortController();\r\n * const results = await manager.createEntities(largeEntityArray, {\r\n * signal: controller.signal,\r\n * onProgress: (p) => console.log(`${p.percentage}% complete`),\r\n * });\r\n * ```\r\n */\r\n async createEntities(\r\n entities: Entity[],\r\n options?: LongRunningOperationOptions\r\n ): Promise<Entity[]> {\r\n // Check for early cancellation\r\n checkCancellation(options?.signal, 'createEntities');\r\n\r\n // Validate input\r\n const validation = BatchCreateEntitiesSchema.safeParse(entities);\r\n if (!validation.success) {\r\n const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);\r\n throw new ValidationError('Invalid entity data', errors);\r\n }\r\n\r\n // Setup progress reporter\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n const total = entities.length;\r\n reportProgress?.(createProgress(0, total, 'createEntities'));\r\n\r\n // Use read-only graph for checking existing entities\r\n const readGraph = await this.storage.loadGraph();\r\n const timestamp = new Date().toISOString();\r\n\r\n // Check graph size limits\r\n const entitiesToAdd = entities.filter(e => !readGraph.entities.some(existing => existing.name === e.name));\r\n if (readGraph.entities.length + entitiesToAdd.length > GRAPH_LIMITS.MAX_ENTITIES) {\r\n throw new ValidationError(\r\n 'Graph size limit exceeded',\r\n [`Adding ${entitiesToAdd.length} entities would exceed maximum of ${GRAPH_LIMITS.MAX_ENTITIES} entities`]\r\n );\r\n }\r\n\r\n // Check for cancellation before processing\r\n checkCancellation(options?.signal, 'createEntities');\r\n\r\n const newEntities: Entity[] = [];\r\n let processed = 0;\r\n\r\n for (const e of entitiesToAdd) {\r\n // Check for cancellation periodically\r\n checkCancellation(options?.signal, 'createEntities');\r\n\r\n const entity: Entity = {\r\n ...e,\r\n createdAt: e.createdAt || timestamp,\r\n lastModified: e.lastModified || timestamp,\r\n };\r\n\r\n // Normalize tags to lowercase\r\n if (e.tags) {\r\n entity.tags = e.tags.map(tag => tag.toLowerCase());\r\n }\r\n\r\n // Validate importance\r\n if (e.importance !== undefined) {\r\n if (e.importance < MIN_IMPORTANCE || e.importance > MAX_IMPORTANCE) {\r\n throw new InvalidImportanceError(e.importance, MIN_IMPORTANCE, MAX_IMPORTANCE);\r\n }\r\n entity.importance = e.importance;\r\n }\r\n\r\n newEntities.push(entity);\r\n processed++;\r\n reportProgress?.(createProgress(processed, entitiesToAdd.length, 'createEntities'));\r\n }\r\n\r\n // OPTIMIZED: Use append for single entity, bulk save for multiple\r\n // (N individual appends is slower than one bulk write)\r\n if (newEntities.length === 1) {\r\n await this.storage.appendEntity(newEntities[0]);\r\n } else if (newEntities.length > 1) {\r\n const graph = await this.storage.getGraphForMutation();\r\n graph.entities.push(...newEntities);\r\n await this.storage.saveGraph(graph);\r\n }\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(entitiesToAdd.length, entitiesToAdd.length, 'createEntities'));\r\n\r\n return newEntities;\r\n }\r\n\r\n /**\r\n * Delete multiple entities by name in a single batch operation.\r\n *\r\n * This method performs cascading deletion:\r\n * - Removes all specified entities from the graph\r\n * - Automatically removes all relations where these entities are source or target\r\n * - Silently ignores entity names that don't exist (no error thrown)\r\n *\r\n * @param entityNames - Array of entity names to delete\r\n * @returns Promise that resolves when deletion is complete\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new EntityManager(storage);\r\n *\r\n * // Delete single entity\r\n * await manager.deleteEntities(['Alice']);\r\n *\r\n * // Delete multiple entities at once\r\n * await manager.deleteEntities(['Bob', 'Charlie', 'Dave']);\r\n *\r\n * // Safe to delete non-existent entities (no error)\r\n * await manager.deleteEntities(['NonExistent']); // No error thrown\r\n * ```\r\n */\r\n async deleteEntities(entityNames: string[]): Promise<void> {\r\n // Validate input\r\n const validation = EntityNamesSchema.safeParse(entityNames);\r\n if (!validation.success) {\r\n const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);\r\n throw new ValidationError('Invalid entity names', errors);\r\n }\r\n\r\n const graph = await this.storage.getGraphForMutation();\r\n\r\n // OPTIMIZED: Use Set for O(1) lookups instead of O(n) includes()\r\n const namesToDelete = new Set(entityNames);\r\n graph.entities = graph.entities.filter(e => !namesToDelete.has(e.name));\r\n graph.relations = graph.relations.filter(\r\n r => !namesToDelete.has(r.from) && !namesToDelete.has(r.to)\r\n );\r\n\r\n await this.storage.saveGraph(graph);\r\n }\r\n\r\n /**\r\n * Retrieve a single entity by its unique name.\r\n *\r\n * This is a read-only operation that does not modify the graph.\r\n * Entity names are case-sensitive.\r\n *\r\n * @param name - The unique name of the entity to retrieve\r\n * @returns Promise resolving to the Entity object if found, or null if not found\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new EntityManager(storage);\r\n *\r\n * // Get an existing entity\r\n * const alice = await manager.getEntity('Alice');\r\n * if (alice) {\r\n * console.log(alice.observations);\r\n * console.log(alice.importance);\r\n * }\r\n *\r\n * // Handle non-existent entity\r\n * const missing = await manager.getEntity('NonExistent');\r\n * console.log(missing); // null\r\n *\r\n * // Get entity with access tracking\r\n * const tracked = await manager.getEntity('Bob', {\r\n * trackAccess: true,\r\n * sessionId: 'session_123'\r\n * });\r\n * ```\r\n */\r\n async getEntity(name: string, options?: GetEntityOptions): Promise<Entity | null> {\r\n const graph = await this.storage.loadGraph();\r\n const entity = graph.entities.find(e => e.name === name) || null;\r\n\r\n // Track access if enabled\r\n if (entity && options?.trackAccess && this.accessTracker) {\r\n const context: AccessContext = {\r\n sessionId: options.sessionId,\r\n taskId: options.taskId,\r\n retrievalMethod: 'direct',\r\n };\r\n await this.accessTracker.recordAccess(name, context);\r\n }\r\n\r\n return entity;\r\n }\r\n\r\n /**\r\n * Update one or more fields of an existing entity.\r\n *\r\n * This method allows partial updates - only the fields specified in the updates\r\n * object will be changed. All other fields remain unchanged.\r\n * The lastModified timestamp is automatically updated.\r\n *\r\n * @param name - The unique name of the entity to update\r\n * @param updates - Partial entity object containing only the fields to update\r\n * @returns Promise resolving to the fully updated Entity object\r\n * @throws {EntityNotFoundError} If no entity with the given name exists\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new EntityManager(storage);\r\n *\r\n * // Update importance only\r\n * const updated = await manager.updateEntity('Alice', {\r\n * importance: 9\r\n * });\r\n *\r\n * // Update multiple fields\r\n * await manager.updateEntity('Bob', {\r\n * entityType: 'senior_engineer',\r\n * tags: ['leadership', 'architecture'],\r\n * observations: ['Led project X', 'Designed system Y']\r\n * });\r\n *\r\n * // Add observations (requires reading existing entity first)\r\n * const entity = await manager.getEntity('Charlie');\r\n * if (entity) {\r\n * await manager.updateEntity('Charlie', {\r\n * observations: [...entity.observations, 'New observation']\r\n * });\r\n * }\r\n * ```\r\n */\r\n async updateEntity(name: string, updates: Partial<Entity>): Promise<Entity> {\r\n // Validate input\r\n const validation = UpdateEntitySchema.safeParse(updates);\r\n if (!validation.success) {\r\n const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);\r\n throw new ValidationError('Invalid update data', errors);\r\n }\r\n\r\n const graph = await this.storage.getGraphForMutation();\r\n const entity = graph.entities.find(e => e.name === name);\r\n\r\n if (!entity) {\r\n throw new EntityNotFoundError(name);\r\n }\r\n\r\n // Apply updates (sanitized to prevent prototype pollution)\r\n Object.assign(entity, sanitizeObject(updates as Record<string, unknown>));\r\n entity.lastModified = new Date().toISOString();\r\n\r\n await this.storage.saveGraph(graph);\r\n return entity;\r\n }\r\n\r\n /**\r\n * Update multiple entities in a single batch operation.\r\n *\r\n * This method is more efficient than calling updateEntity multiple times\r\n * as it loads and saves the graph only once. All updates are applied atomically.\r\n * The lastModified timestamp is automatically updated for all entities.\r\n *\r\n * @param updates - Array of updates, each containing entity name and changes\r\n * @returns Promise resolving to array of updated entities\r\n * @throws {EntityNotFoundError} If any entity is not found\r\n * @throws {ValidationError} If any update data is invalid\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new EntityManager(storage);\r\n *\r\n * // Update multiple entities at once\r\n * const updated = await manager.batchUpdate([\r\n * { name: 'Alice', updates: { importance: 9 } },\r\n * { name: 'Bob', updates: { importance: 8, tags: ['senior'] } },\r\n * { name: 'Charlie', updates: { entityType: 'lead_engineer' } }\r\n * ]);\r\n *\r\n * console.log(`Updated ${updated.length} entities`);\r\n *\r\n * // Efficiently update many entities (single graph load/save)\r\n * const massUpdate = employees.map(name => ({\r\n * name,\r\n * updates: { tags: ['team-2024'] }\r\n * }));\r\n * await manager.batchUpdate(massUpdate);\r\n * ```\r\n */\r\n async batchUpdate(\r\n updates: Array<{ name: string; updates: Partial<Entity> }>\r\n ): Promise<Entity[]> {\r\n // Validate all updates first\r\n for (const { updates: updateData } of updates) {\r\n const validation = UpdateEntitySchema.safeParse(updateData);\r\n if (!validation.success) {\r\n const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);\r\n throw new ValidationError('Invalid update data', errors);\r\n }\r\n }\r\n\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n const updatedEntities: Entity[] = [];\r\n\r\n // OPTIMIZED: Build Map for O(1) lookups instead of O(n) find() per update\r\n const entityIndex = new Map<string, number>();\r\n graph.entities.forEach((e, i) => entityIndex.set(e.name, i));\r\n\r\n for (const { name, updates: updateData } of updates) {\r\n const idx = entityIndex.get(name);\r\n if (idx === undefined) {\r\n throw new EntityNotFoundError(name);\r\n }\r\n const entity = graph.entities[idx];\r\n\r\n // Apply updates (sanitized to prevent prototype pollution)\r\n Object.assign(entity, sanitizeObject(updateData as Record<string, unknown>));\r\n entity.lastModified = timestamp;\r\n updatedEntities.push(entity);\r\n }\r\n\r\n await this.storage.saveGraph(graph);\r\n return updatedEntities;\r\n }\r\n\r\n // ============================================================\r\n // TAG OPERATIONS\r\n // ============================================================\r\n\r\n /**\r\n * Add tags to an entity.\r\n *\r\n * Tags are normalized to lowercase and duplicates are filtered out.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param tags - Tags to add\r\n * @returns Result with entity name and added tags\r\n * @throws {EntityNotFoundError} If entity is not found\r\n */\r\n async addTags(entityName: string, tags: string[]): Promise<{ entityName: string; addedTags: string[] }> {\r\n // OPTIMIZED: Use O(1) NameIndex lookup instead of loadGraph() + O(n) find()\r\n const entity = this.storage.getEntityByName(entityName);\r\n if (!entity) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n // Initialize tags array if it doesn't exist\r\n const existingTags = entity.tags || [];\r\n\r\n // Normalize tags to lowercase and filter out duplicates\r\n const normalizedTags = tags.map(tag => tag.toLowerCase());\r\n const newTags = normalizedTags.filter(tag => !existingTags.includes(tag));\r\n\r\n if (newTags.length > 0) {\r\n // OPTIMIZED: Use updateEntity for in-place update + append\r\n await this.storage.updateEntity(entityName, { tags: [...existingTags, ...newTags] });\r\n }\r\n\r\n return { entityName, addedTags: newTags };\r\n }\r\n\r\n /**\r\n * Remove tags from an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param tags - Tags to remove\r\n * @returns Result with entity name and removed tags\r\n * @throws {EntityNotFoundError} If entity is not found\r\n */\r\n async removeTags(entityName: string, tags: string[]): Promise<{ entityName: string; removedTags: string[] }> {\r\n // OPTIMIZED: Use O(1) NameIndex lookup instead of loadGraph() + O(n) find()\r\n const entity = this.storage.getEntityByName(entityName);\r\n if (!entity) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n if (!entity.tags) {\r\n return { entityName, removedTags: [] };\r\n }\r\n\r\n // Normalize tags to lowercase\r\n const normalizedTags = tags.map(tag => tag.toLowerCase());\r\n const originalLength = entity.tags.length;\r\n\r\n // Capture existing tags (lowercase) BEFORE filtering to accurately track removals\r\n const existingTagsLower = entity.tags.map(t => t.toLowerCase());\r\n\r\n // Filter out the tags to remove\r\n const newTags = entity.tags.filter(tag => !normalizedTags.includes(tag.toLowerCase()));\r\n\r\n // A tag was removed if it existed in the original tags\r\n const removedTags = normalizedTags.filter(tag => existingTagsLower.includes(tag));\r\n\r\n // Update entity via storage if tags were removed\r\n if (newTags.length < originalLength) {\r\n await this.storage.updateEntity(entityName, { tags: newTags });\r\n }\r\n\r\n return { entityName, removedTags };\r\n }\r\n\r\n /**\r\n * Set importance level for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param importance - Importance level (0-10)\r\n * @returns Result with entity name and importance\r\n * @throws {EntityNotFoundError} If entity is not found\r\n * @throws {Error} If importance is out of range\r\n */\r\n async setImportance(entityName: string, importance: number): Promise<{ entityName: string; importance: number }> {\r\n // Validate importance range (0-10)\r\n if (importance < 0 || importance > 10) {\r\n throw new Error(`Importance must be between 0 and 10, got ${importance}`);\r\n }\r\n\r\n // OPTIMIZED: Use O(1) NameIndex lookup instead of loadGraph() + O(n) find()\r\n const entity = this.storage.getEntityByName(entityName);\r\n if (!entity) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n // Use updateEntity for in-place update + append\r\n await this.storage.updateEntity(entityName, { importance });\r\n\r\n return { entityName, importance };\r\n }\r\n\r\n /**\r\n * Add tags to multiple entities in a single operation.\r\n *\r\n * OPTIMIZED: Uses Map for O(1) entity lookups instead of O(n) find() per entity.\r\n *\r\n * @param entityNames - Names of entities to tag\r\n * @param tags - Tags to add to each entity\r\n * @returns Array of results showing which tags were added to each entity\r\n */\r\n async addTagsToMultipleEntities(entityNames: string[], tags: string[]): Promise<{ entityName: string; addedTags: string[] }[]> {\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n const normalizedTags = tags.map(tag => tag.toLowerCase());\r\n const results: { entityName: string; addedTags: string[] }[] = [];\r\n\r\n // OPTIMIZED: Build Map for O(1) lookups instead of O(n) find() per entity\r\n const entityMap = new Map<string, Entity>();\r\n for (const e of graph.entities) {\r\n entityMap.set(e.name, e);\r\n }\r\n\r\n for (const entityName of entityNames) {\r\n const entity = entityMap.get(entityName);\r\n if (!entity) {\r\n continue; // Skip non-existent entities\r\n }\r\n\r\n // Initialize tags array if it doesn't exist\r\n if (!entity.tags) {\r\n entity.tags = [];\r\n }\r\n\r\n // Filter out duplicates\r\n const newTags = normalizedTags.filter(tag => !entity.tags!.includes(tag));\r\n entity.tags.push(...newTags);\r\n\r\n // Update lastModified timestamp if tags were added\r\n if (newTags.length > 0) {\r\n entity.lastModified = timestamp;\r\n }\r\n\r\n results.push({ entityName, addedTags: newTags });\r\n }\r\n\r\n await this.storage.saveGraph(graph);\r\n return results;\r\n }\r\n\r\n /**\r\n * Replace a tag with a new tag across all entities (rename tag).\r\n *\r\n * @param oldTag - Tag to replace\r\n * @param newTag - New tag value\r\n * @returns Result with affected entities and count\r\n */\r\n async replaceTag(oldTag: string, newTag: string): Promise<{ affectedEntities: string[]; count: number }> {\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n const normalizedOldTag = oldTag.toLowerCase();\r\n const normalizedNewTag = newTag.toLowerCase();\r\n const affectedEntities: string[] = [];\r\n\r\n for (const entity of graph.entities) {\r\n if (!entity.tags || !entity.tags.includes(normalizedOldTag)) {\r\n continue;\r\n }\r\n\r\n // Replace old tag with new tag\r\n const index = entity.tags.indexOf(normalizedOldTag);\r\n entity.tags[index] = normalizedNewTag;\r\n entity.lastModified = timestamp;\r\n affectedEntities.push(entity.name);\r\n }\r\n\r\n await this.storage.saveGraph(graph);\r\n return { affectedEntities, count: affectedEntities.length };\r\n }\r\n\r\n /**\r\n * Merge two tags into one target tag across all entities.\r\n *\r\n * Combines tag1 and tag2 into targetTag. Any entity with either tag1 or tag2\r\n * will have both removed and targetTag added (if not already present).\r\n *\r\n * @param tag1 - First tag to merge\r\n * @param tag2 - Second tag to merge\r\n * @param targetTag - Target tag to merge into\r\n * @returns Object with affected entity names and count\r\n */\r\n async mergeTags(tag1: string, tag2: string, targetTag: string): Promise<{ affectedEntities: string[]; count: number }> {\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n const normalizedTag1 = tag1.toLowerCase();\r\n const normalizedTag2 = tag2.toLowerCase();\r\n const normalizedTargetTag = targetTag.toLowerCase();\r\n const affectedEntities: string[] = [];\r\n\r\n for (const entity of graph.entities) {\r\n if (!entity.tags) {\r\n continue;\r\n }\r\n\r\n const hasTag1 = entity.tags.includes(normalizedTag1);\r\n const hasTag2 = entity.tags.includes(normalizedTag2);\r\n\r\n if (!hasTag1 && !hasTag2) {\r\n continue;\r\n }\r\n\r\n // Remove both tags\r\n entity.tags = entity.tags.filter(tag => tag !== normalizedTag1 && tag !== normalizedTag2);\r\n\r\n // Add target tag if not already present\r\n if (!entity.tags.includes(normalizedTargetTag)) {\r\n entity.tags.push(normalizedTargetTag);\r\n }\r\n\r\n entity.lastModified = timestamp;\r\n affectedEntities.push(entity.name);\r\n }\r\n\r\n await this.storage.saveGraph(graph);\r\n return { affectedEntities, count: affectedEntities.length };\r\n }\r\n}\r\n","/**\r\n * Relation Manager\r\n *\r\n * Handles CRUD operations for relations in the knowledge graph.\r\n *\r\n * @module core/RelationManager\r\n */\r\n\r\nimport type { Relation } from '../types/index.js';\r\nimport type { GraphStorage } from './GraphStorage.js';\r\nimport { ValidationError } from '../utils/errors.js';\r\nimport { BatchCreateRelationsSchema, DeleteRelationsSchema } from '../utils/index.js';\r\nimport { GRAPH_LIMITS } from '../utils/constants.js';\r\n\r\n/**\r\n * Manages relation operations with automatic timestamp handling.\r\n */\r\nexport class RelationManager {\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Create multiple relations in a single batch operation.\r\n *\r\n * This method performs the following operations:\r\n * - Validates that all referenced entities exist (prevents dangling relations)\r\n * - Filters out duplicate relations (same from, to, and relationType)\r\n * - Automatically adds createdAt and lastModified timestamps\r\n *\r\n * A relation is considered duplicate if another relation exists with the same:\r\n * - from entity name\r\n * - to entity name\r\n * - relationType\r\n *\r\n * @param relations - Array of relations to create\r\n * @returns Promise resolving to array of newly created relations (excludes duplicates)\r\n * @throws {ValidationError} If any relation references non-existent entities\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new RelationManager(storage);\r\n *\r\n * // Create single relation\r\n * const results = await manager.createRelations([{\r\n * from: 'Alice',\r\n * to: 'Bob',\r\n * relationType: 'works_with'\r\n * }]);\r\n *\r\n * // Create multiple relations at once\r\n * await manager.createRelations([\r\n * { from: 'Alice', to: 'Project_X', relationType: 'contributes_to' },\r\n * { from: 'Bob', to: 'Project_X', relationType: 'leads' },\r\n * { from: 'Charlie', to: 'Alice', relationType: 'reports_to' }\r\n * ]);\r\n *\r\n * // Duplicate relations are filtered out\r\n * await manager.createRelations([\r\n * { from: 'Alice', to: 'Bob', relationType: 'works_with' } // Already exists, won't be added\r\n * ]);\r\n * ```\r\n */\r\n async createRelations(relations: Relation[]): Promise<Relation[]> {\r\n // Validate input\r\n const validation = BatchCreateRelationsSchema.safeParse(relations);\r\n if (!validation.success) {\r\n const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);\r\n throw new ValidationError('Invalid relation data', errors);\r\n }\r\n\r\n // Use read-only graph for checking existing relations and entity existence\r\n const readGraph = await this.storage.loadGraph();\r\n const timestamp = new Date().toISOString();\r\n\r\n // Build set of existing entity names for O(1) lookup\r\n const existingEntityNames = new Set(readGraph.entities.map(e => e.name));\r\n\r\n // Validate that all referenced entities exist (fixes bug 7.2 from analysis)\r\n const danglingRelations: string[] = [];\r\n for (const relation of relations) {\r\n const missingEntities: string[] = [];\r\n if (!existingEntityNames.has(relation.from)) {\r\n missingEntities.push(relation.from);\r\n }\r\n if (!existingEntityNames.has(relation.to)) {\r\n missingEntities.push(relation.to);\r\n }\r\n if (missingEntities.length > 0) {\r\n danglingRelations.push(\r\n `Relation from \"${relation.from}\" to \"${relation.to}\" references non-existent entities: ${missingEntities.join(', ')}`\r\n );\r\n }\r\n }\r\n\r\n if (danglingRelations.length > 0) {\r\n throw new ValidationError('Relations reference non-existent entities', danglingRelations);\r\n }\r\n\r\n // Check graph size limits\r\n const relationsToAdd = relations.filter(r => !readGraph.relations.some(existing =>\r\n existing.from === r.from &&\r\n existing.to === r.to &&\r\n existing.relationType === r.relationType\r\n ));\r\n\r\n if (readGraph.relations.length + relationsToAdd.length > GRAPH_LIMITS.MAX_RELATIONS) {\r\n throw new ValidationError(\r\n 'Graph size limit exceeded',\r\n [`Adding ${relationsToAdd.length} relations would exceed maximum of ${GRAPH_LIMITS.MAX_RELATIONS} relations`]\r\n );\r\n }\r\n\r\n const newRelations = relationsToAdd\r\n .map(r => ({\r\n ...r,\r\n createdAt: r.createdAt || timestamp,\r\n lastModified: r.lastModified || timestamp,\r\n }));\r\n\r\n // Get mutable copy for write operation\r\n const graph = await this.storage.getGraphForMutation();\r\n graph.relations.push(...newRelations);\r\n await this.storage.saveGraph(graph);\r\n\r\n return newRelations;\r\n }\r\n\r\n /**\r\n * Delete multiple relations in a single batch operation.\r\n *\r\n * This method performs the following operations:\r\n * - Removes all specified relations from the graph\r\n * - Automatically updates lastModified timestamp for all affected entities\r\n * - Silently ignores relations that don't exist (no error thrown)\r\n *\r\n * An entity is considered \"affected\" if it appears as either the source (from)\r\n * or target (to) of any deleted relation.\r\n *\r\n * @param relations - Array of relations to delete. Each relation is matched by from, to, and relationType.\r\n * @returns Promise that resolves when deletion is complete\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new RelationManager(storage);\r\n *\r\n * // Delete single relation\r\n * await manager.deleteRelations([{\r\n * from: 'Alice',\r\n * to: 'Bob',\r\n * relationType: 'works_with'\r\n * }]);\r\n *\r\n * // Delete multiple relations at once\r\n * await manager.deleteRelations([\r\n * { from: 'Alice', to: 'Project_X', relationType: 'contributes_to' },\r\n * { from: 'Bob', to: 'Project_X', relationType: 'leads' }\r\n * ]);\r\n * // Note: Alice, Bob, and Project_X will all have their lastModified timestamp updated\r\n *\r\n * // Safe to delete non-existent relations\r\n * await manager.deleteRelations([\r\n * { from: 'NonExistent', to: 'AlsoNonExistent', relationType: 'fake' } // No error\r\n * ]);\r\n * ```\r\n */\r\n async deleteRelations(relations: Relation[]): Promise<void> {\r\n // Validate input\r\n const validation = DeleteRelationsSchema.safeParse(relations);\r\n if (!validation.success) {\r\n const errors = validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);\r\n throw new ValidationError('Invalid relation data', errors);\r\n }\r\n\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n\r\n // Track affected entities\r\n const affectedEntityNames = new Set<string>();\r\n relations.forEach(rel => {\r\n affectedEntityNames.add(rel.from);\r\n affectedEntityNames.add(rel.to);\r\n });\r\n\r\n // OPTIMIZED: Use Set<string> for O(1) lookup instead of O(n) array.some()\r\n // Create composite keys for relations to delete\r\n const relationsToDeleteSet = new Set(\r\n relations.map(r => `${r.from}|${r.to}|${r.relationType}`)\r\n );\r\n\r\n // Remove relations with O(1) Set lookup per relation instead of O(m) array scan\r\n graph.relations = graph.relations.filter(r =>\r\n !relationsToDeleteSet.has(`${r.from}|${r.to}|${r.relationType}`)\r\n );\r\n\r\n // Update lastModified for affected entities\r\n graph.entities.forEach(entity => {\r\n if (affectedEntityNames.has(entity.name)) {\r\n entity.lastModified = timestamp;\r\n }\r\n });\r\n\r\n await this.storage.saveGraph(graph);\r\n }\r\n\r\n /**\r\n * Retrieve all relations involving a specific entity.\r\n *\r\n * This is a read-only operation that returns all relations where the specified\r\n * entity appears as either the source (from) or target (to) of the relation.\r\n * Entity names are case-sensitive.\r\n *\r\n * @param entityName - The unique name of the entity to find relations for\r\n * @returns Promise resolving to array of Relation objects (empty array if no relations found)\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new RelationManager(storage);\r\n *\r\n * // Get all relations for an entity\r\n * const aliceRelations = await manager.getRelations('Alice');\r\n * // Returns: [\r\n * // { from: 'Alice', to: 'Bob', relationType: 'works_with' },\r\n * // { from: 'Alice', to: 'Project_X', relationType: 'contributes_to' },\r\n * // { from: 'Charlie', to: 'Alice', relationType: 'reports_to' }\r\n * // ]\r\n *\r\n * // Process relations by type\r\n * const relations = await manager.getRelations('Alice');\r\n * const outgoing = relations.filter(r => r.from === 'Alice');\r\n * const incoming = relations.filter(r => r.to === 'Alice');\r\n *\r\n * // Handle entity with no relations\r\n * const noRelations = await manager.getRelations('IsolatedEntity');\r\n * console.log(noRelations); // []\r\n * ```\r\n */\r\n async getRelations(entityName: string): Promise<Relation[]> {\r\n // OPTIMIZED: Uses RelationIndex for O(1) lookup instead of O(n) array scan\r\n await this.storage.ensureLoaded();\r\n return this.storage.getRelationsFor(entityName);\r\n }\r\n}\r\n","/**\r\n * Observation Manager\r\n *\r\n * Handles observation CRUD operations for entities.\r\n * Extracted from EntityManager (Phase 4: Consolidate God Objects).\r\n *\r\n * @module core/ObservationManager\r\n */\r\n\r\nimport type { GraphStorage } from './GraphStorage.js';\r\nimport { EntityNotFoundError } from '../utils/errors.js';\r\n\r\n/**\r\n * Manages observation operations for entities in the knowledge graph.\r\n */\r\nexport class ObservationManager {\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Add observations to multiple entities in a single batch operation.\r\n *\r\n * This method performs the following operations:\r\n * - Adds new observations to specified entities\r\n * - Filters out duplicate observations (already present)\r\n * - Updates lastModified timestamp only if new observations were added\r\n * - ATOMIC: All updates are saved in a single operation\r\n *\r\n * @param observations - Array of entity names and observations to add\r\n * @returns Promise resolving to array of results showing which observations were added\r\n * @throws {EntityNotFoundError} If any entity is not found\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new ObservationManager(storage);\r\n *\r\n * // Add observations to multiple entities\r\n * const results = await manager.addObservations([\r\n * { entityName: 'Alice', contents: ['Completed project X', 'Started project Y'] },\r\n * { entityName: 'Bob', contents: ['Joined team meeting'] }\r\n * ]);\r\n *\r\n * // Check what was added (duplicates are filtered out)\r\n * results.forEach(r => {\r\n * console.log(`${r.entityName}: added ${r.addedObservations.length} new observations`);\r\n * });\r\n * ```\r\n */\r\n async addObservations(\r\n observations: { entityName: string; contents: string[] }[]\r\n ): Promise<{ entityName: string; addedObservations: string[] }[]> {\r\n // Get mutable graph for atomic update\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n const results: { entityName: string; addedObservations: string[] }[] = [];\r\n let hasChanges = false;\r\n\r\n for (const o of observations) {\r\n const entity = graph.entities.find(e => e.name === o.entityName);\r\n if (!entity) {\r\n throw new EntityNotFoundError(o.entityName);\r\n }\r\n\r\n const newObservations = o.contents.filter(content => !entity.observations.includes(content));\r\n\r\n if (newObservations.length > 0) {\r\n // Add new observations directly to the entity\r\n entity.observations.push(...newObservations);\r\n entity.lastModified = timestamp;\r\n hasChanges = true;\r\n }\r\n\r\n results.push({ entityName: o.entityName, addedObservations: newObservations });\r\n }\r\n\r\n // Save all changes in a single atomic operation\r\n if (hasChanges) {\r\n await this.storage.saveGraph(graph);\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Delete observations from multiple entities in a single batch operation.\r\n *\r\n * This method performs the following operations:\r\n * - Removes specified observations from entities\r\n * - Updates lastModified timestamp only if observations were deleted\r\n * - Silently ignores entities that don't exist (no error thrown)\r\n * - ATOMIC: All deletions are saved in a single operation\r\n *\r\n * @param deletions - Array of entity names and observations to delete\r\n * @returns Promise that resolves when deletion is complete\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new ObservationManager(storage);\r\n *\r\n * // Delete observations from multiple entities\r\n * await manager.deleteObservations([\r\n * { entityName: 'Alice', observations: ['Old observation 1', 'Old observation 2'] },\r\n * { entityName: 'Bob', observations: ['Outdated info'] }\r\n * ]);\r\n *\r\n * // Safe to delete from non-existent entities (no error)\r\n * await manager.deleteObservations([\r\n * { entityName: 'NonExistent', observations: ['Some text'] }\r\n * ]); // No error thrown\r\n * ```\r\n */\r\n async deleteObservations(\r\n deletions: { entityName: string; observations: string[] }[]\r\n ): Promise<void> {\r\n // Get mutable graph for atomic update\r\n const graph = await this.storage.getGraphForMutation();\r\n const timestamp = new Date().toISOString();\r\n let hasChanges = false;\r\n\r\n deletions.forEach(d => {\r\n const entity = graph.entities.find(e => e.name === d.entityName);\r\n if (entity) {\r\n const originalLength = entity.observations.length;\r\n entity.observations = entity.observations.filter(o => !d.observations.includes(o));\r\n\r\n // Update lastModified timestamp if observations were deleted\r\n if (entity.observations.length < originalLength) {\r\n entity.lastModified = timestamp;\r\n hasChanges = true;\r\n }\r\n }\r\n });\r\n\r\n // Save all changes in a single atomic operation\r\n if (hasChanges) {\r\n await this.storage.saveGraph(graph);\r\n }\r\n }\r\n}\r\n","/**\r\n * Hierarchy Manager\r\n *\r\n * Handles entity hierarchy operations (parent-child relationships).\r\n * Extracted from EntityManager (Phase 4: Consolidate God Objects).\r\n *\r\n * @module core/HierarchyManager\r\n */\r\n\r\nimport type { Entity, KnowledgeGraph, ReadonlyKnowledgeGraph } from '../types/index.js';\r\nimport type { GraphStorage } from './GraphStorage.js';\r\nimport { EntityNotFoundError, CycleDetectedError } from '../utils/errors.js';\r\n\r\n/**\r\n * Manages hierarchical relationships between entities.\r\n */\r\nexport class HierarchyManager {\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Set the parent of an entity.\r\n *\r\n * Validates:\r\n * - Entity and parent exist\r\n * - Setting parent won't create a cycle\r\n *\r\n * @param entityName - Entity to set parent for\r\n * @param parentName - Parent entity name (null to remove parent)\r\n * @returns Updated entity\r\n * @throws {EntityNotFoundError} If entity or parent not found\r\n * @throws {CycleDetectedError} If setting parent would create a cycle\r\n */\r\n async setEntityParent(entityName: string, parentName: string | null): Promise<Entity> {\r\n // Use read-only graph for validation checks\r\n const readGraph = await this.storage.loadGraph();\r\n const entityExists = readGraph.entities.find(e => e.name === entityName);\r\n\r\n if (!entityExists) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n // If setting a parent, validate it exists and doesn't create a cycle\r\n if (parentName !== null) {\r\n const parent = readGraph.entities.find(e => e.name === parentName);\r\n if (!parent) {\r\n throw new EntityNotFoundError(parentName);\r\n }\r\n\r\n // Check for cycles\r\n if (this.wouldCreateCycle(readGraph, entityName, parentName)) {\r\n throw new CycleDetectedError(entityName, parentName);\r\n }\r\n }\r\n\r\n // Get mutable copy for write operation\r\n const graph = await this.storage.getGraphForMutation();\r\n const entity = graph.entities.find(e => e.name === entityName)!;\r\n entity.parentId = parentName || undefined;\r\n entity.lastModified = new Date().toISOString();\r\n\r\n await this.storage.saveGraph(graph);\r\n return entity;\r\n }\r\n\r\n /**\r\n * Check if setting a parent would create a cycle in the hierarchy.\r\n *\r\n * @param graph - Knowledge graph\r\n * @param entityName - Entity to set parent for\r\n * @param parentName - Proposed parent\r\n * @returns True if cycle would be created\r\n */\r\n private wouldCreateCycle(\r\n graph: ReadonlyKnowledgeGraph,\r\n entityName: string,\r\n parentName: string\r\n ): boolean {\r\n const visited = new Set<string>();\r\n let current: string | undefined = parentName;\r\n\r\n while (current) {\r\n if (visited.has(current)) {\r\n return true; // Cycle detected in existing hierarchy\r\n }\r\n if (current === entityName) {\r\n return true; // Would create a cycle\r\n }\r\n visited.add(current);\r\n\r\n const currentEntity = graph.entities.find(e => e.name === current);\r\n current = currentEntity?.parentId;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Get the immediate children of an entity.\r\n *\r\n * @param entityName - Parent entity name\r\n * @returns Array of child entities\r\n * @throws {EntityNotFoundError} If entity not found\r\n */\r\n async getChildren(entityName: string): Promise<Entity[]> {\r\n const graph = await this.storage.loadGraph();\r\n\r\n // Verify entity exists\r\n if (!graph.entities.find(e => e.name === entityName)) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n return graph.entities.filter(e => e.parentId === entityName);\r\n }\r\n\r\n /**\r\n * Get the parent of an entity.\r\n *\r\n * @param entityName - Entity name\r\n * @returns Parent entity or null if no parent\r\n * @throws {EntityNotFoundError} If entity not found\r\n */\r\n async getParent(entityName: string): Promise<Entity | null> {\r\n const graph = await this.storage.loadGraph();\r\n const entity = graph.entities.find(e => e.name === entityName);\r\n\r\n if (!entity) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n if (!entity.parentId) {\r\n return null;\r\n }\r\n\r\n const parent = graph.entities.find(e => e.name === entity.parentId);\r\n return parent || null;\r\n }\r\n\r\n /**\r\n * Get all ancestors of an entity (parent, grandparent, etc.).\r\n *\r\n * @param entityName - Entity name\r\n * @returns Array of ancestor entities (ordered from immediate parent to root)\r\n * @throws {EntityNotFoundError} If entity not found\r\n */\r\n async getAncestors(entityName: string): Promise<Entity[]> {\r\n const graph = await this.storage.loadGraph();\r\n const ancestors: Entity[] = [];\r\n\r\n let current = graph.entities.find(e => e.name === entityName);\r\n if (!current) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n while (current.parentId) {\r\n const parent = graph.entities.find(e => e.name === current!.parentId);\r\n if (!parent) break;\r\n ancestors.push(parent);\r\n current = parent;\r\n }\r\n\r\n return ancestors;\r\n }\r\n\r\n /**\r\n * Get all descendants of an entity (children, grandchildren, etc.).\r\n *\r\n * Uses breadth-first traversal.\r\n *\r\n * @param entityName - Entity name\r\n * @returns Array of descendant entities\r\n * @throws {EntityNotFoundError} If entity not found\r\n */\r\n async getDescendants(entityName: string): Promise<Entity[]> {\r\n const graph = await this.storage.loadGraph();\r\n\r\n // Verify entity exists\r\n if (!graph.entities.find(e => e.name === entityName)) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n const descendants: Entity[] = [];\r\n const toProcess = [entityName];\r\n\r\n while (toProcess.length > 0) {\r\n const current = toProcess.shift()!;\r\n const children = graph.entities.filter(e => e.parentId === current);\r\n\r\n for (const child of children) {\r\n descendants.push(child);\r\n toProcess.push(child.name);\r\n }\r\n }\r\n\r\n return descendants;\r\n }\r\n\r\n /**\r\n * Get the entire subtree rooted at an entity (entity + all descendants).\r\n *\r\n * Includes relations between entities in the subtree.\r\n *\r\n * @param entityName - Root entity name\r\n * @returns Knowledge graph containing subtree\r\n * @throws {EntityNotFoundError} If entity not found\r\n */\r\n async getSubtree(entityName: string): Promise<KnowledgeGraph> {\r\n const graph = await this.storage.loadGraph();\r\n const entity = graph.entities.find(e => e.name === entityName);\r\n\r\n if (!entity) {\r\n throw new EntityNotFoundError(entityName);\r\n }\r\n\r\n const descendants = await this.getDescendants(entityName);\r\n const subtreeEntities = [entity, ...descendants];\r\n const subtreeEntityNames = new Set(subtreeEntities.map(e => e.name));\r\n\r\n // Include relations between entities in the subtree\r\n const subtreeRelations = graph.relations.filter(\r\n r => subtreeEntityNames.has(r.from) && subtreeEntityNames.has(r.to)\r\n );\r\n\r\n return {\r\n entities: subtreeEntities,\r\n relations: subtreeRelations,\r\n };\r\n }\r\n\r\n /**\r\n * Get root entities (entities with no parent).\r\n *\r\n * @returns Array of root entities\r\n */\r\n async getRootEntities(): Promise<Entity[]> {\r\n const graph = await this.storage.loadGraph();\r\n return graph.entities.filter(e => !e.parentId);\r\n }\r\n\r\n /**\r\n * Get the depth of an entity in the hierarchy.\r\n *\r\n * Root entities have depth 0, their children depth 1, etc.\r\n *\r\n * @param entityName - Entity name\r\n * @returns Depth (number of ancestors)\r\n * @throws {EntityNotFoundError} If entity not found\r\n */\r\n async getEntityDepth(entityName: string): Promise<number> {\r\n const ancestors = await this.getAncestors(entityName);\r\n return ancestors.length;\r\n }\r\n\r\n /**\r\n * Move an entity to a new parent (maintaining its descendants).\r\n *\r\n * Alias for setEntityParent.\r\n *\r\n * @param entityName - Entity to move\r\n * @param newParentName - New parent name (null to make root)\r\n * @returns Updated entity\r\n */\r\n async moveEntity(entityName: string, newParentName: string | null): Promise<Entity> {\r\n return await this.setEntityParent(entityName, newParentName);\r\n }\r\n}\r\n","/**\r\n * Graph Traversal\r\n *\r\n * Phase 4 Sprints 6-8: Graph traversal algorithms for knowledge graph analysis.\r\n * Includes BFS, DFS, shortest path, all paths, connected components, and centrality.\r\n *\r\n * @module core/GraphTraversal\r\n */\r\n\r\nimport type {\r\n Entity,\r\n Relation,\r\n TraversalOptions,\r\n TraversalResult,\r\n PathResult,\r\n ConnectedComponentsResult,\r\n CentralityResult,\r\n AccessContext,\r\n} from '../types/index.js';\r\nimport type { GraphStorage } from './GraphStorage.js';\r\nimport type { AccessTracker } from '../agent/AccessTracker.js';\r\nimport { checkCancellation } from '../utils/index.js';\r\n\r\n/**\r\n * Extended traversal options with access tracking support.\r\n */\r\nexport interface TraversalOptionsWithTracking extends TraversalOptions {\r\n /** Enable access tracking for visited nodes */\r\n trackAccess?: boolean;\r\n /** Session ID for access context */\r\n sessionId?: string;\r\n /** Task ID for access context */\r\n taskId?: string;\r\n}\r\n\r\n/**\r\n * Phase 4 Sprint 6: Default traversal options.\r\n */\r\nconst DEFAULT_OPTIONS: Required<TraversalOptions> = {\r\n direction: 'both',\r\n maxDepth: Infinity,\r\n relationTypes: [],\r\n entityTypes: [],\r\n};\r\n\r\n/**\r\n * Graph traversal algorithms for knowledge graph analysis.\r\n *\r\n * Provides BFS, DFS, shortest path finding, connected component detection,\r\n * and centrality metrics for analyzing graph structure.\r\n */\r\nexport class GraphTraversal {\r\n private accessTracker?: AccessTracker;\r\n\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Set the AccessTracker for optional access tracking.\r\n * When set, traversal methods can track access to visited entities.\r\n *\r\n * @param tracker - AccessTracker instance\r\n */\r\n setAccessTracker(tracker: AccessTracker): void {\r\n this.accessTracker = tracker;\r\n }\r\n\r\n /**\r\n * Track access for visited nodes during traversal.\r\n * @internal\r\n */\r\n private async trackTraversalAccess(\r\n nodes: string[],\r\n options: TraversalOptionsWithTracking\r\n ): Promise<void> {\r\n if (!this.accessTracker || nodes.length === 0) return;\r\n\r\n const context: AccessContext = {\r\n sessionId: options.sessionId,\r\n taskId: options.taskId,\r\n retrievalMethod: 'traversal',\r\n };\r\n\r\n // Batch record all visited nodes\r\n await Promise.all(\r\n nodes.map((name) => this.accessTracker!.recordAccess(name, context))\r\n );\r\n }\r\n\r\n // ==================== Sprint 6: BFS and DFS Traversal ====================\r\n\r\n /**\r\n * Get neighbors of a node based on traversal direction and filters.\r\n *\r\n * @param entityName - Entity to get neighbors for\r\n * @param options - Traversal options\r\n * @returns Array of neighbor entity names with their relations\r\n */\r\n getNeighborsWithRelations(\r\n entityName: string,\r\n options: TraversalOptions = {}\r\n ): Array<{ neighbor: string; relation: Relation }> {\r\n // Filter out undefined values before merging with defaults\r\n const definedOptions = Object.fromEntries(\r\n Object.entries(options).filter(([, v]) => v !== undefined)\r\n );\r\n const opts = { ...DEFAULT_OPTIONS, ...definedOptions };\r\n const neighbors: Array<{ neighbor: string; relation: Relation }> = [];\r\n\r\n // Get relations based on direction\r\n let relations: Relation[] = [];\r\n if (opts.direction === 'outgoing' || opts.direction === 'both') {\r\n relations = relations.concat(this.storage.getRelationsFrom(entityName));\r\n }\r\n if (opts.direction === 'incoming' || opts.direction === 'both') {\r\n relations = relations.concat(this.storage.getRelationsTo(entityName));\r\n }\r\n\r\n // Filter by relation types if specified\r\n if (opts.relationTypes && opts.relationTypes.length > 0) {\r\n const typeSet = new Set(opts.relationTypes.map(t => t.toLowerCase()));\r\n relations = relations.filter(r => typeSet.has(r.relationType.toLowerCase()));\r\n }\r\n\r\n // Process relations to get neighbors\r\n for (const relation of relations) {\r\n const neighbor = relation.from === entityName ? relation.to : relation.from;\r\n\r\n // Skip self-loops\r\n if (neighbor === entityName) continue;\r\n\r\n // Filter by entity types if specified\r\n if (opts.entityTypes && opts.entityTypes.length > 0) {\r\n const entity = this.storage.getEntityByName(neighbor);\r\n if (!entity) continue;\r\n const typeSet = new Set(opts.entityTypes.map(t => t.toLowerCase()));\r\n if (!typeSet.has(entity.entityType.toLowerCase())) continue;\r\n }\r\n\r\n neighbors.push({ neighbor, relation });\r\n }\r\n\r\n return neighbors;\r\n }\r\n\r\n /**\r\n * Breadth-First Search traversal starting from a given entity.\r\n *\r\n * @param startEntity - Entity name to start traversal from\r\n * @param options - Traversal options\r\n * @returns Traversal result with visited nodes, depths, and parent pointers\r\n */\r\n bfs(startEntity: string, options: TraversalOptions = {}): TraversalResult {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n // Validate start entity exists\r\n if (!this.storage.hasEntity(startEntity)) {\r\n return { nodes: [], depths: new Map(), parents: new Map() };\r\n }\r\n\r\n const visited = new Set<string>();\r\n const queue: Array<{ node: string; depth: number }> = [{ node: startEntity, depth: 0 }];\r\n const nodes: string[] = [];\r\n const depths = new Map<string, number>();\r\n const parents = new Map<string, string | null>();\r\n\r\n visited.add(startEntity);\r\n parents.set(startEntity, null);\r\n\r\n while (queue.length > 0) {\r\n const { node, depth } = queue.shift()!;\r\n\r\n // Respect maxDepth limit\r\n if (depth > opts.maxDepth) continue;\r\n\r\n nodes.push(node);\r\n depths.set(node, depth);\r\n\r\n // Get neighbors and add unvisited ones to queue\r\n const neighbors = this.getNeighborsWithRelations(node, opts);\r\n for (const { neighbor } of neighbors) {\r\n if (!visited.has(neighbor)) {\r\n visited.add(neighbor);\r\n queue.push({ node: neighbor, depth: depth + 1 });\r\n parents.set(neighbor, node);\r\n }\r\n }\r\n }\r\n\r\n return { nodes, depths, parents };\r\n }\r\n\r\n /**\r\n * Depth-First Search traversal starting from a given entity.\r\n *\r\n * @param startEntity - Entity name to start traversal from\r\n * @param options - Traversal options\r\n * @returns Traversal result with visited nodes, depths, and parent pointers\r\n */\r\n dfs(startEntity: string, options: TraversalOptions = {}): TraversalResult {\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n\r\n // Validate start entity exists\r\n if (!this.storage.hasEntity(startEntity)) {\r\n return { nodes: [], depths: new Map(), parents: new Map() };\r\n }\r\n\r\n const visited = new Set<string>();\r\n const stack: Array<{ node: string; depth: number }> = [{ node: startEntity, depth: 0 }];\r\n const nodes: string[] = [];\r\n const depths = new Map<string, number>();\r\n const parents = new Map<string, string | null>();\r\n\r\n parents.set(startEntity, null);\r\n\r\n while (stack.length > 0) {\r\n const { node, depth } = stack.pop()!;\r\n\r\n // Skip if already visited\r\n if (visited.has(node)) continue;\r\n\r\n // Respect maxDepth limit\r\n if (depth > opts.maxDepth) continue;\r\n\r\n visited.add(node);\r\n nodes.push(node);\r\n depths.set(node, depth);\r\n\r\n // Get neighbors and add unvisited ones to stack\r\n const neighbors = this.getNeighborsWithRelations(node, opts);\r\n for (const { neighbor } of neighbors) {\r\n if (!visited.has(neighbor)) {\r\n stack.push({ node: neighbor, depth: depth + 1 });\r\n if (!parents.has(neighbor)) {\r\n parents.set(neighbor, node);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return { nodes, depths, parents };\r\n }\r\n\r\n // ==================== Sprint 7: Path Finding Algorithms ====================\r\n\r\n /**\r\n * Find the shortest path between two entities using BFS.\r\n *\r\n * @param source - Source entity name\r\n * @param target - Target entity name\r\n * @param options - Traversal options (including optional access tracking)\r\n * @returns PathResult if path exists, null otherwise\r\n */\r\n async findShortestPath(\r\n source: string,\r\n target: string,\r\n options: TraversalOptionsWithTracking = {}\r\n ): Promise<PathResult | null> {\r\n // Ensure graph is loaded to populate indexes\r\n await this.storage.loadGraph();\r\n\r\n // Validate entities exist\r\n if (!this.storage.hasEntity(source) || !this.storage.hasEntity(target)) {\r\n return null;\r\n }\r\n\r\n // Same source and target\r\n if (source === target) {\r\n const result = { path: [source], length: 0, relations: [] };\r\n // Track access if enabled\r\n if (options.trackAccess && this.accessTracker) {\r\n await this.trackTraversalAccess(result.path, options);\r\n }\r\n return result;\r\n }\r\n\r\n const opts = { ...DEFAULT_OPTIONS, ...options };\r\n const visited = new Set<string>();\r\n const queue: string[] = [source];\r\n const parents = new Map<string, { parent: string; relation: Relation } | null>();\r\n\r\n visited.add(source);\r\n parents.set(source, null);\r\n\r\n while (queue.length > 0) {\r\n const current = queue.shift()!;\r\n\r\n // Found target, reconstruct path\r\n if (current === target) {\r\n const result = this.reconstructPath(source, target, parents);\r\n // Track access if enabled\r\n if (options.trackAccess && this.accessTracker) {\r\n await this.trackTraversalAccess(result.path, options);\r\n }\r\n return result;\r\n }\r\n\r\n // Get neighbors\r\n const neighbors = this.getNeighborsWithRelations(current, opts);\r\n for (const { neighbor, relation } of neighbors) {\r\n if (!visited.has(neighbor)) {\r\n visited.add(neighbor);\r\n queue.push(neighbor);\r\n parents.set(neighbor, { parent: current, relation });\r\n }\r\n }\r\n }\r\n\r\n // No path found\r\n return null;\r\n }\r\n\r\n /**\r\n * Reconstruct path from parent pointers.\r\n */\r\n private reconstructPath(\r\n _source: string,\r\n target: string,\r\n parents: Map<string, { parent: string; relation: Relation } | null>\r\n ): PathResult {\r\n const path: string[] = [];\r\n const relations: Relation[] = [];\r\n let current: string | null = target;\r\n\r\n while (current !== null) {\r\n path.unshift(current);\r\n const parentInfo = parents.get(current);\r\n if (parentInfo) {\r\n relations.unshift(parentInfo.relation);\r\n current = parentInfo.parent;\r\n } else {\r\n current = null;\r\n }\r\n }\r\n\r\n return {\r\n path,\r\n length: path.length - 1,\r\n relations,\r\n };\r\n }\r\n\r\n /**\r\n * Find all paths between two entities up to a maximum depth.\r\n *\r\n * Phase 9B: Supports cancellation via AbortSignal in options.\r\n *\r\n * @param source - Source entity name\r\n * @param target - Target entity name\r\n * @param maxDepth - Maximum path length (default: 5)\r\n * @param options - Traversal options (includes signal for cancellation and access tracking)\r\n * @returns Array of PathResult objects for all found paths\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n */\r\n async findAllPaths(\r\n source: string,\r\n target: string,\r\n maxDepth: number = 5,\r\n options: TraversalOptionsWithTracking & { signal?: AbortSignal } = {}\r\n ): Promise<PathResult[]> {\r\n // Check for early cancellation\r\n const { signal, ...traversalOptions } = options;\r\n checkCancellation(signal, 'findAllPaths');\r\n\r\n // Ensure graph is loaded to populate indexes\r\n await this.storage.loadGraph();\r\n\r\n // Check for cancellation after load\r\n checkCancellation(signal, 'findAllPaths');\r\n\r\n // Validate entities exist\r\n if (!this.storage.hasEntity(source) || !this.storage.hasEntity(target)) {\r\n return [];\r\n }\r\n\r\n const opts = { ...DEFAULT_OPTIONS, ...traversalOptions };\r\n const allPaths: PathResult[] = [];\r\n const currentPath: string[] = [source];\r\n const currentRelations: Relation[] = [];\r\n const visited = new Set<string>([source]);\r\n\r\n // Track iterations for periodic cancellation checks\r\n let iterationCount = 0;\r\n const CANCELLATION_CHECK_INTERVAL = 100;\r\n\r\n const dfsAllPaths = (current: string, depth: number) => {\r\n // Periodic cancellation check\r\n iterationCount++;\r\n if (iterationCount % CANCELLATION_CHECK_INTERVAL === 0) {\r\n checkCancellation(signal, 'findAllPaths');\r\n }\r\n\r\n if (depth > maxDepth) return;\r\n\r\n if (current === target && depth > 0) {\r\n allPaths.push({\r\n path: [...currentPath],\r\n length: currentPath.length - 1,\r\n relations: [...currentRelations],\r\n });\r\n return;\r\n }\r\n\r\n const neighbors = this.getNeighborsWithRelations(current, opts);\r\n for (const { neighbor, relation } of neighbors) {\r\n if (!visited.has(neighbor)) {\r\n visited.add(neighbor);\r\n currentPath.push(neighbor);\r\n currentRelations.push(relation);\r\n\r\n dfsAllPaths(neighbor, depth + 1);\r\n\r\n currentPath.pop();\r\n currentRelations.pop();\r\n visited.delete(neighbor);\r\n }\r\n }\r\n };\r\n\r\n dfsAllPaths(source, 0);\r\n\r\n // Track access if enabled - collect unique nodes from all paths\r\n if (options.trackAccess && this.accessTracker && allPaths.length > 0) {\r\n const uniqueNodes = new Set<string>();\r\n for (const pathResult of allPaths) {\r\n for (const node of pathResult.path) {\r\n uniqueNodes.add(node);\r\n }\r\n }\r\n await this.trackTraversalAccess(Array.from(uniqueNodes), options);\r\n }\r\n\r\n return allPaths;\r\n }\r\n\r\n // ==================== Sprint 8: Connected Components ====================\r\n\r\n /**\r\n * Find all connected components in the graph.\r\n *\r\n * Uses BFS to find all weakly connected components (treating the graph as undirected).\r\n *\r\n * @returns ConnectedComponentsResult with all components\r\n */\r\n async findConnectedComponents(): Promise<ConnectedComponentsResult> {\r\n const graph = await this.storage.loadGraph();\r\n const visited = new Set<string>();\r\n const components: string[][] = [];\r\n\r\n for (const entity of graph.entities) {\r\n if (!visited.has(entity.name)) {\r\n // BFS to find all nodes in this component\r\n const component: string[] = [];\r\n const queue: string[] = [entity.name];\r\n visited.add(entity.name);\r\n\r\n while (queue.length > 0) {\r\n const current = queue.shift()!;\r\n component.push(current);\r\n\r\n // Get all neighbors (both directions for weakly connected)\r\n const neighbors = this.getNeighborsWithRelations(current, { direction: 'both' });\r\n for (const { neighbor } of neighbors) {\r\n if (!visited.has(neighbor)) {\r\n visited.add(neighbor);\r\n queue.push(neighbor);\r\n }\r\n }\r\n }\r\n\r\n components.push(component);\r\n }\r\n }\r\n\r\n // Sort components by size (largest first)\r\n components.sort((a, b) => b.length - a.length);\r\n\r\n return {\r\n components,\r\n count: components.length,\r\n largestComponentSize: components.length > 0 ? components[0].length : 0,\r\n };\r\n }\r\n\r\n // ==================== Sprint 8: Centrality Algorithms ====================\r\n\r\n /**\r\n * Calculate degree centrality for all entities.\r\n *\r\n * Degree centrality is the number of connections an entity has,\r\n * normalized by the maximum possible connections.\r\n *\r\n * @param direction - Direction to count: 'in', 'out', or 'both' (default)\r\n * @param topN - Number of top entities to return (default: 10)\r\n * @returns CentralityResult with scores and top entities\r\n */\r\n async calculateDegreeCentrality(\r\n direction: 'in' | 'out' | 'both' = 'both',\r\n topN: number = 10\r\n ): Promise<CentralityResult> {\r\n const graph = await this.storage.loadGraph();\r\n const scores = new Map<string, number>();\r\n const n = graph.entities.length;\r\n\r\n // Calculate degree for each entity\r\n for (const entity of graph.entities) {\r\n let degree = 0;\r\n\r\n if (direction === 'in' || direction === 'both') {\r\n degree += this.storage.getRelationsTo(entity.name).length;\r\n }\r\n if (direction === 'out' || direction === 'both') {\r\n degree += this.storage.getRelationsFrom(entity.name).length;\r\n }\r\n\r\n // Normalize by maximum possible degree\r\n const normalizedDegree = n > 1 ? degree / (n - 1) : 0;\r\n scores.set(entity.name, normalizedDegree);\r\n }\r\n\r\n // Get top N entities\r\n const topEntities = this.getTopEntities(scores, topN);\r\n\r\n return {\r\n scores,\r\n topEntities,\r\n algorithm: 'degree',\r\n };\r\n }\r\n\r\n /**\r\n * Calculate betweenness centrality for all entities.\r\n *\r\n * Betweenness centrality measures how often a node appears on shortest paths\r\n * between other nodes. Uses Brandes' algorithm for efficiency.\r\n *\r\n * @param options - Configuration options\r\n * @param options.topN - Number of top entities to return (default: 10)\r\n * @param options.chunkSize - Yield control every N vertices (default: 50)\r\n * @param options.onProgress - Progress callback (0.0 to 1.0)\r\n * @param options.approximate - Use approximation for faster results (default: false)\r\n * @param options.sampleRate - Sample rate for approximation (default: 0.2)\r\n * @returns CentralityResult with scores and top entities\r\n */\r\n async calculateBetweennessCentrality(\r\n options: {\r\n topN?: number;\r\n chunkSize?: number;\r\n onProgress?: (progress: number) => void;\r\n approximate?: boolean;\r\n sampleRate?: number;\r\n } = {}\r\n ): Promise<CentralityResult> {\r\n const { topN = 10, chunkSize = 50, onProgress, approximate = false, sampleRate = 0.2 } = options;\r\n const graph = await this.storage.loadGraph();\r\n const scores = new Map<string, number>();\r\n\r\n // Initialize scores\r\n for (const entity of graph.entities) {\r\n scores.set(entity.name, 0);\r\n }\r\n\r\n // Determine which sources to process (full or sampled)\r\n let sourcesToProcess = graph.entities;\r\n if (approximate && graph.entities.length > 100) {\r\n const sampleSize = Math.max(10, Math.floor(graph.entities.length * sampleRate));\r\n sourcesToProcess = this.sampleEntities(graph.entities, sampleSize);\r\n }\r\n\r\n // Brandes' algorithm with chunked processing\r\n let processed = 0;\r\n for (const source of sourcesToProcess) {\r\n const stack: string[] = [];\r\n const predecessors = new Map<string, string[]>();\r\n const sigma = new Map<string, number>(); // Number of shortest paths\r\n const distance = new Map<string, number>(); // Distance from source\r\n const delta = new Map<string, number>(); // Dependency\r\n\r\n // Initialize\r\n for (const entity of graph.entities) {\r\n predecessors.set(entity.name, []);\r\n sigma.set(entity.name, 0);\r\n distance.set(entity.name, -1);\r\n delta.set(entity.name, 0);\r\n }\r\n\r\n sigma.set(source.name, 1);\r\n distance.set(source.name, 0);\r\n\r\n // BFS\r\n const queue: string[] = [source.name];\r\n while (queue.length > 0) {\r\n const v = queue.shift()!;\r\n stack.push(v);\r\n\r\n const neighbors = this.getNeighborsWithRelations(v, { direction: 'both' });\r\n for (const { neighbor: w } of neighbors) {\r\n // First time w is discovered\r\n if (distance.get(w) === -1) {\r\n distance.set(w, distance.get(v)! + 1);\r\n queue.push(w);\r\n }\r\n\r\n // w is on a shortest path from source via v\r\n if (distance.get(w) === distance.get(v)! + 1) {\r\n sigma.set(w, sigma.get(w)! + sigma.get(v)!);\r\n predecessors.get(w)!.push(v);\r\n }\r\n }\r\n }\r\n\r\n // Accumulation\r\n while (stack.length > 0) {\r\n const w = stack.pop()!;\r\n for (const v of predecessors.get(w)!) {\r\n const contribution = (sigma.get(v)! / sigma.get(w)!) * (1 + delta.get(w)!);\r\n delta.set(v, delta.get(v)! + contribution);\r\n }\r\n if (w !== source.name) {\r\n scores.set(w, scores.get(w)! + delta.get(w)!);\r\n }\r\n }\r\n\r\n // Yield control periodically to prevent blocking event loop\r\n processed++;\r\n if (processed % chunkSize === 0) {\r\n // Yield control to allow event loop to process other events\r\n await new Promise(resolve => setImmediate(resolve));\r\n\r\n // Report progress\r\n if (onProgress) {\r\n onProgress(processed / sourcesToProcess.length);\r\n }\r\n }\r\n }\r\n\r\n // Final progress update\r\n if (onProgress) {\r\n onProgress(1);\r\n }\r\n\r\n // Scale scores if using approximation\r\n if (approximate && sampleRate < 1.0) {\r\n const scaleFactor = 1 / sampleRate;\r\n for (const [entity, score] of scores) {\r\n scores.set(entity, score * scaleFactor);\r\n }\r\n }\r\n\r\n // Normalize scores\r\n const n = graph.entities.length;\r\n const normalization = n > 2 ? 2 / ((n - 1) * (n - 2)) : 1;\r\n for (const [name, score] of scores) {\r\n scores.set(name, score * normalization);\r\n }\r\n\r\n const topEntities = this.getTopEntities(scores, topN);\r\n\r\n return {\r\n scores,\r\n topEntities,\r\n algorithm: 'betweenness',\r\n };\r\n }\r\n\r\n /**\r\n * Sample entities randomly for approximation algorithms.\r\n *\r\n * @param entities - Array of entities to sample from\r\n * @param sampleSize - Number of entities to sample\r\n * @returns Array of sampled entities\r\n */\r\n private sampleEntities(entities: readonly Entity[], sampleSize: number): Entity[] {\r\n const shuffled = [...entities];\r\n // Fisher-Yates shuffle\r\n for (let i = shuffled.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1));\r\n [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];\r\n }\r\n return shuffled.slice(0, sampleSize);\r\n }\r\n\r\n /**\r\n * Calculate PageRank centrality for all entities.\r\n *\r\n * PageRank measures importance based on incoming connections from\r\n * other important nodes. Uses iterative power method.\r\n *\r\n * @param dampingFactor - Damping factor (default: 0.85)\r\n * @param maxIterations - Maximum iterations (default: 100)\r\n * @param tolerance - Convergence tolerance (default: 1e-6)\r\n * @param topN - Number of top entities to return (default: 10)\r\n * @returns CentralityResult with scores and top entities\r\n */\r\n async calculatePageRank(\r\n dampingFactor: number = 0.85,\r\n maxIterations: number = 100,\r\n tolerance: number = 1e-6,\r\n topN: number = 10\r\n ): Promise<CentralityResult> {\r\n const graph = await this.storage.loadGraph();\r\n const n = graph.entities.length;\r\n\r\n if (n === 0) {\r\n return { scores: new Map(), topEntities: [], algorithm: 'pagerank' };\r\n }\r\n\r\n // Initialize PageRank scores\r\n const scores = new Map<string, number>();\r\n const initialScore = 1 / n;\r\n for (const entity of graph.entities) {\r\n scores.set(entity.name, initialScore);\r\n }\r\n\r\n // Build outgoing links map\r\n const outLinks = new Map<string, string[]>();\r\n for (const entity of graph.entities) {\r\n const outgoing = this.storage.getRelationsFrom(entity.name);\r\n outLinks.set(entity.name, outgoing.map(r => r.to));\r\n }\r\n\r\n // Power iteration\r\n for (let iteration = 0; iteration < maxIterations; iteration++) {\r\n const newScores = new Map<string, number>();\r\n let totalDiff = 0;\r\n\r\n // Calculate dangling node contribution (nodes with no outgoing links)\r\n let danglingSum = 0;\r\n for (const entity of graph.entities) {\r\n if (outLinks.get(entity.name)!.length === 0) {\r\n danglingSum += scores.get(entity.name)!;\r\n }\r\n }\r\n const danglingContribution = (dampingFactor * danglingSum) / n;\r\n\r\n // Calculate new scores\r\n for (const entity of graph.entities) {\r\n let incomingScore = 0;\r\n const incoming = this.storage.getRelationsTo(entity.name);\r\n\r\n for (const relation of incoming) {\r\n const source = relation.from;\r\n const sourceOutCount = outLinks.get(source)?.length || 1;\r\n incomingScore += scores.get(source)! / sourceOutCount;\r\n }\r\n\r\n const newScore =\r\n (1 - dampingFactor) / n + dampingFactor * incomingScore + danglingContribution;\r\n\r\n newScores.set(entity.name, newScore);\r\n totalDiff += Math.abs(newScore - scores.get(entity.name)!);\r\n }\r\n\r\n // Update scores\r\n for (const [name, score] of newScores) {\r\n scores.set(name, score);\r\n }\r\n\r\n // Check convergence\r\n if (totalDiff < tolerance) {\r\n break;\r\n }\r\n }\r\n\r\n const topEntities = this.getTopEntities(scores, topN);\r\n\r\n return {\r\n scores,\r\n topEntities,\r\n algorithm: 'pagerank',\r\n };\r\n }\r\n\r\n /**\r\n * Get top N entities from a scores map.\r\n */\r\n private getTopEntities(\r\n scores: Map<string, number>,\r\n topN: number\r\n ): Array<{ name: string; score: number }> {\r\n return Array.from(scores.entries())\r\n .sort((a, b) => b[1] - a[1])\r\n .slice(0, topN)\r\n .map(([name, score]) => ({ name, score }));\r\n }\r\n}\r\n","/**\r\n * Search Filter Chain\r\n *\r\n * Centralizes filter logic for all search implementations to eliminate\r\n * duplicate filtering code across BasicSearch, BooleanSearch, FuzzySearch,\r\n * and RankedSearch.\r\n *\r\n * @module search/SearchFilterChain\r\n */\r\n\r\nimport type { Entity } from '../types/index.js';\r\nimport {\r\n normalizeTags,\r\n hasMatchingTag,\r\n isWithinImportanceRange,\r\n validatePagination,\r\n applyPagination,\r\n type ValidatedPagination,\r\n} from '../utils/index.js';\r\n\r\n/**\r\n * Search filter configuration options.\r\n * All filters are optional - entities pass if filter is not specified.\r\n */\r\nexport interface SearchFilters {\r\n /** Tags to filter by (any match) */\r\n tags?: string[];\r\n /** Minimum importance (0-10, inclusive) */\r\n minImportance?: number;\r\n /** Maximum importance (0-10, inclusive) */\r\n maxImportance?: number;\r\n /** Entity type to filter by (exact match) */\r\n entityType?: string;\r\n /** Created after date (ISO 8601, inclusive) */\r\n createdAfter?: string;\r\n /** Created before date (ISO 8601, inclusive) */\r\n createdBefore?: string;\r\n /** Modified after date (ISO 8601, inclusive) */\r\n modifiedAfter?: string;\r\n /** Modified before date (ISO 8601, inclusive) */\r\n modifiedBefore?: string;\r\n}\r\n\r\n/**\r\n * Centralized filter chain for all search implementations.\r\n * Ensures consistent filtering behavior across search types.\r\n *\r\n * @example\r\n * ```typescript\r\n * const filters: SearchFilters = { tags: ['important'], minImportance: 5 };\r\n * const filtered = SearchFilterChain.applyFilters(entities, filters);\r\n * const pagination = SearchFilterChain.validatePagination(0, 50);\r\n * const result = SearchFilterChain.paginate(filtered, pagination);\r\n * ```\r\n */\r\nexport class SearchFilterChain {\r\n /**\r\n * Applies all filters to an array of entities.\r\n * Entities must pass ALL specified filters to be included.\r\n *\r\n * @param entities - Entities to filter\r\n * @param filters - Filter criteria to apply\r\n * @returns Filtered entities array\r\n */\r\n static applyFilters(entities: readonly Entity[], filters: SearchFilters): Entity[] {\r\n // Early return if no filters are active\r\n if (!this.hasActiveFilters(filters)) {\r\n return [...entities];\r\n }\r\n\r\n // Pre-normalize tags once for efficiency\r\n const normalizedSearchTags = filters.tags?.length\r\n ? normalizeTags(filters.tags)\r\n : undefined;\r\n\r\n return entities.filter(entity =>\r\n this.entityPassesFilters(entity, filters, normalizedSearchTags)\r\n );\r\n }\r\n\r\n /**\r\n * Checks if an entity passes all active filters.\r\n * Short-circuits on first failing filter for performance.\r\n *\r\n * @param entity - Entity to check\r\n * @param filters - Filter criteria\r\n * @param normalizedSearchTags - Pre-normalized search tags (for efficiency)\r\n * @returns true if entity passes all filters\r\n */\r\n static entityPassesFilters(\r\n entity: Entity,\r\n filters: SearchFilters,\r\n normalizedSearchTags?: string[]\r\n ): boolean {\r\n // Tag filter - check if entity has any matching tag\r\n if (normalizedSearchTags && normalizedSearchTags.length > 0) {\r\n if (!entity.tags || entity.tags.length === 0) {\r\n return false;\r\n }\r\n const entityTags = normalizeTags(entity.tags);\r\n const hasMatch = normalizedSearchTags.some(tag => entityTags.includes(tag));\r\n if (!hasMatch) {\r\n return false;\r\n }\r\n }\r\n\r\n // Importance filter\r\n if (!isWithinImportanceRange(entity.importance, filters.minImportance, filters.maxImportance)) {\r\n return false;\r\n }\r\n\r\n // Entity type filter\r\n if (filters.entityType && entity.entityType !== filters.entityType) {\r\n return false;\r\n }\r\n\r\n // Created date filter\r\n if (filters.createdAfter || filters.createdBefore) {\r\n if (!entity.createdAt) {\r\n return false;\r\n }\r\n const createdAt = new Date(entity.createdAt);\r\n if (filters.createdAfter && createdAt < new Date(filters.createdAfter)) {\r\n return false;\r\n }\r\n if (filters.createdBefore && createdAt > new Date(filters.createdBefore)) {\r\n return false;\r\n }\r\n }\r\n\r\n // Modified date filter\r\n if (filters.modifiedAfter || filters.modifiedBefore) {\r\n if (!entity.lastModified) {\r\n return false;\r\n }\r\n const modifiedAt = new Date(entity.lastModified);\r\n if (filters.modifiedAfter && modifiedAt < new Date(filters.modifiedAfter)) {\r\n return false;\r\n }\r\n if (filters.modifiedBefore && modifiedAt > new Date(filters.modifiedBefore)) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Checks if any filters are actually specified.\r\n * Used for early return optimization.\r\n *\r\n * @param filters - Filter criteria to check\r\n * @returns true if at least one filter is active\r\n */\r\n static hasActiveFilters(filters: SearchFilters): boolean {\r\n return !!(\r\n (filters.tags && filters.tags.length > 0) ||\r\n filters.minImportance !== undefined ||\r\n filters.maxImportance !== undefined ||\r\n filters.entityType ||\r\n filters.createdAfter ||\r\n filters.createdBefore ||\r\n filters.modifiedAfter ||\r\n filters.modifiedBefore\r\n );\r\n }\r\n\r\n /**\r\n * Validates and returns pagination parameters.\r\n * Delegates to paginationUtils.validatePagination.\r\n *\r\n * @param offset - Starting position\r\n * @param limit - Maximum results\r\n * @returns Validated pagination object\r\n */\r\n static validatePagination(offset: number = 0, limit?: number): ValidatedPagination {\r\n return validatePagination(offset, limit);\r\n }\r\n\r\n /**\r\n * Applies pagination to a filtered result set.\r\n *\r\n * @param entities - Entities to paginate\r\n * @param pagination - Validated pagination parameters\r\n * @returns Paginated slice of entities\r\n */\r\n static paginate(entities: Entity[], pagination: ValidatedPagination): Entity[] {\r\n return applyPagination(entities, pagination);\r\n }\r\n\r\n /**\r\n * Convenience method to apply both filters and pagination in one call.\r\n *\r\n * @param entities - Entities to process\r\n * @param filters - Filter criteria\r\n * @param offset - Pagination offset\r\n * @param limit - Pagination limit\r\n * @returns Filtered and paginated entities\r\n */\r\n static filterAndPaginate(\r\n entities: Entity[],\r\n filters: SearchFilters,\r\n offset: number = 0,\r\n limit?: number\r\n ): Entity[] {\r\n const filtered = this.applyFilters(entities, filters);\r\n const pagination = this.validatePagination(offset, limit);\r\n return this.paginate(filtered, pagination);\r\n }\r\n\r\n /**\r\n * Applies tag filter only. Useful when other filters are handled separately.\r\n *\r\n * @param entities - Entities to filter\r\n * @param tags - Tags to filter by\r\n * @returns Filtered entities\r\n */\r\n static filterByTags(entities: Entity[], tags?: string[]): Entity[] {\r\n if (!tags || tags.length === 0) {\r\n return entities;\r\n }\r\n\r\n const normalizedTags = normalizeTags(tags);\r\n return entities.filter(entity => {\r\n if (!entity.tags || entity.tags.length === 0) {\r\n return false;\r\n }\r\n return hasMatchingTag(entity.tags, normalizedTags);\r\n });\r\n }\r\n\r\n /**\r\n * Applies importance filter only. Useful when other filters are handled separately.\r\n *\r\n * @param entities - Entities to filter\r\n * @param minImportance - Minimum importance\r\n * @param maxImportance - Maximum importance\r\n * @returns Filtered entities\r\n */\r\n static filterByImportance(\r\n entities: Entity[],\r\n minImportance?: number,\r\n maxImportance?: number\r\n ): Entity[] {\r\n if (minImportance === undefined && maxImportance === undefined) {\r\n return entities;\r\n }\r\n\r\n return entities.filter(entity =>\r\n isWithinImportanceRange(entity.importance, minImportance, maxImportance)\r\n );\r\n }\r\n}\r\n\r\n// Re-export types for convenience\r\nexport type { ValidatedPagination };\r\n","/**\r\n * Basic Search\r\n *\r\n * Simple text-based search with tag, importance, and date filters with result caching.\r\n *\r\n * @module search/BasicSearch\r\n */\r\n\r\nimport type { KnowledgeGraph } from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport { isWithinDateRange, SEARCH_LIMITS, searchCaches } from '../utils/index.js';\r\nimport { SearchFilterChain, type SearchFilters } from './SearchFilterChain.js';\r\n\r\n/**\r\n * Performs basic text search with optional filters and caching.\r\n */\r\nexport class BasicSearch {\r\n constructor(\r\n private storage: GraphStorage,\r\n private enableCache: boolean = true\r\n ) {}\r\n\r\n /**\r\n * Search nodes by text query with optional filters and pagination.\r\n *\r\n * Searches across entity names, types, and observations.\r\n *\r\n * @param query - Text to search for (case-insensitive)\r\n * @param tags - Optional tags to filter by\r\n * @param minImportance - Optional minimum importance (0-10)\r\n * @param maxImportance - Optional maximum importance (0-10)\r\n * @param offset - Number of results to skip (default: 0)\r\n * @param limit - Maximum number of results (default: 50, max: 200)\r\n * @returns Filtered knowledge graph with pagination applied\r\n */\r\n async searchNodes(\r\n query: string,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n offset: number = 0,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n ): Promise<KnowledgeGraph> {\r\n // Check cache first\r\n if (this.enableCache) {\r\n const cacheKey = { query, tags, minImportance, maxImportance, offset, limit };\r\n const cached = searchCaches.basic.get(cacheKey);\r\n if (cached) {\r\n return cached;\r\n }\r\n }\r\n\r\n const graph = await this.storage.loadGraph();\r\n const queryLower = query.toLowerCase();\r\n\r\n // First filter by text match (search-specific)\r\n // OPTIMIZED: Uses pre-computed lowercase cache to avoid repeated toLowerCase() calls\r\n const textMatched = graph.entities.filter(e => {\r\n const lowercased = this.storage.getLowercased(e.name);\r\n if (lowercased) {\r\n return (\r\n lowercased.name.includes(queryLower) ||\r\n lowercased.entityType.includes(queryLower) ||\r\n lowercased.observations.some(o => o.includes(queryLower))\r\n );\r\n }\r\n // Fallback for entities not in cache (shouldn't happen in normal use)\r\n return (\r\n e.name.toLowerCase().includes(queryLower) ||\r\n e.entityType.toLowerCase().includes(queryLower) ||\r\n e.observations.some(o => o.toLowerCase().includes(queryLower))\r\n );\r\n });\r\n\r\n // Apply tag and importance filters using SearchFilterChain\r\n const filters: SearchFilters = { tags, minImportance, maxImportance };\r\n const filteredEntities = SearchFilterChain.applyFilters(textMatched, filters);\r\n\r\n // Apply pagination using SearchFilterChain\r\n const pagination = SearchFilterChain.validatePagination(offset, limit);\r\n const paginatedEntities = SearchFilterChain.paginate(filteredEntities, pagination);\r\n\r\n const filteredEntityNames = new Set(paginatedEntities.map(e => e.name));\r\n const filteredRelations = graph.relations.filter(\r\n r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)\r\n );\r\n\r\n const result = { entities: paginatedEntities, relations: filteredRelations };\r\n\r\n // Cache the result\r\n if (this.enableCache) {\r\n const cacheKey = { query, tags, minImportance, maxImportance, offset, limit };\r\n searchCaches.basic.set(cacheKey, result);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Open specific nodes by name.\r\n *\r\n * @param names - Array of entity names to retrieve\r\n * @returns Knowledge graph with specified entities and their relations\r\n */\r\n async openNodes(names: string[]): Promise<KnowledgeGraph> {\r\n const graph = await this.storage.loadGraph();\r\n\r\n const filteredEntities = graph.entities.filter(e => names.includes(e.name));\r\n const filteredEntityNames = new Set(filteredEntities.map(e => e.name));\r\n const filteredRelations = graph.relations.filter(\r\n r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)\r\n );\r\n\r\n return { entities: filteredEntities, relations: filteredRelations };\r\n }\r\n\r\n /**\r\n * Search by date range with optional filters and pagination.\r\n *\r\n * @param startDate - Optional start date (ISO 8601)\r\n * @param endDate - Optional end date (ISO 8601)\r\n * @param entityType - Optional entity type filter\r\n * @param tags - Optional tags filter\r\n * @param offset - Number of results to skip (default: 0)\r\n * @param limit - Maximum number of results (default: 50, max: 200)\r\n * @returns Filtered knowledge graph with pagination applied\r\n */\r\n async searchByDateRange(\r\n startDate?: string,\r\n endDate?: string,\r\n entityType?: string,\r\n tags?: string[],\r\n offset: number = 0,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n ): Promise<KnowledgeGraph> {\r\n // Check cache first\r\n if (this.enableCache) {\r\n const cacheKey = { method: 'dateRange', startDate, endDate, entityType, tags, offset, limit };\r\n const cached = searchCaches.basic.get(cacheKey);\r\n if (cached) {\r\n return cached;\r\n }\r\n }\r\n\r\n const graph = await this.storage.loadGraph();\r\n\r\n // First filter by date range (search-specific - uses createdAt OR lastModified)\r\n const dateFiltered = graph.entities.filter(e => {\r\n const dateToCheck = e.createdAt || e.lastModified;\r\n if (dateToCheck && !isWithinDateRange(dateToCheck, startDate, endDate)) {\r\n return false;\r\n }\r\n return true;\r\n });\r\n\r\n // Apply entity type and tag filters using SearchFilterChain\r\n const filters: SearchFilters = { tags, entityType };\r\n const filteredEntities = SearchFilterChain.applyFilters(dateFiltered, filters);\r\n\r\n // Apply pagination using SearchFilterChain\r\n const pagination = SearchFilterChain.validatePagination(offset, limit);\r\n const paginatedEntities = SearchFilterChain.paginate(filteredEntities, pagination);\r\n\r\n const filteredEntityNames = new Set(paginatedEntities.map(e => e.name));\r\n const filteredRelations = graph.relations.filter(r => {\r\n const dateToCheck = r.createdAt || r.lastModified;\r\n const inDateRange = !dateToCheck || isWithinDateRange(dateToCheck, startDate, endDate);\r\n const involvesFilteredEntities =\r\n filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to);\r\n\r\n return inDateRange && involvesFilteredEntities;\r\n });\r\n\r\n const result = { entities: paginatedEntities, relations: filteredRelations };\r\n\r\n // Cache the result\r\n if (this.enableCache) {\r\n const cacheKey = { method: 'dateRange', startDate, endDate, entityType, tags, offset, limit };\r\n searchCaches.basic.set(cacheKey, result);\r\n }\r\n\r\n return result;\r\n }\r\n}\r\n","/**\r\n * TF-IDF Index Manager\r\n *\r\n * Manages pre-calculated TF-IDF indexes for fast ranked search.\r\n * Handles index building, incremental updates, and persistence.\r\n *\r\n * @module search/TFIDFIndexManager\r\n */\r\n\r\nimport * as fs from 'fs/promises';\r\nimport * as path from 'path';\r\nimport type { TFIDFIndex, DocumentVector, KnowledgeGraph, ReadonlyKnowledgeGraph } from '../types/index.js';\r\nimport { calculateIDFFromTokenSets, tokenize } from '../utils/index.js';\r\n\r\nconst INDEX_VERSION = '1.0';\r\nconst INDEX_FILENAME = 'tfidf-index.json';\r\n\r\n/**\r\n * Serializable version of TFIDFIndex for JSON storage.\r\n */\r\ninterface SerializedTFIDFIndex {\r\n version: string;\r\n lastUpdated: string;\r\n documents: Array<[string, DocumentVector]>;\r\n idf: Array<[string, number]>;\r\n}\r\n\r\n/**\r\n * Manages TF-IDF index lifecycle: building, updating, and persistence.\r\n */\r\nexport class TFIDFIndexManager {\r\n private indexPath: string;\r\n private index: TFIDFIndex | null = null;\r\n\r\n constructor(storageDir: string) {\r\n this.indexPath = path.join(storageDir, '.indexes', INDEX_FILENAME);\r\n }\r\n\r\n /**\r\n * Build a complete TF-IDF index from a knowledge graph.\r\n *\r\n * @param graph - Knowledge graph to index\r\n * @returns Newly built TF-IDF index\r\n */\r\n async buildIndex(graph: ReadonlyKnowledgeGraph): Promise<TFIDFIndex> {\r\n const documents = new Map<string, DocumentVector>();\r\n const allTokenSets: Set<string>[] = [];\r\n\r\n // Build document vectors - tokenize once per document\r\n for (const entity of graph.entities) {\r\n const documentText = [\r\n entity.name,\r\n entity.entityType,\r\n ...entity.observations,\r\n ].join(' ');\r\n\r\n const tokens = tokenize(documentText);\r\n const tokenSet = new Set(tokens);\r\n allTokenSets.push(tokenSet);\r\n\r\n // Calculate term frequencies\r\n const termFreq: Record<string, number> = {};\r\n for (const term of tokens) {\r\n termFreq[term] = (termFreq[term] || 0) + 1;\r\n }\r\n\r\n documents.set(entity.name, {\r\n entityName: entity.name,\r\n terms: termFreq,\r\n documentText,\r\n });\r\n }\r\n\r\n // Calculate IDF for all terms using pre-tokenized sets (O(1) lookup per document)\r\n const idf = new Map<string, number>();\r\n const allTerms = new Set(allTokenSets.flatMap(s => Array.from(s)));\r\n\r\n for (const term of allTerms) {\r\n const idfScore = calculateIDFFromTokenSets(term, allTokenSets);\r\n idf.set(term, idfScore);\r\n }\r\n\r\n this.index = {\r\n version: INDEX_VERSION,\r\n lastUpdated: new Date().toISOString(),\r\n documents,\r\n idf,\r\n };\r\n\r\n return this.index;\r\n }\r\n\r\n /**\r\n * Update the index incrementally when entities change.\r\n *\r\n * More efficient than rebuilding the entire index.\r\n *\r\n * @param graph - Updated knowledge graph\r\n * @param changedEntityNames - Names of entities that changed\r\n */\r\n async updateIndex(graph: ReadonlyKnowledgeGraph, changedEntityNames: Set<string>): Promise<TFIDFIndex> {\r\n if (!this.index) {\r\n // No existing index, build from scratch\r\n return this.buildIndex(graph);\r\n }\r\n\r\n // Rebuild document vectors for changed entities\r\n const allTokenSets: Set<string>[] = [];\r\n const updatedDocuments = new Map(this.index.documents);\r\n\r\n // Remove deleted entities\r\n for (const entityName of changedEntityNames) {\r\n const entity = graph.entities.find(e => e.name === entityName);\r\n if (!entity) {\r\n updatedDocuments.delete(entityName);\r\n }\r\n }\r\n\r\n // Update/add changed entities - tokenize once per document\r\n for (const entity of graph.entities) {\r\n const documentText = [\r\n entity.name,\r\n entity.entityType,\r\n ...entity.observations,\r\n ].join(' ');\r\n\r\n const tokens = tokenize(documentText);\r\n const tokenSet = new Set(tokens);\r\n allTokenSets.push(tokenSet);\r\n\r\n if (changedEntityNames.has(entity.name)) {\r\n // Calculate term frequencies for changed entity\r\n const termFreq: Record<string, number> = {};\r\n for (const term of tokens) {\r\n termFreq[term] = (termFreq[term] || 0) + 1;\r\n }\r\n\r\n updatedDocuments.set(entity.name, {\r\n entityName: entity.name,\r\n terms: termFreq,\r\n documentText,\r\n });\r\n }\r\n }\r\n\r\n // Recalculate IDF using pre-tokenized sets (O(1) lookup per document)\r\n const idf = new Map<string, number>();\r\n const allTerms = new Set(allTokenSets.flatMap(s => Array.from(s)));\r\n\r\n for (const term of allTerms) {\r\n const idfScore = calculateIDFFromTokenSets(term, allTokenSets);\r\n idf.set(term, idfScore);\r\n }\r\n\r\n this.index = {\r\n version: INDEX_VERSION,\r\n lastUpdated: new Date().toISOString(),\r\n documents: updatedDocuments,\r\n idf,\r\n };\r\n\r\n return this.index;\r\n }\r\n\r\n /**\r\n * Load index from disk.\r\n *\r\n * @returns Loaded index or null if not found\r\n */\r\n async loadIndex(): Promise<TFIDFIndex | null> {\r\n try {\r\n const data = await fs.readFile(this.indexPath, 'utf-8');\r\n const serialized: SerializedTFIDFIndex = JSON.parse(data);\r\n\r\n this.index = {\r\n version: serialized.version,\r\n lastUpdated: serialized.lastUpdated,\r\n documents: new Map(serialized.documents),\r\n idf: new Map(serialized.idf),\r\n };\r\n\r\n return this.index;\r\n } catch (error) {\r\n // Index doesn't exist or is invalid\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Save index to disk.\r\n *\r\n * @param index - Index to save (uses cached index if not provided)\r\n */\r\n async saveIndex(index?: TFIDFIndex): Promise<void> {\r\n const indexToSave = index || this.index;\r\n if (!indexToSave) {\r\n throw new Error('No index to save');\r\n }\r\n\r\n // Ensure index directory exists\r\n const indexDir = path.dirname(this.indexPath);\r\n await fs.mkdir(indexDir, { recursive: true });\r\n\r\n // Serialize Map objects to arrays for JSON\r\n const serialized: SerializedTFIDFIndex = {\r\n version: indexToSave.version,\r\n lastUpdated: indexToSave.lastUpdated,\r\n documents: Array.from(indexToSave.documents.entries()),\r\n idf: Array.from(indexToSave.idf.entries()),\r\n };\r\n\r\n await fs.writeFile(this.indexPath, JSON.stringify(serialized, null, 2), 'utf-8');\r\n }\r\n\r\n /**\r\n * Get the current cached index.\r\n *\r\n * @returns Cached index or null if not loaded\r\n */\r\n getIndex(): TFIDFIndex | null {\r\n return this.index;\r\n }\r\n\r\n /**\r\n * Clear the cached index and delete from disk.\r\n */\r\n async clearIndex(): Promise<void> {\r\n this.index = null;\r\n try {\r\n await fs.unlink(this.indexPath);\r\n } catch {\r\n // Index file doesn't exist, nothing to delete\r\n }\r\n }\r\n\r\n /**\r\n * Check if the index needs rebuilding based on graph state.\r\n *\r\n * @param graph - Current knowledge graph\r\n * @returns True if index should be rebuilt\r\n */\r\n needsRebuild(graph: KnowledgeGraph): boolean {\r\n if (!this.index) {\r\n return true;\r\n }\r\n\r\n // Check if entity count matches\r\n if (this.index.documents.size !== graph.entities.length) {\r\n return true;\r\n }\r\n\r\n // Check if all entities are in index\r\n for (const entity of graph.entities) {\r\n if (!this.index.documents.has(entity.name)) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n // ==================== Phase 10 Sprint 3: Incremental Index Updates ====================\r\n\r\n /**\r\n * Phase 10 Sprint 3: Add a single document to the index incrementally.\r\n *\r\n * More efficient than rebuilding the entire index for single entity additions.\r\n * Updates TF for the new document and recalculates IDF for affected terms.\r\n *\r\n * @param entity - The entity to add\r\n *\r\n * @example\r\n * ```typescript\r\n * const indexManager = new TFIDFIndexManager('/data');\r\n * await indexManager.loadIndex();\r\n *\r\n * // Add new entity\r\n * indexManager.addDocument({\r\n * name: 'NewEntity',\r\n * entityType: 'person',\r\n * observations: ['Software engineer']\r\n * });\r\n * ```\r\n */\r\n addDocument(entity: { name: string; entityType: string; observations: string[] }): void {\r\n if (!this.index) {\r\n // Can't add to non-existent index\r\n return;\r\n }\r\n\r\n // Build document text and tokens\r\n const documentText = [entity.name, entity.entityType, ...entity.observations].join(' ');\r\n const tokens = tokenize(documentText);\r\n\r\n // Calculate term frequencies\r\n const termFreq: Record<string, number> = {};\r\n for (const term of tokens) {\r\n termFreq[term] = (termFreq[term] || 0) + 1;\r\n }\r\n\r\n // Add to documents map\r\n this.index.documents.set(entity.name, {\r\n entityName: entity.name,\r\n terms: termFreq,\r\n documentText,\r\n });\r\n\r\n // Update IDF for ALL terms because N changed (total document count)\r\n // IDF = log(N/df), and N has increased\r\n this.recalculateAllIDF();\r\n\r\n // Update timestamp\r\n this.index.lastUpdated = new Date().toISOString();\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 3: Remove a single document from the index incrementally.\r\n *\r\n * More efficient than rebuilding the entire index for single entity deletions.\r\n * Recalculates IDF for terms that were in the removed document.\r\n *\r\n * @param entityName - Name of the entity to remove\r\n *\r\n * @example\r\n * ```typescript\r\n * indexManager.removeDocument('DeletedEntity');\r\n * ```\r\n */\r\n removeDocument(entityName: string): void {\r\n if (!this.index) {\r\n return;\r\n }\r\n\r\n const document = this.index.documents.get(entityName);\r\n if (!document) {\r\n return;\r\n }\r\n\r\n // Remove from documents map\r\n this.index.documents.delete(entityName);\r\n\r\n // Update IDF for ALL terms because N changed (total document count)\r\n // IDF = log(N/df), and N has decreased\r\n this.recalculateAllIDF();\r\n\r\n // Update timestamp\r\n this.index.lastUpdated = new Date().toISOString();\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 3: Update a single document in the index incrementally.\r\n *\r\n * More efficient than rebuilding the entire index for single entity updates.\r\n * Handles both term changes and observation updates.\r\n *\r\n * @param entity - The updated entity\r\n *\r\n * @example\r\n * ```typescript\r\n * indexManager.updateDocument({\r\n * name: 'ExistingEntity',\r\n * entityType: 'person',\r\n * observations: ['Updated observations']\r\n * });\r\n * ```\r\n */\r\n updateDocument(entity: { name: string; entityType: string; observations: string[] }): void {\r\n if (!this.index) {\r\n return;\r\n }\r\n\r\n const oldDocument = this.index.documents.get(entity.name);\r\n const oldTerms = oldDocument ? new Set(Object.keys(oldDocument.terms)) : new Set<string>();\r\n\r\n // Build new document\r\n const documentText = [entity.name, entity.entityType, ...entity.observations].join(' ');\r\n const tokens = tokenize(documentText);\r\n const newTerms = new Set(tokens);\r\n\r\n // Calculate term frequencies\r\n const termFreq: Record<string, number> = {};\r\n for (const term of tokens) {\r\n termFreq[term] = (termFreq[term] || 0) + 1;\r\n }\r\n\r\n // Update documents map\r\n this.index.documents.set(entity.name, {\r\n entityName: entity.name,\r\n terms: termFreq,\r\n documentText,\r\n });\r\n\r\n // Find terms that changed (added or removed)\r\n const changedTerms = new Set<string>();\r\n for (const term of oldTerms) {\r\n if (!newTerms.has(term)) {\r\n changedTerms.add(term);\r\n }\r\n }\r\n for (const term of newTerms) {\r\n if (!oldTerms.has(term)) {\r\n changedTerms.add(term);\r\n }\r\n }\r\n\r\n // Recalculate IDF for changed terms\r\n if (changedTerms.size > 0) {\r\n this.recalculateIDFForTerms(changedTerms);\r\n }\r\n\r\n // Update timestamp\r\n this.index.lastUpdated = new Date().toISOString();\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 3: Recalculate IDF scores for a set of terms.\r\n *\r\n * @param terms - Set of terms to recalculate IDF for\r\n * @private\r\n */\r\n private recalculateIDFForTerms(terms: Set<string>): void {\r\n if (!this.index) {\r\n return;\r\n }\r\n\r\n const totalDocs = this.index.documents.size;\r\n if (totalDocs === 0) {\r\n // No documents, clear all IDF for these terms\r\n for (const term of terms) {\r\n this.index.idf.delete(term);\r\n }\r\n return;\r\n }\r\n\r\n // Count documents containing each term\r\n for (const term of terms) {\r\n let docCount = 0;\r\n for (const doc of this.index.documents.values()) {\r\n if (term in doc.terms) {\r\n docCount++;\r\n }\r\n }\r\n\r\n if (docCount > 0) {\r\n // IDF = log(N / df) where N = total docs, df = doc frequency\r\n const idfScore = Math.log(totalDocs / docCount);\r\n this.index.idf.set(term, idfScore);\r\n } else {\r\n // Term no longer exists in any document\r\n this.index.idf.delete(term);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 3: Recalculate IDF scores for ALL terms in the index.\r\n *\r\n * Called when the total document count changes (add/remove document).\r\n * @private\r\n */\r\n private recalculateAllIDF(): void {\r\n if (!this.index) {\r\n return;\r\n }\r\n\r\n const totalDocs = this.index.documents.size;\r\n\r\n if (totalDocs === 0) {\r\n // No documents, clear all IDF\r\n this.index.idf.clear();\r\n return;\r\n }\r\n\r\n // Build term -> document count map\r\n const termDocCounts = new Map<string, number>();\r\n for (const doc of this.index.documents.values()) {\r\n for (const term of Object.keys(doc.terms)) {\r\n termDocCounts.set(term, (termDocCounts.get(term) ?? 0) + 1);\r\n }\r\n }\r\n\r\n // Clear old IDF and recalculate\r\n this.index.idf.clear();\r\n for (const [term, docCount] of termDocCounts) {\r\n // IDF = log(N / df) where N = total docs, df = doc frequency\r\n const idfScore = Math.log(totalDocs / docCount);\r\n this.index.idf.set(term, idfScore);\r\n }\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 3: Check if the index is loaded/initialized.\r\n *\r\n * @returns True if index is available\r\n */\r\n isInitialized(): boolean {\r\n return this.index !== null;\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 3: Get the number of documents in the index.\r\n *\r\n * @returns Document count or 0 if not initialized\r\n */\r\n getDocumentCount(): number {\r\n return this.index?.documents.size ?? 0;\r\n }\r\n}\r\n","/**\r\n * Ranked Search\r\n *\r\n * TF-IDF relevance-based search with scoring and pre-calculated indexes.\r\n *\r\n * @module search/RankedSearch\r\n */\r\n\r\nimport type { Entity, SearchResult, TFIDFIndex, TokenizedEntity } from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport { calculateTF, calculateIDFFromTokenSets, tokenize } from '../utils/index.js';\r\nimport { SEARCH_LIMITS } from '../utils/constants.js';\r\nimport { TFIDFIndexManager } from './TFIDFIndexManager.js';\r\nimport { SearchFilterChain, type SearchFilters } from './SearchFilterChain.js';\r\n\r\n/**\r\n * Performs TF-IDF ranked search with optional pre-calculated indexes.\r\n */\r\nexport class RankedSearch {\r\n private indexManager: TFIDFIndexManager | null = null;\r\n\r\n /**\r\n * Phase 4 Sprint 2: Fallback token cache for entities.\r\n * Maps entity name -> pre-tokenized entity data.\r\n * Invalidated when graph changes (detected by entity count mismatch).\r\n */\r\n private fallbackTokenCache: Map<string, TokenizedEntity> = new Map();\r\n private cachedEntityCount: number = 0;\r\n\r\n constructor(\r\n private storage: GraphStorage,\r\n storageDir?: string\r\n ) {\r\n // Initialize index manager if storage directory is provided\r\n if (storageDir) {\r\n this.indexManager = new TFIDFIndexManager(storageDir);\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 2: Clear the fallback token cache.\r\n * Called when graph changes are detected or explicitly by external code.\r\n */\r\n clearTokenCache(): void {\r\n this.fallbackTokenCache.clear();\r\n this.cachedEntityCount = 0;\r\n }\r\n\r\n /**\r\n * Initialize and build the TF-IDF index for fast searches.\r\n *\r\n * Should be called after graph changes to keep index up-to-date.\r\n */\r\n async buildIndex(): Promise<void> {\r\n if (!this.indexManager) {\r\n throw new Error('Index manager not initialized. Provide storageDir to constructor.');\r\n }\r\n\r\n const graph = await this.storage.loadGraph();\r\n await this.indexManager.buildIndex(graph);\r\n await this.indexManager.saveIndex();\r\n }\r\n\r\n /**\r\n * Update the index incrementally after entity changes.\r\n *\r\n * @param changedEntityNames - Names of entities that were created, updated, or deleted\r\n */\r\n async updateIndex(changedEntityNames: Set<string>): Promise<void> {\r\n if (!this.indexManager) {\r\n return; // No index manager, skip\r\n }\r\n\r\n const graph = await this.storage.loadGraph();\r\n await this.indexManager.updateIndex(graph, changedEntityNames);\r\n await this.indexManager.saveIndex();\r\n }\r\n\r\n /**\r\n * Load the TF-IDF index from disk if available.\r\n */\r\n private async ensureIndexLoaded(): Promise<TFIDFIndex | null> {\r\n if (!this.indexManager) {\r\n return null;\r\n }\r\n\r\n // Return cached index if already loaded\r\n const cached = this.indexManager.getIndex();\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Try to load from disk\r\n return await this.indexManager.loadIndex();\r\n }\r\n\r\n /**\r\n * Search with TF-IDF relevance ranking.\r\n *\r\n * Uses pre-calculated index if available, falls back to on-the-fly calculation.\r\n *\r\n * @param query - Search query\r\n * @param tags - Optional tags filter\r\n * @param minImportance - Optional minimum importance\r\n * @param maxImportance - Optional maximum importance\r\n * @param limit - Maximum results to return (default 50, max 200)\r\n * @returns Array of search results sorted by relevance\r\n */\r\n async searchNodesRanked(\r\n query: string,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n ): Promise<SearchResult[]> {\r\n // Enforce maximum search limit\r\n const effectiveLimit = Math.min(limit, SEARCH_LIMITS.MAX);\r\n const graph = await this.storage.loadGraph();\r\n\r\n // Apply tag and importance filters using SearchFilterChain\r\n const filters: SearchFilters = { tags, minImportance, maxImportance };\r\n const filteredEntities = SearchFilterChain.applyFilters(graph.entities, filters);\r\n\r\n // Try to use pre-calculated index\r\n const index = await this.ensureIndexLoaded();\r\n const queryTerms = tokenize(query);\r\n\r\n if (index) {\r\n // Use pre-calculated index for fast search\r\n return this.searchWithIndex(filteredEntities, queryTerms, index, effectiveLimit);\r\n } else {\r\n // Fall back to on-the-fly calculation\r\n return this.searchWithoutIndex(filteredEntities, queryTerms, effectiveLimit);\r\n }\r\n }\r\n\r\n /**\r\n * Search using pre-calculated TF-IDF index (fast path).\r\n */\r\n private searchWithIndex(\r\n entities: Entity[],\r\n queryTerms: string[],\r\n index: TFIDFIndex,\r\n limit: number\r\n ): SearchResult[] {\r\n const results: SearchResult[] = [];\r\n\r\n for (const entity of entities) {\r\n const docVector = index.documents.get(entity.name);\r\n if (!docVector) {\r\n continue; // Entity not in index\r\n }\r\n\r\n // Calculate total terms in document (sum of all term frequencies)\r\n const totalTerms = Object.values(docVector.terms).reduce((sum, count) => sum + count, 0);\r\n if (totalTerms === 0) continue;\r\n\r\n // Calculate score using pre-calculated term frequencies and IDF\r\n let totalScore = 0;\r\n const matchedFields: SearchResult['matchedFields'] = {};\r\n\r\n for (const term of queryTerms) {\r\n const termCount = docVector.terms[term] || 0;\r\n const idf = index.idf.get(term) || 0;\r\n\r\n // Calculate TF-IDF: (termCount / totalTerms) * IDF\r\n const tf = termCount / totalTerms;\r\n const tfidf = tf * idf;\r\n totalScore += tfidf;\r\n\r\n // Track which fields matched\r\n if (termCount > 0) {\r\n if (entity.name.toLowerCase().includes(term)) {\r\n matchedFields.name = true;\r\n }\r\n if (entity.entityType.toLowerCase().includes(term)) {\r\n matchedFields.entityType = true;\r\n }\r\n const matchedObs = entity.observations.filter(o =>\r\n o.toLowerCase().includes(term)\r\n );\r\n if (matchedObs.length > 0) {\r\n matchedFields.observations = matchedObs;\r\n }\r\n }\r\n }\r\n\r\n // Only include entities with non-zero scores\r\n if (totalScore > 0) {\r\n results.push({\r\n entity,\r\n score: totalScore,\r\n matchedFields,\r\n });\r\n }\r\n }\r\n\r\n // Sort by score descending and apply limit\r\n return results\r\n .sort((a, b) => b.score - a.score)\r\n .slice(0, limit);\r\n }\r\n\r\n /**\r\n * Search without index (on-the-fly calculation, slow path).\r\n *\r\n * OPTIMIZED: Phase 4 Sprint 2 - Uses fallback token cache to avoid\r\n * repeated tokenization of entities. Pre-tokenizes all documents once\r\n * and caches for subsequent searches.\r\n */\r\n private searchWithoutIndex(\r\n entities: Entity[],\r\n queryTerms: string[],\r\n limit: number\r\n ): SearchResult[] {\r\n const results: SearchResult[] = [];\r\n\r\n // Phase 4 Sprint 2: Check if cache needs invalidation\r\n if (entities.length !== this.cachedEntityCount) {\r\n this.clearTokenCache();\r\n this.cachedEntityCount = entities.length;\r\n }\r\n\r\n // Phase 4 Sprint 2: Get or compute tokenized data for each entity\r\n const documentData: TokenizedEntity[] = entities.map(e => {\r\n // Check cache first\r\n const cached = this.fallbackTokenCache.get(e.name);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Compute and cache tokenized data\r\n const text = [e.name, e.entityType, ...e.observations].join(' ');\r\n const tokens = tokenize(text);\r\n const tokenized: TokenizedEntity = {\r\n entity: e,\r\n text,\r\n tokens,\r\n tokenSet: new Set(tokens),\r\n };\r\n this.fallbackTokenCache.set(e.name, tokenized);\r\n return tokenized;\r\n });\r\n\r\n // Pre-compute token sets for IDF calculation (O(1) lookup per document)\r\n const tokenSets = documentData.map(d => d.tokenSet);\r\n\r\n for (const docData of documentData) {\r\n const { entity, text } = docData;\r\n\r\n // Calculate score for each query term\r\n let totalScore = 0;\r\n const matchedFields: SearchResult['matchedFields'] = {};\r\n\r\n for (const term of queryTerms) {\r\n // Calculate TF using pre-tokenized tokens\r\n const tf = calculateTF(term, text);\r\n\r\n // Calculate IDF using pre-computed token sets (O(1) per document)\r\n const idf = calculateIDFFromTokenSets(term, tokenSets);\r\n\r\n // TF-IDF score\r\n const score = tf * idf;\r\n totalScore += score;\r\n\r\n // Track which fields matched\r\n if (entity.name.toLowerCase().includes(term)) {\r\n matchedFields.name = true;\r\n }\r\n if (entity.entityType.toLowerCase().includes(term)) {\r\n matchedFields.entityType = true;\r\n }\r\n const matchedObs = entity.observations.filter(o =>\r\n o.toLowerCase().includes(term)\r\n );\r\n if (matchedObs.length > 0) {\r\n matchedFields.observations = matchedObs;\r\n }\r\n }\r\n\r\n // Only include entities with non-zero scores\r\n if (totalScore > 0) {\r\n results.push({\r\n entity,\r\n score: totalScore,\r\n matchedFields,\r\n });\r\n }\r\n }\r\n\r\n // Sort by score descending and apply limit\r\n return results\r\n .sort((a, b) => b.score - a.score)\r\n .slice(0, limit);\r\n }\r\n}\r\n","/**\r\n * Boolean Search\r\n *\r\n * Advanced search with boolean operators (AND, OR, NOT) and field-specific queries.\r\n *\r\n * @module search/BooleanSearch\r\n */\r\n\r\nimport type { BooleanQueryNode, Entity, KnowledgeGraph } from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport { SEARCH_LIMITS, QUERY_LIMITS } from '../utils/constants.js';\r\nimport { ValidationError } from '../utils/errors.js';\r\nimport { SearchFilterChain, type SearchFilters } from './SearchFilterChain.js';\r\n\r\n/**\r\n * Phase 4 Sprint 4: Cache entry for Boolean search AST and results.\r\n */\r\ninterface BooleanCacheEntry {\r\n /** Parsed AST */\r\n ast: BooleanQueryNode;\r\n /** Cached entity names that matched */\r\n entityNames: string[];\r\n /** Entity count when cache was created (for invalidation) */\r\n entityCount: number;\r\n /** Timestamp when cached */\r\n timestamp: number;\r\n}\r\n\r\n/**\r\n * Phase 4 Sprint 4: Maximum AST cache size.\r\n */\r\nconst AST_CACHE_MAX_SIZE = 50;\r\n\r\n/**\r\n * Phase 4 Sprint 4: Result cache max size.\r\n */\r\nconst RESULT_CACHE_MAX_SIZE = 100;\r\n\r\n/**\r\n * Phase 4 Sprint 4: Cache TTL in milliseconds (5 minutes).\r\n */\r\nconst BOOLEAN_CACHE_TTL_MS = 5 * 60 * 1000;\r\n\r\n/**\r\n * Performs boolean search with query parsing and AST evaluation.\r\n */\r\nexport class BooleanSearch {\r\n /**\r\n * Phase 4 Sprint 4: AST cache to avoid re-parsing queries.\r\n * Maps query string -> parsed AST.\r\n */\r\n private astCache: Map<string, BooleanQueryNode> = new Map();\r\n\r\n /**\r\n * Phase 4 Sprint 4: Result cache for boolean search.\r\n * Maps cache key -> cached results.\r\n */\r\n private resultCache: Map<string, BooleanCacheEntry> = new Map();\r\n\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Phase 4 Sprint 4: Generate cache key for boolean search.\r\n */\r\n private generateCacheKey(\r\n query: string,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n offset?: number,\r\n limit?: number\r\n ): string {\r\n return JSON.stringify({\r\n q: query,\r\n tags: tags?.sort().join(',') ?? '',\r\n min: minImportance,\r\n max: maxImportance,\r\n off: offset,\r\n lim: limit,\r\n });\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 4: Clear all caches.\r\n */\r\n clearCache(): void {\r\n this.astCache.clear();\r\n this.resultCache.clear();\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 4: Cleanup old cache entries.\r\n */\r\n private cleanupResultCache(): void {\r\n const now = Date.now();\r\n const entries = Array.from(this.resultCache.entries());\r\n\r\n // Remove expired entries\r\n for (const [key, entry] of entries) {\r\n if (now - entry.timestamp > BOOLEAN_CACHE_TTL_MS) {\r\n this.resultCache.delete(key);\r\n }\r\n }\r\n\r\n // If still over limit, remove oldest entries\r\n if (this.resultCache.size > RESULT_CACHE_MAX_SIZE) {\r\n const sortedEntries = entries\r\n .filter(([k]) => this.resultCache.has(k))\r\n .sort((a, b) => a[1].timestamp - b[1].timestamp);\r\n\r\n const toRemove = sortedEntries.slice(0, this.resultCache.size - RESULT_CACHE_MAX_SIZE);\r\n for (const [key] of toRemove) {\r\n this.resultCache.delete(key);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 4: Get or parse AST for a query.\r\n */\r\n private getOrParseAST(query: string): BooleanQueryNode {\r\n // Check AST cache\r\n const cached = this.astCache.get(query);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Parse and cache\r\n const ast = this.parseBooleanQuery(query);\r\n\r\n // Enforce cache size limit\r\n if (this.astCache.size >= AST_CACHE_MAX_SIZE) {\r\n // Remove first entry (oldest)\r\n const firstKey = this.astCache.keys().next().value;\r\n if (firstKey) this.astCache.delete(firstKey);\r\n }\r\n\r\n this.astCache.set(query, ast);\r\n return ast;\r\n }\r\n\r\n /**\r\n * Boolean search with support for AND, OR, NOT operators, field-specific queries, and pagination.\r\n *\r\n * Phase 4 Sprint 4: Implements AST caching and result caching for repeated queries.\r\n *\r\n * Query syntax examples:\r\n * - \"alice AND programming\" - Both terms must match\r\n * - \"type:person OR type:organization\" - Either type matches\r\n * - \"NOT archived\" - Exclude archived items\r\n * - \"name:alice AND (observation:coding OR observation:teaching)\"\r\n *\r\n * @param query - Boolean query string\r\n * @param tags - Optional tags filter\r\n * @param minImportance - Optional minimum importance\r\n * @param maxImportance - Optional maximum importance\r\n * @param offset - Number of results to skip (default: 0)\r\n * @param limit - Maximum number of results (default: 50, max: 200)\r\n * @returns Filtered knowledge graph matching the boolean query with pagination applied\r\n */\r\n async booleanSearch(\r\n query: string,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n offset: number = 0,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n ): Promise<KnowledgeGraph> {\r\n // Validate query length\r\n if (query.length > QUERY_LIMITS.MAX_QUERY_LENGTH) {\r\n throw new ValidationError(\r\n 'Query too long',\r\n [`Query length ${query.length} exceeds maximum of ${QUERY_LIMITS.MAX_QUERY_LENGTH} characters`]\r\n );\r\n }\r\n\r\n const graph = await this.storage.loadGraph();\r\n\r\n // Phase 4 Sprint 4: Check result cache\r\n const cacheKey = this.generateCacheKey(query, tags, minImportance, maxImportance, offset, limit);\r\n const cached = this.resultCache.get(cacheKey);\r\n\r\n if (cached && cached.entityCount === graph.entities.length) {\r\n const now = Date.now();\r\n if (now - cached.timestamp < BOOLEAN_CACHE_TTL_MS) {\r\n // Return cached results\r\n const cachedNameSet = new Set(cached.entityNames);\r\n const cachedEntities = graph.entities.filter(e => cachedNameSet.has(e.name));\r\n const cachedRelations = graph.relations.filter(\r\n r => cachedNameSet.has(r.from) && cachedNameSet.has(r.to)\r\n );\r\n return { entities: cachedEntities as Entity[], relations: cachedRelations };\r\n }\r\n }\r\n\r\n // Phase 4 Sprint 4: Use cached AST or parse new one\r\n let queryAst: BooleanQueryNode;\r\n try {\r\n queryAst = this.getOrParseAST(query);\r\n } catch (error) {\r\n throw new Error(\r\n `Failed to parse boolean query: ${error instanceof Error ? error.message : String(error)}`\r\n );\r\n }\r\n\r\n // Validate query complexity\r\n this.validateQueryComplexity(queryAst);\r\n\r\n // First filter by boolean query evaluation (search-specific)\r\n const booleanMatched = graph.entities.filter(e =>\r\n this.evaluateBooleanQuery(queryAst, e)\r\n );\r\n\r\n // Apply tag and importance filters using SearchFilterChain\r\n const filters: SearchFilters = { tags, minImportance, maxImportance };\r\n const filteredEntities = SearchFilterChain.applyFilters(booleanMatched, filters);\r\n\r\n // Apply pagination using SearchFilterChain\r\n const pagination = SearchFilterChain.validatePagination(offset, limit);\r\n const paginatedEntities = SearchFilterChain.paginate(filteredEntities, pagination);\r\n\r\n // Phase 4 Sprint 4: Cache the results\r\n this.resultCache.set(cacheKey, {\r\n ast: queryAst,\r\n entityNames: paginatedEntities.map(e => e.name),\r\n entityCount: graph.entities.length,\r\n timestamp: Date.now(),\r\n });\r\n\r\n // Cleanup old cache entries periodically\r\n if (this.resultCache.size > RESULT_CACHE_MAX_SIZE / 2) {\r\n this.cleanupResultCache();\r\n }\r\n\r\n const filteredEntityNames = new Set(paginatedEntities.map(e => e.name));\r\n const filteredRelations = graph.relations.filter(\r\n r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)\r\n );\r\n\r\n return { entities: paginatedEntities, relations: filteredRelations };\r\n }\r\n\r\n /**\r\n * Tokenize a boolean query into tokens.\r\n *\r\n * Handles quoted strings, parentheses, and operators.\r\n */\r\n private tokenizeBooleanQuery(query: string): string[] {\r\n const tokens: string[] = [];\r\n let current = '';\r\n let inQuotes = false;\r\n\r\n for (let i = 0; i < query.length; i++) {\r\n const char = query[i];\r\n\r\n if (char === '\"') {\r\n if (inQuotes) {\r\n // End of quoted string\r\n tokens.push(current);\r\n current = '';\r\n inQuotes = false;\r\n } else {\r\n // Start of quoted string\r\n if (current.trim()) {\r\n tokens.push(current.trim());\r\n current = '';\r\n }\r\n inQuotes = true;\r\n }\r\n } else if (!inQuotes && (char === '(' || char === ')')) {\r\n // Parentheses are separate tokens\r\n if (current.trim()) {\r\n tokens.push(current.trim());\r\n current = '';\r\n }\r\n tokens.push(char);\r\n } else if (!inQuotes && /\\s/.test(char)) {\r\n // Whitespace outside quotes\r\n if (current.trim()) {\r\n tokens.push(current.trim());\r\n current = '';\r\n }\r\n } else {\r\n current += char;\r\n }\r\n }\r\n\r\n if (current.trim()) {\r\n tokens.push(current.trim());\r\n }\r\n\r\n return tokens;\r\n }\r\n\r\n /**\r\n * Parse a boolean search query into an AST.\r\n *\r\n * Supports: AND, OR, NOT, parentheses, field-specific queries (field:value)\r\n */\r\n private parseBooleanQuery(query: string): BooleanQueryNode {\r\n const tokens = this.tokenizeBooleanQuery(query);\r\n let position = 0;\r\n\r\n const peek = (): string | undefined => tokens[position];\r\n const consume = (): string | undefined => tokens[position++];\r\n\r\n // Parse OR expressions (lowest precedence)\r\n const parseOr = (): BooleanQueryNode => {\r\n let left = parseAnd();\r\n\r\n while (peek()?.toUpperCase() === 'OR') {\r\n consume(); // consume 'OR'\r\n const right = parseAnd();\r\n left = { type: 'OR', children: [left, right] };\r\n }\r\n\r\n return left;\r\n };\r\n\r\n // Parse AND expressions\r\n const parseAnd = (): BooleanQueryNode => {\r\n let left = parseNot();\r\n\r\n while (peek() && peek()?.toUpperCase() !== 'OR' && peek() !== ')') {\r\n // Implicit AND if next token is not OR or )\r\n if (peek()?.toUpperCase() === 'AND') {\r\n consume(); // consume 'AND'\r\n }\r\n const right = parseNot();\r\n left = { type: 'AND', children: [left, right] };\r\n }\r\n\r\n return left;\r\n };\r\n\r\n // Parse NOT expressions\r\n const parseNot = (): BooleanQueryNode => {\r\n if (peek()?.toUpperCase() === 'NOT') {\r\n consume(); // consume 'NOT'\r\n const child = parseNot();\r\n return { type: 'NOT', child };\r\n }\r\n return parsePrimary();\r\n };\r\n\r\n // Parse primary expressions (terms, field queries, parentheses)\r\n const parsePrimary = (): BooleanQueryNode => {\r\n const token = peek();\r\n\r\n if (!token) {\r\n throw new Error('Unexpected end of query');\r\n }\r\n\r\n // Parentheses\r\n if (token === '(') {\r\n consume(); // consume '('\r\n const node = parseOr();\r\n if (consume() !== ')') {\r\n throw new Error('Expected closing parenthesis');\r\n }\r\n return node;\r\n }\r\n\r\n // Field-specific query (field:value)\r\n if (token.includes(':')) {\r\n consume();\r\n const [field, ...valueParts] = token.split(':');\r\n const value = valueParts.join(':'); // Handle colons in value\r\n return { type: 'TERM', field: field.toLowerCase(), value: value.toLowerCase() };\r\n }\r\n\r\n // Regular term\r\n consume();\r\n return { type: 'TERM', value: token.toLowerCase() };\r\n };\r\n\r\n const result = parseOr();\r\n\r\n // Check for unconsumed tokens\r\n if (position < tokens.length) {\r\n throw new Error(`Unexpected token: ${tokens[position]}`);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Evaluate a boolean query AST against an entity.\r\n */\r\n private evaluateBooleanQuery(node: BooleanQueryNode, entity: Entity): boolean {\r\n switch (node.type) {\r\n case 'AND':\r\n return node.children.every(child => this.evaluateBooleanQuery(child, entity));\r\n\r\n case 'OR':\r\n return node.children.some(child => this.evaluateBooleanQuery(child, entity));\r\n\r\n case 'NOT':\r\n return !this.evaluateBooleanQuery(node.child, entity);\r\n\r\n case 'TERM': {\r\n const value = node.value;\r\n // OPTIMIZED: Use pre-computed lowercase cache\r\n const lowercased = this.storage.getLowercased(entity.name);\r\n\r\n // Field-specific search\r\n if (node.field) {\r\n switch (node.field) {\r\n case 'name':\r\n return lowercased ? lowercased.name.includes(value) : entity.name.toLowerCase().includes(value);\r\n case 'type':\r\n case 'entitytype':\r\n return lowercased ? lowercased.entityType.includes(value) : entity.entityType.toLowerCase().includes(value);\r\n case 'observation':\r\n case 'observations':\r\n // OPTIMIZED: Use observation index for simple single-word terms (O(1) vs O(n))\r\n // The index only matches complete words, not substrings, so we can only\r\n // use it as a quick positive check. If not found in index, fall through\r\n // to substring matching for compatibility.\r\n if (this.isSimpleTerm(value) && !value.includes(' ')) {\r\n const candidateNames = this.storage.getEntitiesByObservationWord(value);\r\n if (candidateNames.has(entity.name)) {\r\n return true; // O(1) positive match\r\n }\r\n // Not found in index - entity doesn't have this complete word,\r\n // but might contain it as substring - fall through to check\r\n }\r\n // Linear scan for substring matches, phrases, and patterns\r\n return lowercased\r\n ? lowercased.observations.some(obs => obs.includes(value))\r\n : entity.observations.some(obs => obs.toLowerCase().includes(value));\r\n case 'tag':\r\n case 'tags':\r\n return lowercased\r\n ? lowercased.tags.some(tag => tag.includes(value))\r\n : (entity.tags?.some(tag => tag.toLowerCase().includes(value)) || false);\r\n default:\r\n // Unknown field, search all text fields\r\n return this.entityMatchesTerm(entity, value, lowercased);\r\n }\r\n }\r\n\r\n // General search across all fields\r\n return this.entityMatchesTerm(entity, value, lowercased);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Check if a search term is simple (no regex or wildcards).\r\n * Simple terms can use the O(1) observation index.\r\n */\r\n private isSimpleTerm(term: string): boolean {\r\n const specialChars = /[.*+?^${}()|\\\\[\\]]/;\r\n return !specialChars.test(term);\r\n }\r\n\r\n /**\r\n * Check if entity matches a search term in any text field.\r\n * OPTIMIZED: Uses pre-computed lowercase data when available.\r\n */\r\n private entityMatchesTerm(entity: Entity, term: string, lowercased?: ReturnType<typeof this.storage.getLowercased>): boolean {\r\n if (lowercased) {\r\n return (\r\n lowercased.name.includes(term) ||\r\n lowercased.entityType.includes(term) ||\r\n lowercased.observations.some(obs => obs.includes(term)) ||\r\n lowercased.tags.some(tag => tag.includes(term))\r\n );\r\n }\r\n\r\n // Fallback for entities not in cache\r\n const termLower = term.toLowerCase();\r\n return (\r\n entity.name.toLowerCase().includes(termLower) ||\r\n entity.entityType.toLowerCase().includes(termLower) ||\r\n entity.observations.some(obs => obs.toLowerCase().includes(termLower)) ||\r\n (entity.tags?.some(tag => tag.toLowerCase().includes(termLower)) || false)\r\n );\r\n }\r\n\r\n /**\r\n * Validate query complexity to prevent resource exhaustion.\r\n * Checks nesting depth, term count, and operator count against configured limits.\r\n */\r\n private validateQueryComplexity(node: BooleanQueryNode, depth: number = 0): void {\r\n // Check nesting depth\r\n if (depth > QUERY_LIMITS.MAX_DEPTH) {\r\n throw new ValidationError(\r\n 'Query too complex',\r\n [`Query nesting depth ${depth} exceeds maximum of ${QUERY_LIMITS.MAX_DEPTH}`]\r\n );\r\n }\r\n\r\n // Count terms and operators recursively\r\n const complexity = this.calculateQueryComplexity(node);\r\n\r\n if (complexity.terms > QUERY_LIMITS.MAX_TERMS) {\r\n throw new ValidationError(\r\n 'Query too complex',\r\n [`Query has ${complexity.terms} terms, exceeds maximum of ${QUERY_LIMITS.MAX_TERMS}`]\r\n );\r\n }\r\n\r\n if (complexity.operators > QUERY_LIMITS.MAX_OPERATORS) {\r\n throw new ValidationError(\r\n 'Query too complex',\r\n [`Query has ${complexity.operators} operators, exceeds maximum of ${QUERY_LIMITS.MAX_OPERATORS}`]\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Calculate query complexity metrics.\r\n */\r\n private calculateQueryComplexity(\r\n node: BooleanQueryNode,\r\n depth: number = 0\r\n ): { terms: number; operators: number; maxDepth: number } {\r\n switch (node.type) {\r\n case 'AND':\r\n case 'OR':\r\n const childResults = node.children.map(child => this.calculateQueryComplexity(child, depth + 1));\r\n return {\r\n terms: childResults.reduce((sum, r) => sum + r.terms, 0),\r\n operators: childResults.reduce((sum, r) => sum + r.operators, 1), // +1 for current operator\r\n maxDepth: Math.max(depth, ...childResults.map(r => r.maxDepth)),\r\n };\r\n\r\n case 'NOT':\r\n const notResult = this.calculateQueryComplexity(node.child, depth + 1);\r\n return {\r\n terms: notResult.terms,\r\n operators: notResult.operators + 1,\r\n maxDepth: Math.max(depth, notResult.maxDepth),\r\n };\r\n\r\n case 'TERM':\r\n return {\r\n terms: 1,\r\n operators: 0,\r\n maxDepth: depth,\r\n };\r\n }\r\n }\r\n}\r\n","/**\r\n * Fuzzy Search\r\n *\r\n * Search with typo tolerance using Levenshtein distance similarity.\r\n * Uses workerpool for parallel processing on large datasets.\r\n *\r\n * @module search/FuzzySearch\r\n */\r\n\r\nimport type { Entity, KnowledgeGraph } from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport { levenshteinDistance } from '../utils/index.js';\r\nimport { SEARCH_LIMITS } from '../utils/constants.js';\r\nimport { SearchFilterChain, type SearchFilters } from './SearchFilterChain.js';\r\nimport workerpool, { type Pool } from '@danielsimonjr/workerpool';\r\nimport { fileURLToPath } from 'url';\r\nimport { dirname, join, sep } from 'path';\r\n\r\n/**\r\n * Default fuzzy search similarity threshold (70% match required).\r\n * Lower values are more permissive (more typos tolerated).\r\n * Higher values are stricter (fewer typos tolerated).\r\n */\r\nexport const DEFAULT_FUZZY_THRESHOLD = 0.7;\r\n\r\n/**\r\n * Phase 4 Sprint 3: Cache entry for fuzzy search results.\r\n */\r\ninterface FuzzyCacheEntry {\r\n /** Cached entity names that matched */\r\n entityNames: string[];\r\n /** Entity count when cache was created (for invalidation) */\r\n entityCount: number;\r\n /** Timestamp when cached */\r\n timestamp: number;\r\n}\r\n\r\n/**\r\n * Phase 4 Sprint 3: Maximum cache size to prevent memory bloat.\r\n */\r\nconst FUZZY_CACHE_MAX_SIZE = 100;\r\n\r\n/**\r\n * Phase 4 Sprint 3: Cache TTL in milliseconds (5 minutes).\r\n */\r\nconst FUZZY_CACHE_TTL_MS = 5 * 60 * 1000;\r\n\r\n/**\r\n * Phase 7 Sprint 3: Minimum number of entities to activate worker pool.\r\n */\r\nconst WORKER_MIN_ENTITIES = 500;\r\n\r\n/**\r\n * Phase 7 Sprint 3: Maximum threshold for worker pool activation.\r\n * Higher thresholds have fewer matches, so single-threaded is faster.\r\n */\r\nconst WORKER_MAX_THRESHOLD = 0.8;\r\n\r\n/**\r\n * Match result from worker.\r\n */\r\ninterface MatchResult {\r\n name: string;\r\n score: number;\r\n matchedIn: 'name' | 'observation';\r\n}\r\n\r\n/**\r\n * Options for FuzzySearch constructor.\r\n */\r\nexport interface FuzzySearchOptions {\r\n /**\r\n * Whether to use worker pool for parallel processing.\r\n * Set to false for testing or when workers are not available.\r\n * Default: true\r\n */\r\n useWorkerPool?: boolean;\r\n}\r\n\r\n/**\r\n * Performs fuzzy search with configurable similarity threshold.\r\n */\r\nexport class FuzzySearch {\r\n /**\r\n * Phase 4 Sprint 3: Result cache for fuzzy search.\r\n * Maps cache key -> cached entity names.\r\n */\r\n private fuzzyResultCache: Map<string, FuzzyCacheEntry> = new Map();\r\n\r\n /**\r\n * Phase 8: Worker pool using workerpool library.\r\n * Initialized lazily when needed.\r\n */\r\n private workerPool: Pool | null = null;\r\n\r\n /**\r\n * Phase 7 Sprint 3: Path to the worker script.\r\n */\r\n private workerPath: string;\r\n\r\n /**\r\n * Phase 8: Whether to use worker pool for parallel processing.\r\n * Can be disabled for testing or when workers are not available.\r\n */\r\n private useWorkerPool: boolean;\r\n\r\n constructor(private storage: GraphStorage, options: FuzzySearchOptions = {}) {\r\n this.useWorkerPool = options.useWorkerPool ?? true;\r\n // Calculate worker path using ESM module resolution\r\n const currentFileUrl = import.meta.url;\r\n const currentDir = dirname(fileURLToPath(currentFileUrl));\r\n\r\n // Check if we're running from src/ (during tests) or dist/ (production)\r\n const isRunningFromSrc = currentDir.includes(`${sep}src${sep}`);\r\n\r\n if (isRunningFromSrc) {\r\n // During tests, worker is in dist/workers/ relative to project root\r\n const projectRoot = join(currentDir, '..', '..');\r\n this.workerPath = join(projectRoot, 'dist', 'workers', 'levenshteinWorker.js');\r\n } else {\r\n // In production, worker is in dist/workers/ relative to current dist/search/\r\n this.workerPath = join(currentDir, '..', 'workers', 'levenshteinWorker.js');\r\n }\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 3: Generate cache key for fuzzy search parameters.\r\n */\r\n private generateCacheKey(\r\n query: string,\r\n threshold: number,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n offset?: number,\r\n limit?: number\r\n ): string {\r\n return JSON.stringify({\r\n q: query.toLowerCase(),\r\n t: threshold,\r\n tags: tags?.sort().join(',') ?? '',\r\n min: minImportance,\r\n max: maxImportance,\r\n off: offset,\r\n lim: limit,\r\n });\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 3: Clear the fuzzy search cache.\r\n */\r\n clearCache(): void {\r\n this.fuzzyResultCache.clear();\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 3: Invalidate stale cache entries.\r\n */\r\n private cleanupCache(): void {\r\n const now = Date.now();\r\n const entries = Array.from(this.fuzzyResultCache.entries());\r\n\r\n // Remove expired entries\r\n for (const [key, entry] of entries) {\r\n if (now - entry.timestamp > FUZZY_CACHE_TTL_MS) {\r\n this.fuzzyResultCache.delete(key);\r\n }\r\n }\r\n\r\n // If still over limit, remove oldest entries\r\n if (this.fuzzyResultCache.size > FUZZY_CACHE_MAX_SIZE) {\r\n const sortedEntries = entries\r\n .filter(([k]) => this.fuzzyResultCache.has(k))\r\n .sort((a, b) => a[1].timestamp - b[1].timestamp);\r\n\r\n const toRemove = sortedEntries.slice(0, this.fuzzyResultCache.size - FUZZY_CACHE_MAX_SIZE);\r\n for (const [key] of toRemove) {\r\n this.fuzzyResultCache.delete(key);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Fuzzy search for entities with typo tolerance and pagination.\r\n *\r\n * Uses Levenshtein distance to calculate similarity between strings.\r\n * Matches if similarity >= threshold (0.0 to 1.0).\r\n *\r\n * Phase 4 Sprint 3: Implements result caching for repeated queries.\r\n *\r\n * @param query - Search query\r\n * @param threshold - Similarity threshold (0.0 to 1.0), default DEFAULT_FUZZY_THRESHOLD\r\n * @param tags - Optional tags filter\r\n * @param minImportance - Optional minimum importance\r\n * @param maxImportance - Optional maximum importance\r\n * @param offset - Number of results to skip (default: 0)\r\n * @param limit - Maximum number of results (default: 50, max: 200)\r\n * @returns Filtered knowledge graph with fuzzy matches and pagination applied\r\n */\r\n async fuzzySearch(\r\n query: string,\r\n threshold: number = DEFAULT_FUZZY_THRESHOLD,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n offset: number = 0,\r\n limit: number = SEARCH_LIMITS.DEFAULT\r\n ): Promise<KnowledgeGraph> {\r\n const graph = await this.storage.loadGraph();\r\n const queryLower = query.toLowerCase();\r\n\r\n // Phase 4 Sprint 3: Generate cache key and check cache\r\n const cacheKey = this.generateCacheKey(query, threshold, tags, minImportance, maxImportance, offset, limit);\r\n const cached = this.fuzzyResultCache.get(cacheKey);\r\n\r\n // Check if cache is valid (entity count hasn't changed)\r\n if (cached && cached.entityCount === graph.entities.length) {\r\n const now = Date.now();\r\n if (now - cached.timestamp < FUZZY_CACHE_TTL_MS) {\r\n // Return cached results\r\n const cachedNameSet = new Set(cached.entityNames);\r\n const cachedEntities = graph.entities.filter(e => cachedNameSet.has(e.name));\r\n const cachedEntityNames = new Set(cached.entityNames);\r\n const cachedRelations = graph.relations.filter(\r\n r => cachedEntityNames.has(r.from) && cachedEntityNames.has(r.to)\r\n );\r\n return { entities: cachedEntities, relations: cachedRelations };\r\n }\r\n }\r\n\r\n // Phase 7 Sprint 3: Use worker pool for large graphs with low thresholds\r\n // Phase 8: Respect useWorkerPool flag for testing\r\n const shouldUseWorkers =\r\n this.useWorkerPool &&\r\n graph.entities.length >= WORKER_MIN_ENTITIES &&\r\n threshold < WORKER_MAX_THRESHOLD;\r\n\r\n let fuzzyMatched: Entity[];\r\n\r\n if (shouldUseWorkers) {\r\n fuzzyMatched = await this.searchWithWorkers(query, threshold, graph.entities as Entity[]);\r\n } else {\r\n // Perform single-threaded fuzzy search\r\n fuzzyMatched = this.performFuzzyMatch(graph.entities, queryLower, threshold);\r\n }\r\n\r\n // Apply tag and importance filters using SearchFilterChain\r\n const filters: SearchFilters = { tags, minImportance, maxImportance };\r\n const filteredEntities = SearchFilterChain.applyFilters(fuzzyMatched, filters);\r\n\r\n // Apply pagination using SearchFilterChain\r\n const pagination = SearchFilterChain.validatePagination(offset, limit);\r\n const paginatedEntities = SearchFilterChain.paginate(filteredEntities, pagination);\r\n\r\n // Phase 4 Sprint 3: Cache the results\r\n this.fuzzyResultCache.set(cacheKey, {\r\n entityNames: paginatedEntities.map(e => e.name),\r\n entityCount: graph.entities.length,\r\n timestamp: Date.now(),\r\n });\r\n\r\n // Cleanup old cache entries periodically\r\n if (this.fuzzyResultCache.size > FUZZY_CACHE_MAX_SIZE / 2) {\r\n this.cleanupCache();\r\n }\r\n\r\n const filteredEntityNames = new Set(paginatedEntities.map(e => e.name));\r\n const filteredRelations = graph.relations.filter(\r\n r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)\r\n );\r\n\r\n return {\r\n entities: paginatedEntities,\r\n relations: filteredRelations,\r\n };\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 3: Perform the actual fuzzy matching logic.\r\n * Extracted from fuzzySearch for cleaner code structure.\r\n */\r\n private performFuzzyMatch(entities: readonly Entity[], queryLower: string, threshold: number): Entity[] {\r\n return entities.filter(e => {\r\n const lowercased = this.storage.getLowercased(e.name);\r\n\r\n // Check name match (use pre-computed lowercase)\r\n const nameLower = lowercased?.name ?? e.name.toLowerCase();\r\n if (this.isFuzzyMatchLower(nameLower, queryLower, threshold)) return true;\r\n\r\n // Check type match (use pre-computed lowercase)\r\n const typeLower = lowercased?.entityType ?? e.entityType.toLowerCase();\r\n if (this.isFuzzyMatchLower(typeLower, queryLower, threshold)) return true;\r\n\r\n // Check observations (use pre-computed lowercase array)\r\n const obsLower = lowercased?.observations ?? e.observations.map(o => o.toLowerCase());\r\n return obsLower.some(\r\n o =>\r\n // For observations, split into words and check each word\r\n o\r\n .split(/\\s+/)\r\n .some(word => this.isFuzzyMatchLower(word, queryLower, threshold)) ||\r\n // Also check if the observation contains the query\r\n this.isFuzzyMatchLower(o, queryLower, threshold)\r\n );\r\n }) as Entity[];\r\n }\r\n\r\n /**\r\n * Check if two already-lowercase strings match with fuzzy logic.\r\n *\r\n * OPTIMIZED: Skips toLowerCase() calls when strings are already lowercase.\r\n *\r\n * @param s1 - First string (already lowercase)\r\n * @param s2 - Second string (already lowercase)\r\n * @param threshold - Similarity threshold (0.0 to 1.0)\r\n * @returns True if strings match fuzzily\r\n */\r\n private isFuzzyMatchLower(s1: string, s2: string, threshold: number = 0.7): boolean {\r\n // Exact match\r\n if (s1 === s2) return true;\r\n\r\n // One contains the other\r\n if (s1.includes(s2) || s2.includes(s1)) return true;\r\n\r\n // Calculate similarity using Levenshtein distance\r\n const distance = levenshteinDistance(s1, s2);\r\n const maxLength = Math.max(s1.length, s2.length);\r\n const similarity = 1 - distance / maxLength;\r\n\r\n return similarity >= threshold;\r\n }\r\n\r\n /**\r\n * Phase 8: Perform fuzzy search using workerpool for parallel processing.\r\n *\r\n * Splits entities into chunks and processes them in parallel using worker threads.\r\n * Falls back to single-threaded search if worker execution fails.\r\n *\r\n * @param query - Search query\r\n * @param threshold - Similarity threshold\r\n * @param entities - Entities to search\r\n * @returns Array of matched entities\r\n */\r\n private async searchWithWorkers(\r\n query: string,\r\n threshold: number,\r\n entities: Entity[]\r\n ): Promise<Entity[]> {\r\n try {\r\n // Initialize worker pool lazily using workerpool\r\n if (!this.workerPool) {\r\n // Enable ESM module support for Node.js 20+\r\n // The 'type: module' option is needed for ESM workers but may not be in @types/node\r\n const workerThreadOpts = { type: 'module' } as Record<string, unknown>;\r\n this.workerPool = workerpool.pool(this.workerPath, {\r\n maxWorkers: Math.max(1, workerpool.cpus - 1),\r\n workerType: 'thread',\r\n workerThreadOpts,\r\n });\r\n }\r\n\r\n // Split entities into chunks based on CPU count\r\n const numWorkers = Math.max(1, workerpool.cpus - 1);\r\n const chunkSize = Math.ceil(entities.length / numWorkers);\r\n const chunks: Entity[][] = [];\r\n for (let i = 0; i < entities.length; i += chunkSize) {\r\n chunks.push(entities.slice(i, i + chunkSize));\r\n }\r\n\r\n // Prepare worker inputs with lowercased data\r\n const workerInputs = chunks.map(chunk => ({\r\n query,\r\n threshold,\r\n entities: chunk.map(e => ({\r\n name: e.name,\r\n nameLower: e.name.toLowerCase(),\r\n observations: e.observations.map(o => o.toLowerCase()),\r\n })),\r\n }));\r\n\r\n // Execute all chunks in parallel using workerpool with timeout\r\n const WORKER_TIMEOUT_MS = 30000; // 30 seconds\r\n const results = await Promise.all(\r\n workerInputs.map(input =>\r\n this.workerPool!.exec('searchEntities', [input])\r\n .timeout(WORKER_TIMEOUT_MS) as Promise<MatchResult[]>\r\n )\r\n );\r\n\r\n // Flatten results and extract matched entity names\r\n const matchedNames = new Set(results.flat().map(r => r.name));\r\n\r\n // Return entities that matched\r\n return entities.filter(e => matchedNames.has(e.name));\r\n } catch (error) {\r\n // Worker execution failed - fall back to single-threaded mode\r\n console.warn(\r\n `Worker pool execution failed, falling back to single-threaded fuzzy search: ${\r\n error instanceof Error ? error.message : String(error)\r\n }`\r\n );\r\n\r\n // Use the existing single-threaded implementation\r\n const queryLower = query.toLowerCase();\r\n return this.performFuzzyMatch(entities, queryLower, threshold);\r\n }\r\n }\r\n\r\n /**\r\n * Phase 8: Shutdown the worker pool and clean up resources.\r\n *\r\n * Should be called when FuzzySearch is no longer needed.\r\n */\r\n async shutdown(): Promise<void> {\r\n if (this.workerPool) {\r\n await this.workerPool.terminate();\r\n this.workerPool = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * Search Suggestions\r\n *\r\n * Provides \"did you mean?\" suggestions using Levenshtein distance.\r\n *\r\n * @module search/SearchSuggestions\r\n */\r\n\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport { levenshteinDistance } from '../utils/index.js';\r\n\r\n/**\r\n * Internal suggestion with similarity score.\r\n */\r\ninterface Suggestion {\r\n text: string;\r\n similarity: number;\r\n}\r\n\r\n/**\r\n * Generates search suggestions based on entity names and types.\r\n */\r\nexport class SearchSuggestions {\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Get \"did you mean?\" suggestions for a query.\r\n *\r\n * Returns similar entity names and types that might be what the user intended.\r\n * Excludes exact matches (similarity < 1.0) and very dissimilar strings (similarity > 0.5).\r\n *\r\n * @param query - The search query\r\n * @param maxSuggestions - Maximum number of suggestions to return (default 5)\r\n * @returns Array of suggested entity/type names sorted by similarity\r\n */\r\n async getSearchSuggestions(query: string, maxSuggestions: number = 5): Promise<string[]> {\r\n const graph = await this.storage.loadGraph();\r\n const queryLower = query.toLowerCase();\r\n\r\n const suggestions: Suggestion[] = [];\r\n\r\n // Check entity names\r\n for (const entity of graph.entities) {\r\n const distance = levenshteinDistance(queryLower, entity.name.toLowerCase());\r\n const maxLength = Math.max(queryLower.length, entity.name.length);\r\n const similarity = 1 - distance / maxLength;\r\n\r\n if (similarity > 0.5 && similarity < 1.0) {\r\n // Not exact match but similar\r\n suggestions.push({ text: entity.name, similarity });\r\n }\r\n }\r\n\r\n // Check entity types\r\n const uniqueTypes = [...new Set(graph.entities.map(e => e.entityType))];\r\n for (const type of uniqueTypes) {\r\n const distance = levenshteinDistance(queryLower, type.toLowerCase());\r\n const maxLength = Math.max(queryLower.length, type.length);\r\n const similarity = 1 - distance / maxLength;\r\n\r\n if (similarity > 0.5 && similarity < 1.0) {\r\n suggestions.push({ text: type, similarity });\r\n }\r\n }\r\n\r\n // Sort by similarity and return top suggestions\r\n return suggestions\r\n .sort((a, b) => b.similarity - a.similarity)\r\n .slice(0, maxSuggestions)\r\n .map(s => s.text);\r\n }\r\n}\r\n","/**\r\n * Saved Search Manager\r\n *\r\n * Manages persistent saved searches with JSONL storage.\r\n *\r\n * @module search/SavedSearchManager\r\n */\r\n\r\nimport * as fs from 'fs/promises';\r\nimport type { SavedSearch, KnowledgeGraph } from '../types/index.js';\r\nimport type { BasicSearch } from './BasicSearch.js';\r\nimport { sanitizeObject } from '../utils/index.js';\r\n\r\n/**\r\n * Manages saved search queries with usage tracking.\r\n */\r\nexport class SavedSearchManager {\r\n constructor(\r\n private savedSearchesFilePath: string,\r\n private basicSearch: BasicSearch\r\n ) {}\r\n\r\n /**\r\n * Load all saved searches from JSONL file.\r\n *\r\n * @returns Array of saved searches\r\n */\r\n private async loadSavedSearches(): Promise<SavedSearch[]> {\r\n try {\r\n const data = await fs.readFile(this.savedSearchesFilePath, 'utf-8');\r\n const lines = data.split('\\n').filter((line: string) => line.trim() !== '');\r\n return lines.map((line: string) => JSON.parse(line) as SavedSearch);\r\n } catch (error) {\r\n if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {\r\n return [];\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Save searches to JSONL file.\r\n *\r\n * @param searches - Array of saved searches\r\n */\r\n private async saveSavedSearches(searches: SavedSearch[]): Promise<void> {\r\n const lines = searches.map(s => JSON.stringify(s));\r\n await fs.writeFile(this.savedSearchesFilePath, lines.join('\\n'));\r\n }\r\n\r\n /**\r\n * Save a search query for later reuse.\r\n *\r\n * @param search - Search parameters (without createdAt, useCount, lastUsed)\r\n * @returns The newly created saved search\r\n * @throws Error if search name already exists\r\n */\r\n async saveSearch(\r\n search: Omit<SavedSearch, 'createdAt' | 'useCount' | 'lastUsed'>\r\n ): Promise<SavedSearch> {\r\n const searches = await this.loadSavedSearches();\r\n\r\n // Check if name already exists\r\n if (searches.some(s => s.name === search.name)) {\r\n throw new Error(`Saved search with name \"${search.name}\" already exists`);\r\n }\r\n\r\n const newSearch: SavedSearch = {\r\n ...search,\r\n createdAt: new Date().toISOString(),\r\n useCount: 0,\r\n };\r\n\r\n searches.push(newSearch);\r\n await this.saveSavedSearches(searches);\r\n\r\n return newSearch;\r\n }\r\n\r\n /**\r\n * List all saved searches.\r\n *\r\n * @returns Array of all saved searches\r\n */\r\n async listSavedSearches(): Promise<SavedSearch[]> {\r\n return await this.loadSavedSearches();\r\n }\r\n\r\n /**\r\n * Get a specific saved search by name.\r\n *\r\n * @param name - Search name\r\n * @returns Saved search or null if not found\r\n */\r\n async getSavedSearch(name: string): Promise<SavedSearch | null> {\r\n const searches = await this.loadSavedSearches();\r\n return searches.find(s => s.name === name) || null;\r\n }\r\n\r\n /**\r\n * Execute a saved search by name.\r\n *\r\n * Updates usage statistics (lastUsed, useCount) before executing.\r\n *\r\n * @param name - Search name\r\n * @returns Search results as knowledge graph\r\n * @throws Error if search not found\r\n */\r\n async executeSavedSearch(name: string): Promise<KnowledgeGraph> {\r\n const searches = await this.loadSavedSearches();\r\n const search = searches.find(s => s.name === name);\r\n\r\n if (!search) {\r\n throw new Error(`Saved search \"${name}\" not found`);\r\n }\r\n\r\n // Update usage statistics\r\n search.lastUsed = new Date().toISOString();\r\n search.useCount++;\r\n await this.saveSavedSearches(searches);\r\n\r\n // Execute the search using BasicSearch\r\n return await this.basicSearch.searchNodes(\r\n search.query,\r\n search.tags,\r\n search.minImportance,\r\n search.maxImportance\r\n );\r\n }\r\n\r\n /**\r\n * Delete a saved search.\r\n *\r\n * @param name - Search name\r\n * @returns True if deleted, false if not found\r\n */\r\n async deleteSavedSearch(name: string): Promise<boolean> {\r\n const searches = await this.loadSavedSearches();\r\n const initialLength = searches.length;\r\n const filtered = searches.filter(s => s.name !== name);\r\n\r\n if (filtered.length === initialLength) {\r\n return false; // Search not found\r\n }\r\n\r\n await this.saveSavedSearches(filtered);\r\n return true;\r\n }\r\n\r\n /**\r\n * Update a saved search.\r\n *\r\n * Cannot update name, createdAt, useCount, or lastUsed fields.\r\n *\r\n * @param name - Search name\r\n * @param updates - Partial search with fields to update\r\n * @returns Updated saved search\r\n * @throws Error if search not found\r\n */\r\n async updateSavedSearch(\r\n name: string,\r\n updates: Partial<Omit<SavedSearch, 'name' | 'createdAt' | 'useCount' | 'lastUsed'>>\r\n ): Promise<SavedSearch> {\r\n const searches = await this.loadSavedSearches();\r\n const search = searches.find(s => s.name === name);\r\n\r\n if (!search) {\r\n throw new Error(`Saved search \"${name}\" not found`);\r\n }\r\n\r\n // Apply updates (sanitized to prevent prototype pollution)\r\n Object.assign(search, sanitizeObject(updates as Record<string, unknown>));\r\n\r\n await this.saveSavedSearches(searches);\r\n return search;\r\n }\r\n}\r\n","/**\r\n * Query Cost Estimator\r\n *\r\n * Phase 10 Sprint 4: Estimates the cost of different search methods\r\n * and recommends the optimal method based on query characteristics\r\n * and graph size.\r\n *\r\n * Phase 12 Sprint 4: Enhanced with adaptive depth calculation,\r\n * layer recommendations, and token estimation.\r\n *\r\n * @module search/QueryCostEstimator\r\n */\r\n\r\nimport type {\r\n SearchMethod,\r\n QueryCostEstimate,\r\n QueryCostEstimatorOptions,\r\n QueryAnalysis,\r\n} from '../types/index.js';\r\n\r\n/**\r\n * Phase 12 Sprint 4: Layer type for hybrid search.\r\n */\r\nexport type SearchLayer = 'semantic' | 'lexical' | 'symbolic';\r\n\r\n/**\r\n * Phase 12 Sprint 4: Extended cost estimate with layer recommendations.\r\n */\r\nexport interface ExtendedQueryCostEstimate extends QueryCostEstimate {\r\n /** Recommended layers in priority order */\r\n recommendedLayers: SearchLayer[];\r\n /** Estimated tokens for this query */\r\n estimatedTokens: number;\r\n /** Adaptive depth (k_dyn) for search results */\r\n adaptiveDepth: number;\r\n /** Layer-specific cost estimates */\r\n layerCosts: Record<SearchLayer, number>;\r\n}\r\n\r\n/**\r\n * Phase 12 Sprint 4: Layer recommendation options.\r\n */\r\nexport interface LayerRecommendationOptions {\r\n /** Query analysis from QueryAnalyzer */\r\n analysis?: QueryAnalysis;\r\n /** Whether semantic search is available */\r\n semanticAvailable?: boolean;\r\n /** Maximum layers to recommend (default: 3) */\r\n maxLayers?: number;\r\n}\r\n\r\n/**\r\n * Phase 12 Sprint 4: Token estimation options.\r\n */\r\nexport interface TokenEstimationOptions {\r\n /** Average characters per token (default: 4) */\r\n charsPerToken?: number;\r\n /** Include entity count in estimate (default: true) */\r\n includeEntityCount?: boolean;\r\n}\r\n\r\n/**\r\n * Phase 12 Sprint 4: Adaptive depth calculation parameters.\r\n */\r\nexport interface AdaptiveDepthConfig {\r\n /** Base number of results (k_base, default: 10) */\r\n kBase?: number;\r\n /** Complexity scaling factor (δ, delta, default: 0.5) */\r\n delta?: number;\r\n /** Maximum depth cap (default: 100) */\r\n maxDepth?: number;\r\n}\r\n\r\n/**\r\n * Default options for the QueryCostEstimator.\r\n */\r\nconst DEFAULT_OPTIONS: Required<QueryCostEstimatorOptions> = {\r\n basicTimePerEntity: 0.01,\r\n rankedTimePerEntity: 0.05,\r\n booleanTimePerEntity: 0.02,\r\n fuzzyTimePerEntity: 0.1,\r\n semanticTimePerEntity: 0.5,\r\n lowComplexityThreshold: 100,\r\n highComplexityThreshold: 1000,\r\n};\r\n\r\n/**\r\n * Default adaptive depth configuration.\r\n */\r\nconst DEFAULT_ADAPTIVE_DEPTH: Required<AdaptiveDepthConfig> = {\r\n kBase: 10,\r\n delta: 0.5,\r\n maxDepth: 100,\r\n};\r\n\r\n/**\r\n * Default token estimation options.\r\n */\r\nconst DEFAULT_TOKEN_ESTIMATION: Required<TokenEstimationOptions> = {\r\n charsPerToken: 4,\r\n includeEntityCount: true,\r\n};\r\n\r\n/**\r\n * Phase 10 Sprint 4: Estimates search query costs and recommends optimal methods.\r\n *\r\n * Analyzes query characteristics and graph size to estimate execution time\r\n * and recommend the most appropriate search method.\r\n *\r\n * @example\r\n * ```typescript\r\n * const estimator = new QueryCostEstimator();\r\n *\r\n * // Get estimate for a specific method\r\n * const estimate = estimator.estimateMethod('ranked', 'test query', 1000);\r\n *\r\n * // Get the recommended method for a query\r\n * const recommendation = estimator.recommendMethod('test query', 1000);\r\n *\r\n * // Get estimates for all methods\r\n * const allEstimates = estimator.estimateAllMethods('test query', 1000);\r\n * ```\r\n */\r\nexport class QueryCostEstimator {\r\n private options: Required<QueryCostEstimatorOptions>;\r\n private adaptiveDepthConfig: Required<AdaptiveDepthConfig>;\r\n private tokenEstimationConfig: Required<TokenEstimationOptions>;\r\n\r\n /**\r\n * Create a new QueryCostEstimator instance.\r\n *\r\n * @param options - Optional configuration overrides\r\n * @param adaptiveDepthConfig - Optional adaptive depth configuration\r\n * @param tokenEstimationConfig - Optional token estimation configuration\r\n */\r\n constructor(\r\n options?: QueryCostEstimatorOptions,\r\n adaptiveDepthConfig?: AdaptiveDepthConfig,\r\n tokenEstimationConfig?: TokenEstimationOptions\r\n ) {\r\n this.options = { ...DEFAULT_OPTIONS, ...options };\r\n this.adaptiveDepthConfig = { ...DEFAULT_ADAPTIVE_DEPTH, ...adaptiveDepthConfig };\r\n this.tokenEstimationConfig = { ...DEFAULT_TOKEN_ESTIMATION, ...tokenEstimationConfig };\r\n }\r\n\r\n /**\r\n * Estimate the cost of a specific search method.\r\n *\r\n * @param method - The search method to estimate\r\n * @param query - The search query\r\n * @param entityCount - Number of entities in the graph\r\n * @returns Cost estimate for the method\r\n */\r\n estimateMethod(\r\n method: SearchMethod,\r\n query: string,\r\n entityCount: number\r\n ): QueryCostEstimate {\r\n // Get the recommended method first to determine isRecommended\r\n const recommendedMethod = this.getRecommendedMethodOnly(query, entityCount);\r\n return this.estimateMethodInternal(method, query, entityCount, method === recommendedMethod);\r\n }\r\n\r\n /**\r\n * Internal method to estimate without triggering recursion.\r\n * @private\r\n */\r\n private estimateMethodInternal(\r\n method: SearchMethod,\r\n query: string,\r\n entityCount: number,\r\n isRecommended: boolean\r\n ): QueryCostEstimate {\r\n const baseTime = this.getBaseTimeForMethod(method);\r\n const queryComplexityFactor = this.getQueryComplexityFactor(query, method);\r\n const estimatedTimeMs = baseTime * entityCount * queryComplexityFactor;\r\n const complexity = this.getComplexity(entityCount);\r\n const recommendation = this.getRecommendation(method, query, entityCount, complexity);\r\n\r\n return {\r\n method,\r\n estimatedTimeMs: Math.round(estimatedTimeMs * 100) / 100,\r\n complexity,\r\n entityCount,\r\n recommendation,\r\n isRecommended,\r\n };\r\n }\r\n\r\n /**\r\n * Get just the recommended method without full estimate (avoids recursion).\r\n * @private\r\n */\r\n private getRecommendedMethodOnly(\r\n query: string,\r\n entityCount: number,\r\n preferredMethods?: SearchMethod[]\r\n ): SearchMethod {\r\n const methods = preferredMethods ?? (['basic', 'ranked', 'boolean', 'fuzzy', 'semantic'] as SearchMethod[]);\r\n\r\n // Score each method and find the best\r\n let bestMethod = methods[0];\r\n let bestScore = this.scoreMethod(methods[0], query, entityCount);\r\n\r\n for (let i = 1; i < methods.length; i++) {\r\n const score = this.scoreMethod(methods[i], query, entityCount);\r\n if (score > bestScore) {\r\n bestScore = score;\r\n bestMethod = methods[i];\r\n }\r\n }\r\n\r\n return bestMethod;\r\n }\r\n\r\n /**\r\n * Get estimates for all available search methods.\r\n *\r\n * @param query - The search query\r\n * @param entityCount - Number of entities in the graph\r\n * @returns Array of estimates for all methods\r\n */\r\n estimateAllMethods(query: string, entityCount: number): QueryCostEstimate[] {\r\n const methods: SearchMethod[] = ['basic', 'ranked', 'boolean', 'fuzzy', 'semantic'];\r\n const recommendedMethod = this.getRecommendedMethodOnly(query, entityCount);\r\n return methods.map(method =>\r\n this.estimateMethodInternal(method, query, entityCount, method === recommendedMethod)\r\n );\r\n }\r\n\r\n /**\r\n * Recommend the best search method for a query.\r\n *\r\n * @param query - The search query\r\n * @param entityCount - Number of entities in the graph\r\n * @param preferredMethods - Optional array of methods to consider (default: all)\r\n * @returns The recommended method and reason\r\n */\r\n recommendMethod(\r\n query: string,\r\n entityCount: number,\r\n preferredMethods?: SearchMethod[]\r\n ): { method: SearchMethod; reason: string; estimate: QueryCostEstimate } {\r\n const methods = preferredMethods ?? (['basic', 'ranked', 'boolean', 'fuzzy', 'semantic'] as SearchMethod[]);\r\n\r\n // Score each method based on query characteristics and cost\r\n const scores = methods.map(method => ({\r\n method,\r\n score: this.scoreMethod(method, query, entityCount),\r\n estimate: this.estimateMethod(method, query, entityCount),\r\n }));\r\n\r\n // Sort by score (higher is better)\r\n scores.sort((a, b) => b.score - a.score);\r\n\r\n const best = scores[0];\r\n const reason = this.getSelectionReason(best.method, query, entityCount);\r\n\r\n return {\r\n method: best.method,\r\n reason,\r\n estimate: best.estimate,\r\n };\r\n }\r\n\r\n /**\r\n * Get the base time per entity for a search method.\r\n * @private\r\n */\r\n private getBaseTimeForMethod(method: SearchMethod): number {\r\n switch (method) {\r\n case 'basic':\r\n return this.options.basicTimePerEntity;\r\n case 'ranked':\r\n return this.options.rankedTimePerEntity;\r\n case 'boolean':\r\n return this.options.booleanTimePerEntity;\r\n case 'fuzzy':\r\n return this.options.fuzzyTimePerEntity;\r\n case 'semantic':\r\n return this.options.semanticTimePerEntity;\r\n }\r\n }\r\n\r\n /**\r\n * Calculate a complexity factor based on query characteristics.\r\n * @private\r\n */\r\n private getQueryComplexityFactor(query: string, method: SearchMethod): number {\r\n const words = query.trim().split(/\\s+/).length;\r\n const hasOperators = /\\b(AND|OR|NOT)\\b/.test(query);\r\n const hasWildcard = query.includes('*');\r\n const hasQuotes = query.includes('\"');\r\n\r\n let factor = 1.0;\r\n\r\n // More words = slightly more complex\r\n factor += (words - 1) * 0.1;\r\n\r\n // Boolean operators increase boolean search cost, decrease others\r\n if (hasOperators) {\r\n if (method === 'boolean') {\r\n factor *= 0.8; // Boolean search is optimized for operators\r\n } else {\r\n factor *= 1.5; // Other methods struggle with operators\r\n }\r\n }\r\n\r\n // Wildcards increase fuzzy search efficiency\r\n if (hasWildcard) {\r\n if (method === 'fuzzy') {\r\n factor *= 0.9;\r\n } else {\r\n factor *= 1.3;\r\n }\r\n }\r\n\r\n // Quoted phrases benefit ranked search\r\n if (hasQuotes) {\r\n if (method === 'ranked') {\r\n factor *= 0.9;\r\n } else {\r\n factor *= 1.1;\r\n }\r\n }\r\n\r\n return Math.max(0.5, Math.min(factor, 3.0)); // Clamp between 0.5 and 3.0\r\n }\r\n\r\n /**\r\n * Get the complexity level based on entity count.\r\n * @private\r\n */\r\n private getComplexity(entityCount: number): 'low' | 'medium' | 'high' {\r\n if (entityCount <= this.options.lowComplexityThreshold) {\r\n return 'low';\r\n }\r\n if (entityCount >= this.options.highComplexityThreshold) {\r\n return 'high';\r\n }\r\n return 'medium';\r\n }\r\n\r\n /**\r\n * Generate a human-readable recommendation.\r\n * @private\r\n */\r\n private getRecommendation(\r\n method: SearchMethod,\r\n _query: string,\r\n _entityCount: number,\r\n complexity: 'low' | 'medium' | 'high'\r\n ): string {\r\n const recommendations: Record<SearchMethod, string> = {\r\n basic: 'Fast substring matching, best for simple queries',\r\n ranked: 'TF-IDF relevance ranking, best for finding most relevant results',\r\n boolean: 'Boolean operators (AND/OR/NOT), best for precise filtering',\r\n fuzzy: 'Tolerant of typos and misspellings, best for uncertain queries',\r\n semantic: 'Meaning-based search using embeddings, best for conceptual queries',\r\n };\r\n\r\n let recommendation = recommendations[method];\r\n\r\n if (complexity === 'high' && method === 'semantic') {\r\n recommendation += ' - may be slow for large graphs';\r\n }\r\n\r\n if (complexity === 'low') {\r\n recommendation += ' - fast for small graphs';\r\n }\r\n\r\n return recommendation;\r\n }\r\n\r\n /**\r\n * Score a method based on query characteristics and graph size.\r\n * Higher score = better fit.\r\n * @private\r\n */\r\n private scoreMethod(method: SearchMethod, query: string, entityCount: number): number {\r\n let score = 50; // Base score\r\n\r\n const hasOperators = /\\b(AND|OR|NOT)\\b/.test(query);\r\n const hasWildcard = query.includes('*');\r\n const hasQuotes = query.includes('\"');\r\n const words = query.trim().split(/\\s+/).length;\r\n const isShortQuery = words <= 2;\r\n const isLongQuery = words >= 5;\r\n const complexity = this.getComplexity(entityCount);\r\n\r\n switch (method) {\r\n case 'basic':\r\n // Basic is good for simple, short queries on any size graph\r\n if (isShortQuery && !hasOperators && !hasWildcard) {\r\n score += 30;\r\n }\r\n if (complexity === 'low') {\r\n score += 20;\r\n }\r\n // Basic is fastest, give bonus for speed\r\n score += 10;\r\n break;\r\n\r\n case 'ranked':\r\n // Ranked is good for relevance-focused queries\r\n if (words >= 2) {\r\n score += 25; // Better for multi-word queries\r\n }\r\n if (hasQuotes) {\r\n score += 15; // Good for phrase matching\r\n }\r\n if (!hasOperators) {\r\n score += 10; // Not optimized for boolean\r\n }\r\n // Good balance of speed and quality\r\n score += 15;\r\n break;\r\n\r\n case 'boolean':\r\n // Boolean is best for queries with operators\r\n if (hasOperators) {\r\n score += 40;\r\n }\r\n if (!hasOperators) {\r\n score -= 20; // Not useful without operators\r\n }\r\n // Fast for filtering\r\n score += 10;\r\n break;\r\n\r\n case 'fuzzy':\r\n // Fuzzy is good for typo-tolerant search\r\n if (hasWildcard) {\r\n score += 25;\r\n }\r\n if (isShortQuery) {\r\n score += 15; // Works best on shorter queries\r\n }\r\n if (isLongQuery) {\r\n score -= 15; // Slow on long queries\r\n }\r\n if (complexity === 'high') {\r\n score -= 20; // Slow on large graphs\r\n }\r\n break;\r\n\r\n case 'semantic':\r\n // Semantic is good for conceptual/meaning-based queries\r\n if (isLongQuery) {\r\n score += 30; // Better for longer, more descriptive queries\r\n }\r\n if (!hasOperators && !hasWildcard) {\r\n score += 15; // Natural language queries\r\n }\r\n // Penalize for large graphs (slow)\r\n if (complexity === 'high') {\r\n score -= 30;\r\n }\r\n if (complexity === 'medium') {\r\n score -= 10;\r\n }\r\n break;\r\n }\r\n\r\n return score;\r\n }\r\n\r\n /**\r\n * Get a human-readable reason for why a method was selected.\r\n * @private\r\n */\r\n private getSelectionReason(method: SearchMethod, query: string, entityCount: number): string {\r\n const hasOperators = /\\b(AND|OR|NOT)\\b/.test(query);\r\n const hasWildcard = query.includes('*');\r\n const words = query.trim().split(/\\s+/).length;\r\n const complexity = this.getComplexity(entityCount);\r\n\r\n switch (method) {\r\n case 'basic':\r\n if (complexity === 'low') {\r\n return 'Basic search selected for small graph size - fast and efficient';\r\n }\r\n return 'Basic search selected for simple query pattern';\r\n\r\n case 'ranked':\r\n if (words >= 2) {\r\n return 'Ranked search selected for multi-word query - provides relevance ordering';\r\n }\r\n return 'Ranked search selected for best balance of speed and relevance';\r\n\r\n case 'boolean':\r\n if (hasOperators) {\r\n return 'Boolean search selected - query contains logical operators (AND/OR/NOT)';\r\n }\r\n return 'Boolean search selected for precise filtering';\r\n\r\n case 'fuzzy':\r\n if (hasWildcard) {\r\n return 'Fuzzy search selected - query contains wildcard patterns';\r\n }\r\n return 'Fuzzy search selected for typo-tolerant matching';\r\n\r\n case 'semantic':\r\n if (words >= 5) {\r\n return 'Semantic search selected - longer natural language query benefits from meaning-based matching';\r\n }\r\n return 'Semantic search selected for conceptual/meaning-based matching';\r\n }\r\n }\r\n\r\n // ==================== Phase 12 Sprint 4: Enhanced Features ====================\r\n\r\n /**\r\n * Calculate adaptive depth (k_dyn) based on query complexity.\r\n *\r\n * Formula: k_dyn = k_base × (1 + δ × C_q)\r\n * where:\r\n * - k_base: base number of results\r\n * - δ (delta): complexity scaling factor\r\n * - C_q: query complexity score (0-1)\r\n *\r\n * @param query - The search query\r\n * @param analysis - Optional query analysis for more accurate complexity\r\n * @returns Adaptive depth value\r\n */\r\n calculateAdaptiveDepth(query: string, analysis?: QueryAnalysis): number {\r\n const { kBase, delta, maxDepth } = this.adaptiveDepthConfig;\r\n\r\n // Calculate complexity score (0-1)\r\n const complexityScore = this.calculateComplexityScore(query, analysis);\r\n\r\n // Apply formula: k_dyn = k_base × (1 + δ × C_q)\r\n const kDyn = kBase * (1 + delta * complexityScore);\r\n\r\n // Round and cap at maxDepth\r\n return Math.min(Math.round(kDyn), maxDepth);\r\n }\r\n\r\n /**\r\n * Calculate query complexity score (0-1).\r\n * @private\r\n */\r\n private calculateComplexityScore(query: string, analysis?: QueryAnalysis): number {\r\n let score = 0;\r\n\r\n // Use analysis if provided\r\n if (analysis) {\r\n switch (analysis.complexity) {\r\n case 'low':\r\n score = 0.2;\r\n break;\r\n case 'medium':\r\n score = 0.5;\r\n break;\r\n case 'high':\r\n score = 0.8;\r\n break;\r\n }\r\n\r\n // Adjust for sub-queries\r\n if (analysis.subQueries && analysis.subQueries.length > 1) {\r\n score += 0.1 * (analysis.subQueries.length - 1);\r\n }\r\n\r\n // Adjust for temporal range (adds complexity)\r\n if (analysis.temporalRange) {\r\n score += 0.1;\r\n }\r\n\r\n // Adjust for entities (more entities = more complex)\r\n if (analysis.entities.length > 0) {\r\n score += 0.05 * Math.min(analysis.entities.length, 3);\r\n }\r\n } else {\r\n // Fallback to query-based estimation\r\n const words = query.trim().split(/\\s+/).length;\r\n const hasOperators = /\\b(AND|OR|NOT)\\b/.test(query);\r\n const hasMultipleClauses = /[,;]/.test(query);\r\n const hasConjunctions = /\\b(and|or|but|then|therefore)\\b/i.test(query);\r\n\r\n score = Math.min(words / 20, 0.5); // Base score from length\r\n if (hasOperators) score += 0.2;\r\n if (hasMultipleClauses) score += 0.15;\r\n if (hasConjunctions) score += 0.1;\r\n }\r\n\r\n // Clamp to 0-1\r\n return Math.max(0, Math.min(1, score));\r\n }\r\n\r\n /**\r\n * Estimate tokens for query and expected results.\r\n *\r\n * @param query - The search query\r\n * @param entityCount - Number of entities in the graph\r\n * @param expectedResults - Expected number of results (default: 10)\r\n * @returns Estimated token count\r\n */\r\n estimateTokens(query: string, entityCount: number, expectedResults = 10): number {\r\n const { charsPerToken, includeEntityCount } = this.tokenEstimationConfig;\r\n\r\n // Query tokens\r\n const queryTokens = Math.ceil(query.length / charsPerToken);\r\n\r\n // Result tokens (approximate average entity text size)\r\n const avgEntityTextSize = 200; // Conservative estimate\r\n const resultTokens = Math.ceil((avgEntityTextSize * expectedResults) / charsPerToken);\r\n\r\n // Add overhead for entity count if enabled\r\n let totalTokens = queryTokens + resultTokens;\r\n if (includeEntityCount) {\r\n // More entities = slightly more processing/context\r\n const entityOverhead = Math.ceil(Math.log10(entityCount + 1) * 10);\r\n totalTokens += entityOverhead;\r\n }\r\n\r\n return totalTokens;\r\n }\r\n\r\n /**\r\n * Recommend search layers based on query characteristics.\r\n *\r\n * @param query - The search query\r\n * @param options - Layer recommendation options\r\n * @returns Ordered array of recommended layers (fastest first)\r\n */\r\n recommendLayers(query: string, options: LayerRecommendationOptions = {}): SearchLayer[] {\r\n const {\r\n analysis,\r\n semanticAvailable = true,\r\n maxLayers = 3,\r\n } = options;\r\n\r\n const layers: Array<{ layer: SearchLayer; score: number; cost: number }> = [];\r\n\r\n // Score lexical layer\r\n const lexicalScore = this.scoreLexicalLayer(query, analysis);\r\n layers.push({\r\n layer: 'lexical',\r\n score: lexicalScore,\r\n cost: this.estimateLayerCost('lexical', query),\r\n });\r\n\r\n // Score symbolic layer\r\n const symbolicScore = this.scoreSymbolicLayer(query, analysis);\r\n layers.push({\r\n layer: 'symbolic',\r\n score: symbolicScore,\r\n cost: this.estimateLayerCost('symbolic', query),\r\n });\r\n\r\n // Score semantic layer (if available)\r\n if (semanticAvailable) {\r\n const semanticScore = this.scoreSemanticLayer(query, analysis);\r\n layers.push({\r\n layer: 'semantic',\r\n score: semanticScore,\r\n cost: this.estimateLayerCost('semantic', query),\r\n });\r\n }\r\n\r\n // Sort by score (higher is better), then by cost (lower is better)\r\n layers.sort((a, b) => {\r\n if (b.score !== a.score) return b.score - a.score;\r\n return a.cost - b.cost;\r\n });\r\n\r\n // Return up to maxLayers\r\n return layers.slice(0, maxLayers).map(l => l.layer);\r\n }\r\n\r\n /**\r\n * Estimate cost for a specific layer.\r\n * @private\r\n */\r\n private estimateLayerCost(layer: SearchLayer, query: string): number {\r\n const words = query.trim().split(/\\s+/).length;\r\n\r\n switch (layer) {\r\n case 'lexical':\r\n return 1 + words * 0.1; // Fast, scales with words\r\n case 'symbolic':\r\n return 0.5; // Very fast, constant time\r\n case 'semantic':\r\n return 5 + words * 0.5; // Slow, embedding computation\r\n }\r\n }\r\n\r\n /**\r\n * Score lexical layer for query.\r\n * @private\r\n */\r\n private scoreLexicalLayer(query: string, analysis?: QueryAnalysis): number {\r\n let score = 50;\r\n\r\n const words = query.trim().split(/\\s+/).length;\r\n const hasOperators = /\\b(AND|OR|NOT)\\b/.test(query);\r\n\r\n // Good for keyword queries\r\n if (words >= 2 && words <= 5) score += 20;\r\n if (!hasOperators) score += 10;\r\n\r\n // Check analysis for factual questions\r\n if (analysis?.questionType === 'factual') score += 15;\r\n if (analysis?.questionType === 'aggregation') score += 10;\r\n\r\n return score;\r\n }\r\n\r\n /**\r\n * Score symbolic layer for query.\r\n * @private\r\n */\r\n private scoreSymbolicLayer(query: string, analysis?: QueryAnalysis): number {\r\n let score = 40;\r\n\r\n // Good for temporal queries\r\n if (analysis?.temporalRange) score += 30;\r\n if (analysis?.questionType === 'temporal') score += 25;\r\n\r\n // Good for structured filtering\r\n const hasFilterKeywords = /\\b(type|tag|importance|date|created|modified)\\b/i.test(query);\r\n if (hasFilterKeywords) score += 20;\r\n\r\n // If analysis detected entities, might benefit from symbolic\r\n if (analysis?.entities && analysis.entities.length > 0) score += 10;\r\n\r\n return score;\r\n }\r\n\r\n /**\r\n * Score semantic layer for query.\r\n * @private\r\n */\r\n private scoreSemanticLayer(query: string, analysis?: QueryAnalysis): number {\r\n let score = 45;\r\n\r\n const words = query.trim().split(/\\s+/).length;\r\n const hasOperators = /\\b(AND|OR|NOT)\\b/.test(query);\r\n\r\n // Good for longer, natural language queries\r\n if (words >= 5) score += 25;\r\n if (!hasOperators) score += 10;\r\n\r\n // Good for conceptual questions\r\n if (analysis?.questionType === 'conceptual') score += 30;\r\n if (analysis?.questionType === 'comparative') score += 20;\r\n\r\n // High complexity benefits from semantic understanding\r\n if (analysis?.complexity === 'high') score += 15;\r\n\r\n return score;\r\n }\r\n\r\n /**\r\n * Get extended cost estimate with all Phase 12 features.\r\n *\r\n * @param method - The search method\r\n * @param query - The search query\r\n * @param entityCount - Number of entities in the graph\r\n * @param analysis - Optional query analysis\r\n * @returns Extended cost estimate\r\n */\r\n estimateExtended(\r\n method: SearchMethod,\r\n query: string,\r\n entityCount: number,\r\n analysis?: QueryAnalysis\r\n ): ExtendedQueryCostEstimate {\r\n const base = this.estimateMethod(method, query, entityCount);\r\n const adaptiveDepth = this.calculateAdaptiveDepth(query, analysis);\r\n const estimatedTokens = this.estimateTokens(query, entityCount, adaptiveDepth);\r\n const recommendedLayers = this.recommendLayers(query, {\r\n analysis,\r\n semanticAvailable: method === 'semantic' || true, // Assume available\r\n });\r\n\r\n const layerCosts: Record<SearchLayer, number> = {\r\n semantic: this.estimateLayerCost('semantic', query) * entityCount * 0.001,\r\n lexical: this.estimateLayerCost('lexical', query) * entityCount * 0.001,\r\n symbolic: this.estimateLayerCost('symbolic', query) * entityCount * 0.001,\r\n };\r\n\r\n return {\r\n ...base,\r\n recommendedLayers,\r\n estimatedTokens,\r\n adaptiveDepth,\r\n layerCosts,\r\n };\r\n }\r\n\r\n /**\r\n * Get layers sorted by estimated cost (fastest first).\r\n *\r\n * @param query - The search query\r\n * @param entityCount - Number of entities\r\n * @param semanticAvailable - Whether semantic search is available\r\n * @returns Layers sorted by cost\r\n */\r\n getLayersByCost(\r\n query: string,\r\n entityCount: number,\r\n semanticAvailable = true\r\n ): Array<{ layer: SearchLayer; estimatedMs: number }> {\r\n const layers: Array<{ layer: SearchLayer; estimatedMs: number }> = [\r\n {\r\n layer: 'symbolic',\r\n estimatedMs: this.estimateLayerCost('symbolic', query) * Math.log10(entityCount + 1),\r\n },\r\n {\r\n layer: 'lexical',\r\n estimatedMs: this.estimateLayerCost('lexical', query) * Math.sqrt(entityCount),\r\n },\r\n ];\r\n\r\n if (semanticAvailable) {\r\n layers.push({\r\n layer: 'semantic',\r\n estimatedMs: this.estimateLayerCost('semantic', query) * entityCount * 0.1,\r\n });\r\n }\r\n\r\n return layers.sort((a, b) => a.estimatedMs - b.estimatedMs);\r\n }\r\n}\r\n","/**\r\n * Search Manager\r\n *\r\n * Orchestrates all search types (basic, ranked, boolean, fuzzy).\r\n * Focused on search operations only (Phase 4: Consolidate God Objects).\r\n *\r\n * @module search/SearchManager\r\n */\r\n\r\nimport type { KnowledgeGraph, SearchResult, SavedSearch, AutoSearchResult, Entity, AccessContext } from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport { BasicSearch } from './BasicSearch.js';\r\nimport { RankedSearch } from './RankedSearch.js';\r\nimport { BooleanSearch } from './BooleanSearch.js';\r\nimport { FuzzySearch } from './FuzzySearch.js';\r\nimport { SearchSuggestions } from './SearchSuggestions.js';\r\nimport { SavedSearchManager } from './SavedSearchManager.js';\r\nimport { QueryCostEstimator } from './QueryCostEstimator.js';\r\nimport type { AccessTracker } from '../agent/AccessTracker.js';\r\n\r\n/**\r\n * Options for search methods with access tracking support.\r\n */\r\nexport interface SearchOptionsWithTracking {\r\n /** Enable access tracking for returned results */\r\n trackAccess?: boolean;\r\n /** Session ID for access context */\r\n sessionId?: string;\r\n /** Task ID for access context */\r\n taskId?: string;\r\n}\r\n\r\n/**\r\n * Unified search manager providing access to all search types.\r\n *\r\n * Phase 4 Sprint 5: Manages search caches across all search types.\r\n */\r\nexport class SearchManager {\r\n private basicSearch: BasicSearch;\r\n private rankedSearch: RankedSearch;\r\n private booleanSearcher: BooleanSearch;\r\n private fuzzySearcher: FuzzySearch;\r\n private searchSuggestions: SearchSuggestions;\r\n private savedSearchManager: SavedSearchManager;\r\n private storage: GraphStorage;\r\n private queryEstimator: QueryCostEstimator;\r\n private accessTracker?: AccessTracker;\r\n\r\n constructor(storage: GraphStorage, savedSearchesFilePath: string) {\r\n this.storage = storage;\r\n this.basicSearch = new BasicSearch(storage);\r\n this.rankedSearch = new RankedSearch(storage);\r\n this.booleanSearcher = new BooleanSearch(storage);\r\n this.fuzzySearcher = new FuzzySearch(storage);\r\n this.searchSuggestions = new SearchSuggestions(storage);\r\n this.savedSearchManager = new SavedSearchManager(savedSearchesFilePath, this.basicSearch);\r\n this.queryEstimator = new QueryCostEstimator();\r\n }\r\n\r\n /**\r\n * Set the AccessTracker for optional access tracking.\r\n * When set, search methods can track access to returned entities.\r\n *\r\n * @param tracker - AccessTracker instance\r\n */\r\n setAccessTracker(tracker: AccessTracker): void {\r\n this.accessTracker = tracker;\r\n }\r\n\r\n // ==================== Cache Management (Phase 4 Sprint 5) ====================\r\n\r\n /**\r\n * Phase 4 Sprint 5: Clear all search caches.\r\n *\r\n * Clears caches in all search types: fuzzy, boolean, and ranked token cache.\r\n * Call this when the graph has been modified to ensure fresh results.\r\n */\r\n clearAllCaches(): void {\r\n this.fuzzySearcher.clearCache();\r\n this.booleanSearcher.clearCache();\r\n this.rankedSearch.clearTokenCache();\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 5: Clear fuzzy search cache.\r\n */\r\n clearFuzzyCache(): void {\r\n this.fuzzySearcher.clearCache();\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 5: Clear boolean search cache.\r\n */\r\n clearBooleanCache(): void {\r\n this.booleanSearcher.clearCache();\r\n }\r\n\r\n /**\r\n * Phase 4 Sprint 5: Clear ranked search token cache.\r\n */\r\n clearRankedCache(): void {\r\n this.rankedSearch.clearTokenCache();\r\n }\r\n\r\n // ==================== Basic Search ====================\r\n\r\n /**\r\n * Perform a simple text-based search across entity names and observations.\r\n *\r\n * This is the primary search method that searches through entity names,\r\n * observations, and types using case-insensitive substring matching.\r\n * Optionally filter by tags and importance range.\r\n *\r\n * @param query - Text to search for (case-insensitive, searches names/observations/types)\r\n * @param tags - Optional array of tags to filter results (lowercase)\r\n * @param minImportance - Optional minimum importance value (0-10)\r\n * @param maxImportance - Optional maximum importance value (0-10)\r\n * @returns KnowledgeGraph containing matching entities and their relations\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new SearchManager(storage, savedSearchesPath);\r\n *\r\n * // Simple text search\r\n * const results = await manager.searchNodes('Alice');\r\n *\r\n * // Search with tag filter\r\n * const engineeringResults = await manager.searchNodes('project', ['engineering']);\r\n *\r\n * // Search with importance range\r\n * const importantResults = await manager.searchNodes('critical', undefined, 8, 10);\r\n *\r\n * // Combined filters\r\n * const filtered = await manager.searchNodes('bug', ['backend'], 5, 10);\r\n *\r\n * // Search with access tracking enabled\r\n * const tracked = await manager.searchNodes('Alice', undefined, undefined, undefined, {\r\n * trackAccess: true,\r\n * sessionId: 'session_123'\r\n * });\r\n * ```\r\n */\r\n async searchNodes(\r\n query: string,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n options?: SearchOptionsWithTracking\r\n ): Promise<KnowledgeGraph> {\r\n const result = await this.basicSearch.searchNodes(query, tags, minImportance, maxImportance);\r\n\r\n // Track access if enabled\r\n if (options?.trackAccess && this.accessTracker) {\r\n await this.trackSearchResults(result.entities, query, options);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Track access for search results.\r\n * @internal\r\n */\r\n private async trackSearchResults(\r\n entities: Entity[],\r\n query: string,\r\n options: SearchOptionsWithTracking\r\n ): Promise<void> {\r\n if (!this.accessTracker || entities.length === 0) return;\r\n\r\n const context: AccessContext = {\r\n queryContext: query,\r\n sessionId: options.sessionId,\r\n taskId: options.taskId,\r\n retrievalMethod: 'search',\r\n };\r\n\r\n // Batch record all results\r\n await Promise.all(\r\n entities.map((entity) => this.accessTracker!.recordAccess(entity.name, context))\r\n );\r\n }\r\n\r\n /**\r\n * Open specific nodes by name.\r\n *\r\n * @param names - Array of entity names\r\n * @returns Knowledge graph with specified entities\r\n */\r\n async openNodes(names: string[]): Promise<KnowledgeGraph> {\r\n return this.basicSearch.openNodes(names);\r\n }\r\n\r\n /**\r\n * Search by date range.\r\n *\r\n * @param startDate - Optional start date (ISO 8601)\r\n * @param endDate - Optional end date (ISO 8601)\r\n * @param entityType - Optional entity type filter\r\n * @param tags - Optional tags filter\r\n * @returns Filtered knowledge graph\r\n */\r\n async searchByDateRange(\r\n startDate?: string,\r\n endDate?: string,\r\n entityType?: string,\r\n tags?: string[]\r\n ): Promise<KnowledgeGraph> {\r\n return this.basicSearch.searchByDateRange(startDate, endDate, entityType, tags);\r\n }\r\n\r\n // ==================== Ranked Search ====================\r\n\r\n /**\r\n * Perform TF-IDF ranked search with relevance scoring.\r\n *\r\n * Uses Term Frequency-Inverse Document Frequency algorithm to rank results\r\n * by relevance to the query. Results are sorted by score (highest first).\r\n * This is ideal for finding the most relevant entities for a search query.\r\n *\r\n * @param query - Search query (analyzed for term frequency)\r\n * @param tags - Optional array of tags to filter results (lowercase)\r\n * @param minImportance - Optional minimum importance value (0-10)\r\n * @param maxImportance - Optional maximum importance value (0-10)\r\n * @param limit - Maximum number of results to return (default: 50, max: 200)\r\n * @returns Array of SearchResult objects sorted by relevance score (descending)\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new SearchManager(storage, savedSearchesPath);\r\n *\r\n * // Basic ranked search\r\n * const results = await manager.searchNodesRanked('machine learning algorithms');\r\n * results.forEach(r => {\r\n * console.log(`${r.entity.name} (score: ${r.score})`);\r\n * });\r\n *\r\n * // Limit to top 10 most relevant results\r\n * const top10 = await manager.searchNodesRanked('database optimization', undefined, undefined, undefined, 10);\r\n *\r\n * // Ranked search with filters\r\n * const relevantImportant = await manager.searchNodesRanked(\r\n * 'security vulnerability',\r\n * ['security', 'critical'],\r\n * 8,\r\n * 10,\r\n * 20\r\n * );\r\n * ```\r\n */\r\n async searchNodesRanked(\r\n query: string,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number,\r\n limit?: number\r\n ): Promise<SearchResult[]> {\r\n return this.rankedSearch.searchNodesRanked(query, tags, minImportance, maxImportance, limit);\r\n }\r\n\r\n // ==================== Boolean Search ====================\r\n\r\n /**\r\n * Perform boolean search with AND, OR, NOT operators.\r\n *\r\n * Supports complex boolean logic for precise search queries.\r\n * Use AND/OR/NOT operators (case-insensitive) to combine search terms.\r\n * Parentheses are supported for grouping.\r\n *\r\n * @param query - Boolean query string (e.g., \"alice AND bob\", \"frontend OR backend NOT legacy\")\r\n * @param tags - Optional array of tags to filter results (lowercase)\r\n * @param minImportance - Optional minimum importance value (0-10)\r\n * @param maxImportance - Optional maximum importance value (0-10)\r\n * @returns KnowledgeGraph containing entities matching the boolean expression\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new SearchManager(storage, savedSearchesPath);\r\n *\r\n * // AND operator - entities matching all terms\r\n * const both = await manager.booleanSearch('database AND performance');\r\n *\r\n * // OR operator - entities matching any term\r\n * const either = await manager.booleanSearch('frontend OR backend');\r\n *\r\n * // NOT operator - exclude terms\r\n * const excluding = await manager.booleanSearch('API NOT deprecated');\r\n *\r\n * // Complex queries with grouping\r\n * const complex = await manager.booleanSearch('(react OR vue) AND (component OR hook) NOT legacy');\r\n * ```\r\n */\r\n async booleanSearch(\r\n query: string,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number\r\n ): Promise<KnowledgeGraph> {\r\n return this.booleanSearcher.booleanSearch(query, tags, minImportance, maxImportance);\r\n }\r\n\r\n // ==================== Fuzzy Search ====================\r\n\r\n /**\r\n * Perform fuzzy search with typo tolerance.\r\n *\r\n * Uses Levenshtein distance to find entities that approximately match the query,\r\n * making it ideal for handling typos and variations in spelling.\r\n * Higher threshold values require closer matches.\r\n *\r\n * @param query - Search query (will match approximate spellings)\r\n * @param threshold - Similarity threshold from 0.0 (very lenient) to 1.0 (exact match). Default: 0.7\r\n * @param tags - Optional array of tags to filter results (lowercase)\r\n * @param minImportance - Optional minimum importance value (0-10)\r\n * @param maxImportance - Optional maximum importance value (0-10)\r\n * @returns KnowledgeGraph containing entities with similar names/observations\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new SearchManager(storage, savedSearchesPath);\r\n *\r\n * // Find entities even with typos\r\n * const results = await manager.fuzzySearch('databse'); // Will match \"database\"\r\n *\r\n * // Adjust threshold for strictness\r\n * const strict = await manager.fuzzySearch('optmization', 0.9); // Requires very close match\r\n * const lenient = await manager.fuzzySearch('optmization', 0.6); // More tolerant of differences\r\n *\r\n * // Fuzzy search with filters\r\n * const filtered = await manager.fuzzySearch('secrity', 0.7, ['important'], 7, 10);\r\n * ```\r\n */\r\n async fuzzySearch(\r\n query: string,\r\n threshold?: number,\r\n tags?: string[],\r\n minImportance?: number,\r\n maxImportance?: number\r\n ): Promise<KnowledgeGraph> {\r\n return this.fuzzySearcher.fuzzySearch(query, threshold, tags, minImportance, maxImportance);\r\n }\r\n\r\n // ==================== Search Suggestions ====================\r\n\r\n /**\r\n * Get search suggestions for a query.\r\n *\r\n * @param query - Search query\r\n * @param maxSuggestions - Maximum suggestions to return\r\n * @returns Array of suggested terms\r\n */\r\n async getSearchSuggestions(query: string, maxSuggestions?: number): Promise<string[]> {\r\n return this.searchSuggestions.getSearchSuggestions(query, maxSuggestions);\r\n }\r\n\r\n // ==================== Saved Searches ====================\r\n\r\n /**\r\n * Save a search query for later reuse.\r\n *\r\n * Saved searches store query parameters and can be re-executed later.\r\n * The system tracks usage count and last used timestamp automatically.\r\n *\r\n * @param search - Search parameters (name, query, and optional filters)\r\n * @returns Newly created SavedSearch object with metadata\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new SearchManager(storage, savedSearchesPath);\r\n *\r\n * // Save a simple search\r\n * const saved = await manager.saveSearch({\r\n * name: 'High Priority Bugs',\r\n * query: 'bug',\r\n * tags: ['critical'],\r\n * minImportance: 8\r\n * });\r\n *\r\n * // Save a complex search\r\n * await manager.saveSearch({\r\n * name: 'Recent Frontend Work',\r\n * query: 'component OR hook',\r\n * tags: ['frontend', 'react'],\r\n * searchType: 'boolean'\r\n * });\r\n * ```\r\n */\r\n async saveSearch(\r\n search: Omit<SavedSearch, 'createdAt' | 'useCount' | 'lastUsed'>\r\n ): Promise<SavedSearch> {\r\n return this.savedSearchManager.saveSearch(search);\r\n }\r\n\r\n /**\r\n * List all saved searches.\r\n *\r\n * @returns Array of saved searches\r\n */\r\n async listSavedSearches(): Promise<SavedSearch[]> {\r\n return this.savedSearchManager.listSavedSearches();\r\n }\r\n\r\n /**\r\n * Get a saved search by name.\r\n *\r\n * @param name - Search name\r\n * @returns Saved search or null\r\n */\r\n async getSavedSearch(name: string): Promise<SavedSearch | null> {\r\n return this.savedSearchManager.getSavedSearch(name);\r\n }\r\n\r\n /**\r\n * Execute a saved search by name.\r\n *\r\n * Runs a previously saved search with its stored parameters.\r\n * Automatically updates the search's useCount and lastUsed timestamp.\r\n *\r\n * @param name - The unique name of the saved search to execute\r\n * @returns KnowledgeGraph containing the search results\r\n * @throws Error if saved search not found\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new SearchManager(storage, savedSearchesPath);\r\n *\r\n * // Execute a saved search\r\n * const results = await manager.executeSavedSearch('High Priority Bugs');\r\n * console.log(`Found ${results.entities.length} high priority bugs`);\r\n *\r\n * // Handle missing saved search\r\n * try {\r\n * await manager.executeSavedSearch('NonExistent');\r\n * } catch (error) {\r\n * console.error('Search not found');\r\n * }\r\n * ```\r\n */\r\n async executeSavedSearch(name: string): Promise<KnowledgeGraph> {\r\n return this.savedSearchManager.executeSavedSearch(name);\r\n }\r\n\r\n /**\r\n * Delete a saved search.\r\n *\r\n * @param name - Search name\r\n * @returns True if deleted\r\n */\r\n async deleteSavedSearch(name: string): Promise<boolean> {\r\n return this.savedSearchManager.deleteSavedSearch(name);\r\n }\r\n\r\n /**\r\n * Update a saved search.\r\n *\r\n * @param name - Search name\r\n * @param updates - Fields to update\r\n * @returns Updated saved search\r\n */\r\n async updateSavedSearch(\r\n name: string,\r\n updates: Partial<Omit<SavedSearch, 'name' | 'createdAt' | 'useCount' | 'lastUsed'>>\r\n ): Promise<SavedSearch> {\r\n return this.savedSearchManager.updateSavedSearch(name, updates);\r\n }\r\n\r\n // ==================== Phase 10 Sprint 4: Automatic Search ====================\r\n\r\n /**\r\n * Phase 10 Sprint 4: Automatically select and execute the best search method.\r\n *\r\n * Analyzes the query and graph size to determine the optimal search method,\r\n * then executes it and returns both the results and the selection reasoning.\r\n *\r\n * @param query - The search query\r\n * @param limit - Maximum number of results (default: 10)\r\n * @returns AutoSearchResult with selected method, results, and estimates\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new SearchManager(storage, savedSearchesPath);\r\n *\r\n * // Let the system choose the best search method\r\n * const result = await manager.autoSearch('software engineer skills');\r\n *\r\n * console.log(`Used ${result.selectedMethod} because: ${result.selectionReason}`);\r\n * console.log(`Found ${result.results.length} results in ${result.executionTimeMs}ms`);\r\n * ```\r\n */\r\n async autoSearch(query: string, limit: number = 10): Promise<AutoSearchResult> {\r\n const startTime = Date.now();\r\n\r\n // Get entity count from graph\r\n const graph = await this.storage.loadGraph();\r\n const entityCount = graph.entities.length;\r\n\r\n // Get cost estimates for all methods\r\n const estimates = this.queryEstimator.estimateAllMethods(query, entityCount);\r\n\r\n // Get the recommended method\r\n const recommendation = this.queryEstimator.recommendMethod(query, entityCount);\r\n const selectedMethod = recommendation.method;\r\n const selectionReason = recommendation.reason;\r\n\r\n // Execute the selected search method\r\n let results: SearchResult[];\r\n\r\n switch (selectedMethod) {\r\n case 'basic': {\r\n const basicResult = await this.basicSearch.searchNodes(query);\r\n results = basicResult.entities.map((e: Entity, idx: number) => ({\r\n entity: e,\r\n score: 1.0 - idx * 0.01, // Rank by position\r\n matchedFields: { name: true, observations: e.observations },\r\n }));\r\n break;\r\n }\r\n\r\n case 'ranked': {\r\n results = await this.rankedSearch.searchNodesRanked(query, undefined, undefined, undefined, limit);\r\n break;\r\n }\r\n\r\n case 'boolean': {\r\n const booleanResult = await this.booleanSearcher.booleanSearch(query);\r\n results = booleanResult.entities.map((e: Entity, idx: number) => ({\r\n entity: e,\r\n score: 1.0 - idx * 0.01, // Rank by position\r\n matchedFields: { name: true, observations: e.observations },\r\n }));\r\n break;\r\n }\r\n\r\n case 'fuzzy': {\r\n const fuzzyResult = await this.fuzzySearcher.fuzzySearch(query);\r\n results = fuzzyResult.entities.map((e: Entity, idx: number) => ({\r\n entity: e,\r\n score: 1.0 - idx * 0.01, // Rank by position\r\n matchedFields: { name: true, observations: e.observations },\r\n }));\r\n break;\r\n }\r\n\r\n case 'semantic': {\r\n // Semantic search not available through SearchManager\r\n // Fall back to ranked search\r\n results = await this.rankedSearch.searchNodesRanked(query, undefined, undefined, undefined, limit);\r\n break;\r\n }\r\n\r\n default: {\r\n const _exhaustiveCheck: never = selectedMethod;\r\n throw new Error(`Unknown search method: ${_exhaustiveCheck}`);\r\n }\r\n }\r\n\r\n // Limit results\r\n const limitedResults = results.slice(0, limit);\r\n\r\n return {\r\n selectedMethod,\r\n selectionReason,\r\n estimates,\r\n results: limitedResults,\r\n executionTimeMs: Date.now() - startTime,\r\n };\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 4: Get cost estimates for all search methods.\r\n *\r\n * Useful for clients that want to display cost information or\r\n * make their own method selection decisions.\r\n *\r\n * @param query - The search query\r\n * @returns Array of cost estimates for all methods\r\n */\r\n async getSearchCostEstimates(query: string): Promise<import('../types/index.js').QueryCostEstimate[]> {\r\n const graph = await this.storage.loadGraph();\r\n const entityCount = graph.entities.length;\r\n return this.queryEstimator.estimateAllMethods(query, entityCount);\r\n }\r\n\r\n /**\r\n * Phase 10 Sprint 4: Get the query cost estimator instance.\r\n *\r\n * @returns The QueryCostEstimator instance\r\n */\r\n getQueryEstimator(): QueryCostEstimator {\r\n return this.queryEstimator;\r\n }\r\n}\r\n","/**\r\n * Embedding Service\r\n *\r\n * Phase 4 Sprint 10: Provides embedding abstractions for semantic search.\r\n * Phase 12 Sprint 5: Added query/document prefixes, l2Normalize, mode parameter,\r\n * and batch embedding with progress callback.\r\n * Supports multiple providers: OpenAI (cloud) and local (transformers.js).\r\n *\r\n * @module search/EmbeddingService\r\n */\r\n\r\nimport type { EmbeddingService, EmbeddingConfig, EmbeddingMode } from '../types/index.js';\r\nimport {\r\n EMBEDDING_DEFAULTS,\r\n OPENAI_API_CONFIG,\r\n getEmbeddingConfig,\r\n} from '../utils/constants.js';\r\n\r\n/**\r\n * Phase 12 Sprint 5: Prefixes for query-optimized embedding encoding.\r\n *\r\n * These prefixes are used to distinguish between query and document embeddings,\r\n * which can improve retrieval performance with asymmetric embedding models.\r\n */\r\nexport const QUERY_PREFIX = 'query: ';\r\nexport const DOCUMENT_PREFIX = 'passage: ';\r\n\r\n/**\r\n * Phase 12 Sprint 5: Callback for batch embedding progress.\r\n */\r\nexport type EmbeddingProgressCallback = (progress: {\r\n current: number;\r\n total: number;\r\n percentage: number;\r\n}) => void;\r\n\r\n/**\r\n * Phase 12 Sprint 5: L2 normalize a vector for cosine similarity.\r\n *\r\n * Normalizes a vector to unit length (magnitude 1), which is required\r\n * for accurate cosine similarity calculations.\r\n *\r\n * @param vector - Input vector to normalize\r\n * @returns L2 normalized vector\r\n *\r\n * @example\r\n * ```typescript\r\n * const normalized = l2Normalize([3, 4]); // [0.6, 0.8]\r\n * ```\r\n */\r\nexport function l2Normalize(vector: number[]): number[] {\r\n let magnitude = 0;\r\n for (const v of vector) {\r\n magnitude += v * v;\r\n }\r\n magnitude = Math.sqrt(magnitude);\r\n\r\n if (magnitude === 0 || magnitude === 1) {\r\n return magnitude === 0 ? vector : vector.slice();\r\n }\r\n\r\n return vector.map(v => v / magnitude);\r\n}\r\n\r\n/**\r\n * OpenAI Embedding Service\r\n *\r\n * Uses OpenAI's text-embedding-3-small model for generating embeddings.\r\n * Supports single and batch embedding with rate limit handling.\r\n * Phase 12 Sprint 5: Added mode parameter and progress callback support.\r\n *\r\n * @example\r\n * ```typescript\r\n * const service = new OpenAIEmbeddingService('sk-...');\r\n * const embedding = await service.embed(\"Hello world\", 'query');\r\n * console.log(`Generated ${embedding.length} dimensions`);\r\n * ```\r\n */\r\nexport class OpenAIEmbeddingService implements EmbeddingService {\r\n readonly dimensions: number;\r\n readonly provider = 'openai';\r\n readonly model: string;\r\n private apiKey: string;\r\n\r\n /**\r\n * Create an OpenAI embedding service.\r\n *\r\n * @param apiKey - OpenAI API key\r\n * @param model - Optional model override (default: text-embedding-3-small)\r\n */\r\n constructor(apiKey: string, model?: string) {\r\n if (!apiKey) {\r\n throw new Error('OpenAI API key is required');\r\n }\r\n this.apiKey = apiKey;\r\n this.model = model || EMBEDDING_DEFAULTS.OPENAI_MODEL;\r\n this.dimensions = EMBEDDING_DEFAULTS.OPENAI_DIMENSIONS;\r\n }\r\n\r\n /**\r\n * Check if the service is ready.\r\n */\r\n async isReady(): Promise<boolean> {\r\n return !!this.apiKey;\r\n }\r\n\r\n /**\r\n * Apply prefix to text based on embedding mode.\r\n *\r\n * @param text - Original text\r\n * @param mode - Embedding mode ('query' or 'document')\r\n * @returns Text with appropriate prefix\r\n */\r\n private applyPrefix(text: string, mode: EmbeddingMode = 'document'): string {\r\n return mode === 'query' ? `${QUERY_PREFIX}${text}` : `${DOCUMENT_PREFIX}${text}`;\r\n }\r\n\r\n /**\r\n * Generate embedding for a single text.\r\n *\r\n * @param text - Text to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @returns Embedding vector\r\n */\r\n async embed(text: string, mode: EmbeddingMode = 'document'): Promise<number[]> {\r\n const results = await this.embedBatch([text], mode);\r\n return results[0];\r\n }\r\n\r\n /**\r\n * Generate embeddings for multiple texts in batch.\r\n *\r\n * @param texts - Array of texts to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @returns Array of embedding vectors\r\n */\r\n async embedBatch(texts: string[], mode: EmbeddingMode = 'document'): Promise<number[][]> {\r\n if (texts.length === 0) {\r\n return [];\r\n }\r\n\r\n // Apply prefix based on mode\r\n const prefixedTexts = texts.map(text => this.applyPrefix(text, mode));\r\n\r\n // Split into batches if needed\r\n const maxBatchSize = EMBEDDING_DEFAULTS.OPENAI_MAX_BATCH_SIZE;\r\n const results: number[][] = [];\r\n\r\n for (let i = 0; i < prefixedTexts.length; i += maxBatchSize) {\r\n const batch = prefixedTexts.slice(i, i + maxBatchSize);\r\n const batchResults = await this.embedBatchInternal(batch);\r\n results.push(...batchResults);\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Generate embeddings with progress callback support.\r\n *\r\n * Phase 12 Sprint 5: Added for tracking progress on large batch operations.\r\n *\r\n * @param texts - Array of texts to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @param onProgress - Optional progress callback\r\n * @returns Array of embedding vectors\r\n */\r\n async embedBatchWithProgress(\r\n texts: string[],\r\n mode: EmbeddingMode = 'document',\r\n onProgress?: EmbeddingProgressCallback\r\n ): Promise<number[][]> {\r\n if (texts.length === 0) {\r\n return [];\r\n }\r\n\r\n // Apply prefix based on mode\r\n const prefixedTexts = texts.map(text => this.applyPrefix(text, mode));\r\n\r\n // Split into batches if needed\r\n const maxBatchSize = EMBEDDING_DEFAULTS.OPENAI_MAX_BATCH_SIZE;\r\n const results: number[][] = [];\r\n const total = prefixedTexts.length;\r\n\r\n for (let i = 0; i < prefixedTexts.length; i += maxBatchSize) {\r\n const batch = prefixedTexts.slice(i, i + maxBatchSize);\r\n const batchResults = await this.embedBatchInternal(batch);\r\n results.push(...batchResults);\r\n\r\n // Report progress\r\n if (onProgress) {\r\n const current = Math.min(i + maxBatchSize, total);\r\n onProgress({\r\n current,\r\n total,\r\n percentage: Math.round((current / total) * 100),\r\n });\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Internal batch embedding with retry logic.\r\n */\r\n private async embedBatchInternal(texts: string[]): Promise<number[][]> {\r\n let lastError: Error | null = null;\r\n let backoff = OPENAI_API_CONFIG.INITIAL_BACKOFF_MS;\r\n\r\n for (let attempt = 0; attempt <= OPENAI_API_CONFIG.MAX_RETRIES; attempt++) {\r\n try {\r\n const response = await fetch(\r\n `${OPENAI_API_CONFIG.BASE_URL}${OPENAI_API_CONFIG.EMBEDDINGS_ENDPOINT}`,\r\n {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'Authorization': `Bearer ${this.apiKey}`,\r\n },\r\n body: JSON.stringify({\r\n model: this.model,\r\n input: texts,\r\n }),\r\n }\r\n );\r\n\r\n if (!response.ok) {\r\n const errorBody = await response.text();\r\n\r\n // Handle rate limiting\r\n if (response.status === 429) {\r\n if (attempt < OPENAI_API_CONFIG.MAX_RETRIES) {\r\n await this.sleep(backoff);\r\n backoff = Math.min(backoff * 2, OPENAI_API_CONFIG.MAX_BACKOFF_MS);\r\n continue;\r\n }\r\n }\r\n\r\n throw new Error(`OpenAI API error: ${response.status} - ${errorBody}`);\r\n }\r\n\r\n const data = await response.json() as OpenAIEmbeddingResponse;\r\n\r\n // Sort by index to ensure correct order\r\n const sortedData = [...data.data].sort((a, b) => a.index - b.index);\r\n return sortedData.map(item => item.embedding);\r\n } catch (error) {\r\n lastError = error instanceof Error ? error : new Error(String(error));\r\n\r\n // Retry on network errors\r\n if (attempt < OPENAI_API_CONFIG.MAX_RETRIES && this.isRetryableError(error)) {\r\n await this.sleep(backoff);\r\n backoff = Math.min(backoff * 2, OPENAI_API_CONFIG.MAX_BACKOFF_MS);\r\n continue;\r\n }\r\n\r\n throw lastError;\r\n }\r\n }\r\n\r\n throw lastError || new Error('Failed to generate embeddings after retries');\r\n }\r\n\r\n /**\r\n * Check if an error is retryable.\r\n */\r\n private isRetryableError(error: unknown): boolean {\r\n if (error instanceof Error) {\r\n // Network errors and rate limits are retryable\r\n return error.message.includes('fetch') ||\r\n error.message.includes('network') ||\r\n error.message.includes('429');\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Sleep for a given duration.\r\n */\r\n private sleep(ms: number): Promise<void> {\r\n return new Promise(resolve => setTimeout(resolve, ms));\r\n }\r\n}\r\n\r\n/**\r\n * OpenAI API response type for embeddings.\r\n */\r\ninterface OpenAIEmbeddingResponse {\r\n object: string;\r\n data: Array<{\r\n object: string;\r\n embedding: number[];\r\n index: number;\r\n }>;\r\n model: string;\r\n usage: {\r\n prompt_tokens: number;\r\n total_tokens: number;\r\n };\r\n}\r\n\r\n/**\r\n * Local Embedding Service\r\n *\r\n * Uses @xenova/transformers for local embedding generation.\r\n * No API calls needed - runs entirely offline after initial model download.\r\n * Phase 12 Sprint 5: Added mode parameter and progress callback support.\r\n *\r\n * Note: Requires @xenova/transformers to be installed as an optional dependency.\r\n * If not available, initialization will fail gracefully.\r\n *\r\n * @example\r\n * ```typescript\r\n * const service = new LocalEmbeddingService();\r\n * await service.initialize();\r\n * const embedding = await service.embed(\"Hello world\", 'query');\r\n * ```\r\n */\r\nexport class LocalEmbeddingService implements EmbeddingService {\r\n readonly dimensions: number = EMBEDDING_DEFAULTS.LOCAL_DIMENSIONS;\r\n readonly provider = 'local';\r\n readonly model: string;\r\n\r\n private pipeline: unknown = null;\r\n private initialized = false;\r\n private initPromise: Promise<void> | null = null;\r\n\r\n /**\r\n * Create a local embedding service.\r\n *\r\n * @param model - Optional model override (default: Xenova/all-MiniLM-L6-v2)\r\n */\r\n constructor(model?: string) {\r\n this.model = model || EMBEDDING_DEFAULTS.LOCAL_MODEL;\r\n }\r\n\r\n /**\r\n * Initialize the model pipeline.\r\n * Must be called before using embed/embedBatch.\r\n */\r\n async initialize(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n if (this.initPromise) {\r\n return this.initPromise;\r\n }\r\n\r\n this.initPromise = this.initializeInternal();\r\n return this.initPromise;\r\n }\r\n\r\n /**\r\n * Internal initialization.\r\n */\r\n private async initializeInternal(): Promise<void> {\r\n try {\r\n // Dynamic import to allow optional dependency\r\n // @ts-expect-error - @xenova/transformers is an optional peer dependency\r\n const transformers = await import('@xenova/transformers');\r\n const { pipeline } = transformers;\r\n\r\n this.pipeline = await pipeline('feature-extraction', this.model);\r\n this.initialized = true;\r\n } catch (error) {\r\n this.initPromise = null;\r\n throw new Error(\r\n `Failed to initialize local embedding service: ${error instanceof Error ? error.message : String(error)}. ` +\r\n 'Make sure @xenova/transformers is installed.'\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Check if the service is ready.\r\n */\r\n async isReady(): Promise<boolean> {\r\n if (!this.initialized && !this.initPromise) {\r\n try {\r\n await this.initialize();\r\n } catch {\r\n return false;\r\n }\r\n }\r\n return this.initialized;\r\n }\r\n\r\n /**\r\n * Apply prefix to text based on embedding mode.\r\n *\r\n * @param text - Original text\r\n * @param mode - Embedding mode ('query' or 'document')\r\n * @returns Text with appropriate prefix\r\n */\r\n private applyPrefix(text: string, mode: EmbeddingMode = 'document'): string {\r\n return mode === 'query' ? `${QUERY_PREFIX}${text}` : `${DOCUMENT_PREFIX}${text}`;\r\n }\r\n\r\n /**\r\n * Generate embedding for a single text.\r\n *\r\n * @param text - Text to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @returns Embedding vector\r\n */\r\n async embed(text: string, mode: EmbeddingMode = 'document'): Promise<number[]> {\r\n await this.ensureInitialized();\r\n\r\n const prefixedText = this.applyPrefix(text, mode);\r\n const pipelineFn = this.pipeline as (text: string, options: { pooling: string; normalize: boolean }) => Promise<{ data: Float32Array }>;\r\n const output = await pipelineFn(prefixedText, { pooling: 'mean', normalize: true });\r\n\r\n return Array.from(output.data);\r\n }\r\n\r\n /**\r\n * Generate embeddings for multiple texts in batch.\r\n * Note: Local processing is done sequentially to avoid memory issues.\r\n *\r\n * @param texts - Array of texts to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @returns Array of embedding vectors\r\n */\r\n async embedBatch(texts: string[], mode: EmbeddingMode = 'document'): Promise<number[][]> {\r\n await this.ensureInitialized();\r\n\r\n const results: number[][] = [];\r\n for (const text of texts) {\r\n const embedding = await this.embed(text, mode);\r\n results.push(embedding);\r\n }\r\n return results;\r\n }\r\n\r\n /**\r\n * Generate embeddings with progress callback support.\r\n *\r\n * Phase 12 Sprint 5: Added for tracking progress on large batch operations.\r\n *\r\n * @param texts - Array of texts to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @param onProgress - Optional progress callback\r\n * @returns Array of embedding vectors\r\n */\r\n async embedBatchWithProgress(\r\n texts: string[],\r\n mode: EmbeddingMode = 'document',\r\n onProgress?: EmbeddingProgressCallback\r\n ): Promise<number[][]> {\r\n await this.ensureInitialized();\r\n\r\n const results: number[][] = [];\r\n const total = texts.length;\r\n\r\n for (let i = 0; i < texts.length; i++) {\r\n const embedding = await this.embed(texts[i], mode);\r\n results.push(embedding);\r\n\r\n // Report progress\r\n if (onProgress) {\r\n const current = i + 1;\r\n onProgress({\r\n current,\r\n total,\r\n percentage: Math.round((current / total) * 100),\r\n });\r\n }\r\n }\r\n return results;\r\n }\r\n\r\n /**\r\n * Ensure the service is initialized.\r\n */\r\n private async ensureInitialized(): Promise<void> {\r\n if (!this.initialized) {\r\n await this.initialize();\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Mock Embedding Service for testing\r\n *\r\n * Generates deterministic mock embeddings for testing purposes.\r\n * Useful for unit tests that don't need real embeddings.\r\n * Phase 12 Sprint 5: Added mode parameter and progress callback support.\r\n */\r\nexport class MockEmbeddingService implements EmbeddingService {\r\n readonly dimensions: number;\r\n readonly provider = 'mock';\r\n readonly model = 'mock-model';\r\n\r\n /**\r\n * Create a mock embedding service.\r\n *\r\n * @param dimensions - Number of dimensions for mock embeddings\r\n */\r\n constructor(dimensions: number = 384) {\r\n this.dimensions = dimensions;\r\n }\r\n\r\n /**\r\n * Check if the service is ready.\r\n */\r\n async isReady(): Promise<boolean> {\r\n return true;\r\n }\r\n\r\n /**\r\n * Apply prefix to text based on embedding mode.\r\n *\r\n * @param text - Original text\r\n * @param mode - Embedding mode ('query' or 'document')\r\n * @returns Text with appropriate prefix\r\n */\r\n private applyPrefix(text: string, mode: EmbeddingMode = 'document'): string {\r\n return mode === 'query' ? `${QUERY_PREFIX}${text}` : `${DOCUMENT_PREFIX}${text}`;\r\n }\r\n\r\n /**\r\n * Generate a deterministic mock embedding for a text.\r\n *\r\n * @param text - Text to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @returns Mock embedding vector\r\n */\r\n async embed(text: string, mode: EmbeddingMode = 'document'): Promise<number[]> {\r\n // Apply prefix based on mode (affects hash for different embeddings per mode)\r\n const prefixedText = this.applyPrefix(text, mode);\r\n\r\n // Generate deterministic embedding based on text hash\r\n const hash = this.hashString(prefixedText);\r\n const embedding: number[] = [];\r\n\r\n for (let i = 0; i < this.dimensions; i++) {\r\n // Use hash and index to generate deterministic values\r\n const value = Math.sin(hash + i * 0.1) * 0.5;\r\n embedding.push(value);\r\n }\r\n\r\n // Normalize the vector\r\n return this.normalize(embedding);\r\n }\r\n\r\n /**\r\n * Generate mock embeddings for multiple texts.\r\n *\r\n * @param texts - Array of texts to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @returns Array of mock embedding vectors\r\n */\r\n async embedBatch(texts: string[], mode: EmbeddingMode = 'document'): Promise<number[][]> {\r\n return Promise.all(texts.map(text => this.embed(text, mode)));\r\n }\r\n\r\n /**\r\n * Generate mock embeddings with progress callback support.\r\n *\r\n * Phase 12 Sprint 5: Added for tracking progress on large batch operations.\r\n *\r\n * @param texts - Array of texts to embed\r\n * @param mode - Embedding mode ('query' or 'document', default: 'document')\r\n * @param onProgress - Optional progress callback\r\n * @returns Array of mock embedding vectors\r\n */\r\n async embedBatchWithProgress(\r\n texts: string[],\r\n mode: EmbeddingMode = 'document',\r\n onProgress?: EmbeddingProgressCallback\r\n ): Promise<number[][]> {\r\n const results: number[][] = [];\r\n const total = texts.length;\r\n\r\n for (let i = 0; i < texts.length; i++) {\r\n const embedding = await this.embed(texts[i], mode);\r\n results.push(embedding);\r\n\r\n // Report progress\r\n if (onProgress) {\r\n const current = i + 1;\r\n onProgress({\r\n current,\r\n total,\r\n percentage: Math.round((current / total) * 100),\r\n });\r\n }\r\n }\r\n return results;\r\n }\r\n\r\n /**\r\n * Simple string hash function.\r\n */\r\n private hashString(str: string): number {\r\n let hash = 0;\r\n for (let i = 0; i < str.length; i++) {\r\n const char = str.charCodeAt(i);\r\n hash = ((hash << 5) - hash) + char;\r\n hash = hash & hash; // Convert to 32bit integer\r\n }\r\n return hash;\r\n }\r\n\r\n /**\r\n * Normalize a vector to unit length.\r\n */\r\n private normalize(vector: number[]): number[] {\r\n let magnitude = 0;\r\n for (const v of vector) {\r\n magnitude += v * v;\r\n }\r\n magnitude = Math.sqrt(magnitude);\r\n\r\n if (magnitude === 0) {\r\n return vector;\r\n }\r\n\r\n return vector.map(v => v / magnitude);\r\n }\r\n}\r\n\r\n/**\r\n * Create an embedding service based on configuration.\r\n *\r\n * @param config - Optional configuration override\r\n * @returns Embedding service instance, or null if provider is 'none'\r\n */\r\nexport function createEmbeddingService(config?: Partial<EmbeddingConfig>): EmbeddingService | null {\r\n const envConfig = getEmbeddingConfig();\r\n const mergedConfig = { ...envConfig, ...config };\r\n\r\n switch (mergedConfig.provider) {\r\n case 'openai':\r\n if (!mergedConfig.apiKey) {\r\n throw new Error(\r\n 'OpenAI API key is required. Set MEMORY_OPENAI_API_KEY environment variable or provide apiKey in config.'\r\n );\r\n }\r\n return new OpenAIEmbeddingService(mergedConfig.apiKey, mergedConfig.model);\r\n\r\n case 'local':\r\n return new LocalEmbeddingService(mergedConfig.model);\r\n\r\n case 'none':\r\n default:\r\n return null;\r\n }\r\n}\r\n","/**\n * Embedding Cache\n *\n * Phase 12 Sprint 5: LRU cache for embedding vectors with hit/miss tracking,\n * auto-invalidation on text hash changes, and TTL support.\n *\n * @module search/EmbeddingCache\n */\n\nimport { createHash } from 'crypto';\n\n/**\n * Cache entry for embedding vectors.\n */\ninterface CacheEntry {\n /** The embedding vector */\n vector: number[];\n /** Hash of the original text for invalidation */\n textHash: string;\n /** Timestamp when the entry was created (for TTL) */\n createdAt: number;\n /** Timestamp of last access (for LRU) */\n lastAccess: number;\n}\n\n/**\n * Statistics for the embedding cache.\n */\nexport interface EmbeddingCacheStats {\n /** Number of entries in the cache */\n size: number;\n /** Estimated memory usage in bytes */\n memoryBytes: number;\n /** Cache hit rate (0-1) */\n hitRate: number;\n /** Total cache hits */\n hits: number;\n /** Total cache misses */\n misses: number;\n}\n\n/**\n * Options for EmbeddingCache.\n */\nexport interface EmbeddingCacheOptions {\n /** Maximum number of entries in the cache (default: 1000) */\n maxSize?: number;\n /** Time-to-live in milliseconds (default: 3600000 = 1 hour) */\n ttlMs?: number;\n /** Dimensions of embedding vectors for memory estimation (default: 384) */\n dimensions?: number;\n}\n\n/**\n * Default cache options.\n */\nexport const DEFAULT_EMBEDDING_CACHE_OPTIONS: Required<EmbeddingCacheOptions> = {\n maxSize: 1000,\n ttlMs: 3600000, // 1 hour\n dimensions: 384,\n};\n\n/**\n * LRU cache for embedding vectors with hit/miss tracking.\n *\n * Features:\n * - LRU eviction when max size is reached\n * - Text hash-based invalidation (detects stale entries)\n * - TTL support for automatic expiration\n * - Hit/miss statistics tracking\n *\n * @example\n * ```typescript\n * const cache = new EmbeddingCache({ maxSize: 500, ttlMs: 60000 });\n *\n * // Cache an embedding\n * cache.set('entity1', 'Original text content', [0.1, 0.2, ...]);\n *\n * // Retrieve from cache\n * const result = cache.get('entity1', 'Original text content');\n * if (result) {\n * console.log('Cache hit!', result);\n * }\n *\n * // Check stats\n * console.log(cache.getStats()); // { size, memoryBytes, hitRate, hits, misses }\n * ```\n */\nexport class EmbeddingCache {\n private cache: Map<string, CacheEntry>;\n private options: Required<EmbeddingCacheOptions>;\n private hits: number = 0;\n private misses: number = 0;\n\n /**\n * Create a new embedding cache.\n *\n * @param options - Cache configuration options\n */\n constructor(options?: EmbeddingCacheOptions) {\n this.options = { ...DEFAULT_EMBEDDING_CACHE_OPTIONS, ...options };\n this.cache = new Map();\n }\n\n /**\n * Hash a text string for cache invalidation.\n *\n * @param text - Text to hash\n * @returns MD5 hash of the text\n */\n private hashText(text: string): string {\n return createHash('md5').update(text).digest('hex');\n }\n\n /**\n * Check if an entry is expired based on TTL.\n *\n * @param entry - Cache entry to check\n * @returns True if expired\n */\n private isExpired(entry: CacheEntry): boolean {\n return Date.now() - entry.createdAt > this.options.ttlMs;\n }\n\n /**\n * Evict the least recently used entry.\n */\n private evictLRU(): void {\n let oldestKey: string | null = null;\n let oldestTime = Infinity;\n\n for (const [key, entry] of this.cache) {\n if (entry.lastAccess < oldestTime) {\n oldestTime = entry.lastAccess;\n oldestKey = key;\n }\n }\n\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n /**\n * Get an embedding from the cache.\n *\n * Returns null if:\n * - Key not found\n * - Entry is expired (TTL)\n * - Text hash doesn't match (content changed)\n *\n * @param key - Cache key (typically entity name)\n * @param text - Current text content (for hash validation)\n * @returns Embedding vector if found and valid, null otherwise\n */\n get(key: string, text: string): number[] | null {\n const entry = this.cache.get(key);\n\n if (!entry) {\n this.misses++;\n return null;\n }\n\n // Check TTL\n if (this.isExpired(entry)) {\n this.cache.delete(key);\n this.misses++;\n return null;\n }\n\n // Check text hash for invalidation\n const currentHash = this.hashText(text);\n if (entry.textHash !== currentHash) {\n this.cache.delete(key);\n this.misses++;\n return null;\n }\n\n // Update last access time\n entry.lastAccess = Date.now();\n this.hits++;\n\n return entry.vector;\n }\n\n /**\n * Set an embedding in the cache.\n *\n * Automatically evicts LRU entries if max size is reached.\n *\n * @param key - Cache key (typically entity name)\n * @param text - Text content (used for hash-based invalidation)\n * @param vector - Embedding vector to cache\n */\n set(key: string, text: string, vector: number[]): void {\n // Evict if at capacity and not updating existing key\n if (this.cache.size >= this.options.maxSize && !this.cache.has(key)) {\n this.evictLRU();\n }\n\n const now = Date.now();\n this.cache.set(key, {\n vector,\n textHash: this.hashText(text),\n createdAt: now,\n lastAccess: now,\n });\n }\n\n /**\n * Check if a key exists in the cache (without affecting hit/miss stats).\n *\n * @param key - Cache key to check\n * @returns True if key exists (may be expired or stale)\n */\n has(key: string): boolean {\n return this.cache.has(key);\n }\n\n /**\n * Delete an entry from the cache.\n *\n * @param key - Cache key to delete\n * @returns True if entry was deleted\n */\n delete(key: string): boolean {\n return this.cache.delete(key);\n }\n\n /**\n * Clear all entries from the cache.\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Get cache statistics.\n *\n * @returns Cache statistics including size, memory usage, and hit rate\n */\n getStats(): EmbeddingCacheStats {\n const size = this.cache.size;\n // Estimate memory: each entry has vector (dimensions * 8 bytes for float64)\n // plus overhead for hash (~32 bytes), timestamps (~16 bytes), and Map overhead (~50 bytes)\n const memoryPerEntry = this.options.dimensions * 8 + 32 + 16 + 50;\n const memoryBytes = size * memoryPerEntry;\n\n const totalRequests = this.hits + this.misses;\n const hitRate = totalRequests > 0 ? this.hits / totalRequests : 0;\n\n return {\n size,\n memoryBytes,\n hitRate,\n hits: this.hits,\n misses: this.misses,\n };\n }\n\n /**\n * Reset hit/miss statistics (useful for benchmarks).\n */\n resetStats(): void {\n this.hits = 0;\n this.misses = 0;\n }\n\n /**\n * Remove expired entries from the cache.\n *\n * Called automatically during get operations, but can be\n * manually triggered for maintenance.\n *\n * @returns Number of entries removed\n */\n pruneExpired(): number {\n let removed = 0;\n for (const [key, entry] of this.cache) {\n if (this.isExpired(entry)) {\n this.cache.delete(key);\n removed++;\n }\n }\n return removed;\n }\n\n /**\n * Get the current cache size.\n *\n * @returns Number of entries in the cache\n */\n size(): number {\n return this.cache.size;\n }\n\n /**\n * Get all cached keys.\n *\n * @returns Array of cache keys\n */\n keys(): string[] {\n return Array.from(this.cache.keys());\n }\n\n /**\n * Update options dynamically.\n *\n * Note: Reducing maxSize will not immediately evict entries.\n *\n * @param options - New options to apply\n */\n updateOptions(options: Partial<EmbeddingCacheOptions>): void {\n this.options = { ...this.options, ...options };\n }\n}\n","/**\n * Incremental Indexer\n *\n * Phase 12 Sprint 5: Queues embedding index updates and batch-processes them\n * efficiently using flush thresholds and timer-based flushing.\n *\n * @module search/IncrementalIndexer\n */\n\nimport type { EmbeddingService, EmbeddingMode } from '../types/index.js';\nimport type { IVectorStore } from '../types/index.js';\nimport type { EmbeddingProgressCallback } from './EmbeddingService.js';\n\n/**\n * Types of index operations.\n */\nexport type IndexOperationType = 'create' | 'update' | 'delete';\n\n/**\n * Queued index operation.\n */\nexport interface IndexOperation {\n /** Type of operation */\n type: IndexOperationType;\n /** Entity name */\n entityName: string;\n /** Text to embed (for create/update) */\n text?: string;\n /** Timestamp when operation was queued */\n queuedAt: number;\n}\n\n/**\n * Options for IncrementalIndexer.\n */\nexport interface IncrementalIndexerOptions {\n /** Flush threshold - number of operations to queue before auto-flush (default: 50) */\n flushThreshold?: number;\n /** Timer-based flush interval in milliseconds (default: 5000ms) */\n flushIntervalMs?: number;\n /** Embedding mode for new embeddings (default: 'document') */\n embeddingMode?: EmbeddingMode;\n /** Progress callback for batch operations */\n onProgress?: EmbeddingProgressCallback;\n}\n\n/**\n * Default indexer options.\n */\nexport const DEFAULT_INDEXER_OPTIONS: Required<Omit<IncrementalIndexerOptions, 'onProgress'>> = {\n flushThreshold: 50,\n flushIntervalMs: 5000,\n embeddingMode: 'document',\n};\n\n/**\n * Result of a flush operation.\n */\nexport interface FlushResult {\n /** Number of operations processed */\n processed: number;\n /** Number of successful operations */\n succeeded: number;\n /** Number of failed operations */\n failed: number;\n /** Errors encountered during flush */\n errors: Array<{ entityName: string; error: string }>;\n /** Duration of flush in milliseconds */\n durationMs: number;\n}\n\n/**\n * Incremental indexer for embedding vectors.\n *\n * Queues index updates and batch-processes them efficiently:\n * - Auto-flush when queue reaches threshold\n * - Timer-based flush for time-sensitive updates\n * - Supports create, update, and delete operations\n * - Graceful shutdown with final flush\n *\n * @example\n * ```typescript\n * const indexer = new IncrementalIndexer(embeddingService, vectorStore, {\n * flushThreshold: 100,\n * flushIntervalMs: 10000,\n * });\n *\n * // Queue operations\n * indexer.queueCreate('entity1', 'Entity text content');\n * indexer.queueUpdate('entity2', 'Updated text content');\n * indexer.queueDelete('entity3');\n *\n * // Manual flush\n * const result = await indexer.flush();\n * console.log(`Processed ${result.processed} operations`);\n *\n * // Graceful shutdown\n * await indexer.shutdown();\n * ```\n */\nexport class IncrementalIndexer {\n private embeddingService: EmbeddingService;\n private vectorStore: IVectorStore;\n private options: Required<Omit<IncrementalIndexerOptions, 'onProgress'>>;\n private onProgress?: EmbeddingProgressCallback;\n\n private queue: IndexOperation[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private isFlushing = false;\n private isShutdown = false;\n\n /**\n * Create a new incremental indexer.\n *\n * @param embeddingService - Service for generating embeddings\n * @param vectorStore - Store for embedding vectors\n * @param options - Indexer configuration options\n */\n constructor(\n embeddingService: EmbeddingService,\n vectorStore: IVectorStore,\n options?: IncrementalIndexerOptions\n ) {\n this.embeddingService = embeddingService;\n this.vectorStore = vectorStore;\n this.options = { ...DEFAULT_INDEXER_OPTIONS, ...options };\n this.onProgress = options?.onProgress;\n\n // Start the flush timer\n this.startFlushTimer();\n }\n\n /**\n * Start the timer-based flush interval.\n */\n private startFlushTimer(): void {\n if (this.flushTimer || this.isShutdown) {\n return;\n }\n\n this.flushTimer = setInterval(async () => {\n if (this.queue.length > 0 && !this.isFlushing) {\n await this.flush();\n }\n }, this.options.flushIntervalMs);\n }\n\n /**\n * Stop the flush timer.\n */\n private stopFlushTimer(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n /**\n * Queue a create operation.\n *\n * @param entityName - Name of the entity to create\n * @param text - Text content to embed\n */\n queueCreate(entityName: string, text: string): void {\n if (this.isShutdown) {\n throw new Error('Indexer is shutdown');\n }\n\n // Remove any existing operations for this entity\n this.removeFromQueue(entityName);\n\n this.queue.push({\n type: 'create',\n entityName,\n text,\n queuedAt: Date.now(),\n });\n\n this.checkAutoFlush();\n }\n\n /**\n * Queue an update operation.\n *\n * @param entityName - Name of the entity to update\n * @param text - Updated text content to embed\n */\n queueUpdate(entityName: string, text: string): void {\n if (this.isShutdown) {\n throw new Error('Indexer is shutdown');\n }\n\n // Remove any existing operations for this entity\n this.removeFromQueue(entityName);\n\n this.queue.push({\n type: 'update',\n entityName,\n text,\n queuedAt: Date.now(),\n });\n\n this.checkAutoFlush();\n }\n\n /**\n * Queue a delete operation.\n *\n * @param entityName - Name of the entity to delete\n */\n queueDelete(entityName: string): void {\n if (this.isShutdown) {\n throw new Error('Indexer is shutdown');\n }\n\n // Remove any existing operations for this entity\n this.removeFromQueue(entityName);\n\n this.queue.push({\n type: 'delete',\n entityName,\n queuedAt: Date.now(),\n });\n\n this.checkAutoFlush();\n }\n\n /**\n * Remove all queued operations for an entity.\n *\n * @param entityName - Entity name to remove from queue\n */\n private removeFromQueue(entityName: string): void {\n this.queue = this.queue.filter(op => op.entityName !== entityName);\n }\n\n /**\n * Check if auto-flush threshold is reached.\n */\n private checkAutoFlush(): void {\n if (this.queue.length >= this.options.flushThreshold && !this.isFlushing) {\n // Use setImmediate to avoid blocking\n setImmediate(() => this.flush());\n }\n }\n\n /**\n * Flush the queue and process all pending operations.\n *\n * @returns Result of the flush operation\n */\n async flush(): Promise<FlushResult> {\n if (this.isFlushing) {\n return {\n processed: 0,\n succeeded: 0,\n failed: 0,\n errors: [],\n durationMs: 0,\n };\n }\n\n this.isFlushing = true;\n const startTime = Date.now();\n const errors: Array<{ entityName: string; error: string }> = [];\n let succeeded = 0;\n let failed = 0;\n\n // Take the current queue\n const operations = [...this.queue];\n this.queue = [];\n\n try {\n // Separate operations by type\n const createOps = operations.filter(op => op.type === 'create');\n const updateOps = operations.filter(op => op.type === 'update');\n const deleteOps = operations.filter(op => op.type === 'delete');\n\n // Process deletes first (fast, O(1))\n for (const op of deleteOps) {\n try {\n this.vectorStore.remove(op.entityName);\n succeeded++;\n } catch (error) {\n failed++;\n errors.push({\n entityName: op.entityName,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // Batch process creates and updates together\n const embedOps = [...createOps, ...updateOps];\n if (embedOps.length > 0) {\n const texts = embedOps.map(op => op.text!);\n const entityNames = embedOps.map(op => op.entityName);\n\n try {\n // Check if the embedding service has the batch with progress method\n let embeddings: number[][];\n\n if (this.onProgress && 'embedBatchWithProgress' in this.embeddingService) {\n embeddings = await (this.embeddingService as { embedBatchWithProgress: (texts: string[], mode: EmbeddingMode, onProgress?: EmbeddingProgressCallback) => Promise<number[][]> })\n .embedBatchWithProgress(texts, this.options.embeddingMode, this.onProgress);\n } else {\n embeddings = await this.embeddingService.embedBatch(texts, this.options.embeddingMode);\n }\n\n // Store each embedding\n for (let i = 0; i < embeddings.length; i++) {\n try {\n this.vectorStore.add(entityNames[i], embeddings[i]);\n succeeded++;\n } catch (error) {\n failed++;\n errors.push({\n entityName: entityNames[i],\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n } catch (error) {\n // Batch embedding failed, count all as failed\n failed += embedOps.length;\n for (const op of embedOps) {\n errors.push({\n entityName: op.entityName,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n } finally {\n this.isFlushing = false;\n }\n\n return {\n processed: operations.length,\n succeeded,\n failed,\n errors,\n durationMs: Date.now() - startTime,\n };\n }\n\n /**\n * Get the current queue size.\n *\n * @returns Number of operations in the queue\n */\n getQueueSize(): number {\n return this.queue.length;\n }\n\n /**\n * Get the current queue contents (for debugging/monitoring).\n *\n * @returns Copy of the current queue\n */\n getQueue(): IndexOperation[] {\n return [...this.queue];\n }\n\n /**\n * Check if the indexer is currently flushing.\n *\n * @returns True if flushing\n */\n isBusy(): boolean {\n return this.isFlushing;\n }\n\n /**\n * Check if the indexer is shutdown.\n *\n * @returns True if shutdown\n */\n isShutdownComplete(): boolean {\n return this.isShutdown;\n }\n\n /**\n * Graceful shutdown with final flush.\n *\n * Stops the timer and flushes any remaining operations.\n *\n * @returns Result of the final flush\n */\n async shutdown(): Promise<FlushResult> {\n this.isShutdown = true;\n this.stopFlushTimer();\n\n // Wait for any in-progress flush to complete\n while (this.isFlushing) {\n await new Promise(resolve => setTimeout(resolve, 10));\n }\n\n // Final flush\n return this.flush();\n }\n\n /**\n * Update indexer options.\n *\n * Note: Changes to flushIntervalMs will take effect on the next interval.\n *\n * @param options - New options to apply\n */\n updateOptions(options: Partial<IncrementalIndexerOptions>): void {\n const { onProgress, ...rest } = options;\n\n if (onProgress !== undefined) {\n this.onProgress = onProgress;\n }\n\n if (Object.keys(rest).length > 0) {\n this.options = { ...this.options, ...rest } as Required<Omit<IncrementalIndexerOptions, 'onProgress'>>;\n\n // Restart timer if interval changed\n if (rest.flushIntervalMs !== undefined) {\n this.stopFlushTimer();\n this.startFlushTimer();\n }\n }\n }\n\n /**\n * Clear the queue without processing.\n *\n * @returns Number of operations cleared\n */\n clearQueue(): number {\n const count = this.queue.length;\n this.queue = [];\n return count;\n }\n}\n","/**\r\n * Vector Store\r\n *\r\n * Phase 4 Sprint 11: Vector storage and retrieval for semantic search.\r\n * Provides in-memory and SQLite-backed implementations.\r\n *\r\n * @module search/VectorStore\r\n */\r\n\r\nimport type { IVectorStore, VectorSearchResult } from '../types/index.js';\r\n\r\n/**\r\n * Calculate cosine similarity between two vectors.\r\n *\r\n * Uses an optimized inner loop without array methods for maximum performance.\r\n *\r\n * @param a - First vector\r\n * @param b - Second vector\r\n * @returns Cosine similarity score (0.0 to 1.0)\r\n */\r\nexport function cosineSimilarity(a: number[], b: number[]): number {\r\n if (a.length !== b.length) {\r\n throw new Error(`Vector dimensions mismatch: ${a.length} vs ${b.length}`);\r\n }\r\n\r\n let dotProduct = 0;\r\n let magnitudeA = 0;\r\n let magnitudeB = 0;\r\n\r\n // Optimized single-pass loop\r\n for (let i = 0; i < a.length; i++) {\r\n const ai = a[i];\r\n const bi = b[i];\r\n dotProduct += ai * bi;\r\n magnitudeA += ai * ai;\r\n magnitudeB += bi * bi;\r\n }\r\n\r\n const magnitude = Math.sqrt(magnitudeA) * Math.sqrt(magnitudeB);\r\n\r\n if (magnitude === 0) {\r\n return 0;\r\n }\r\n\r\n // Clamp to [-1, 1] to handle floating point errors\r\n const similarity = dotProduct / magnitude;\r\n return Math.max(-1, Math.min(1, similarity));\r\n}\r\n\r\n/**\r\n * In-Memory Vector Store\r\n *\r\n * Stores vectors in memory using a Map for O(1) add/remove operations.\r\n * Search uses brute-force cosine similarity which is O(n) but fast for\r\n * small to medium graphs (<10K entities).\r\n *\r\n * @example\r\n * ```typescript\r\n * const store = new InMemoryVectorStore();\r\n * store.add(\"entity1\", [0.1, 0.2, 0.3]);\r\n * store.add(\"entity2\", [0.4, 0.5, 0.6]);\r\n * const results = store.search([0.1, 0.2, 0.3], 5);\r\n * console.log(results); // [{ name: \"entity1\", score: 1.0 }, ...]\r\n * ```\r\n */\r\nexport class InMemoryVectorStore implements IVectorStore {\r\n /** Map of entity name to embedding vector */\r\n private vectors: Map<string, number[]> = new Map();\r\n\r\n /**\r\n * Add a vector for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param vector - Embedding vector\r\n */\r\n add(entityName: string, vector: number[]): void {\r\n this.vectors.set(entityName, vector);\r\n }\r\n\r\n /**\r\n * Search for similar vectors using cosine similarity.\r\n *\r\n * @param queryVector - Query embedding vector\r\n * @param k - Number of results to return\r\n * @returns Array of results with entity name and similarity score\r\n */\r\n search(queryVector: number[], k: number): VectorSearchResult[] {\r\n if (this.vectors.size === 0) {\r\n return [];\r\n }\r\n\r\n // Calculate similarity for all vectors\r\n const results: VectorSearchResult[] = [];\r\n\r\n for (const [name, vector] of this.vectors) {\r\n try {\r\n const score = cosineSimilarity(queryVector, vector);\r\n results.push({ name, score });\r\n } catch {\r\n // Skip vectors with dimension mismatch\r\n continue;\r\n }\r\n }\r\n\r\n // Sort by score descending and take top k\r\n results.sort((a, b) => b.score - a.score);\r\n return results.slice(0, k);\r\n }\r\n\r\n /**\r\n * Remove a vector by entity name.\r\n *\r\n * @param entityName - Name of the entity to remove\r\n * @returns True if found and removed\r\n */\r\n remove(entityName: string): boolean {\r\n return this.vectors.delete(entityName);\r\n }\r\n\r\n /**\r\n * Get the number of vectors stored.\r\n *\r\n * @returns Number of vectors\r\n */\r\n size(): number {\r\n return this.vectors.size;\r\n }\r\n\r\n /**\r\n * Clear all vectors from the store.\r\n */\r\n clear(): void {\r\n this.vectors.clear();\r\n }\r\n\r\n /**\r\n * Check if a vector exists for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @returns True if vector exists\r\n */\r\n has(entityName: string): boolean {\r\n return this.vectors.has(entityName);\r\n }\r\n\r\n /**\r\n * Get the vector for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @returns Vector if found, undefined otherwise\r\n */\r\n get(entityName: string): number[] | undefined {\r\n return this.vectors.get(entityName);\r\n }\r\n\r\n /**\r\n * Get all entity names with stored vectors.\r\n *\r\n * @returns Array of entity names\r\n */\r\n getEntityNames(): string[] {\r\n return Array.from(this.vectors.keys());\r\n }\r\n\r\n /**\r\n * Load vectors from an iterable source.\r\n *\r\n * @param entries - Iterable of [entityName, vector] pairs\r\n */\r\n loadFrom(entries: Iterable<[string, number[]]>): void {\r\n for (const [name, vector] of entries) {\r\n this.vectors.set(name, vector);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * SQLite Vector Store\r\n *\r\n * Persists vectors to SQLite storage while maintaining an in-memory cache\r\n * for fast search operations. Combines persistence with performance.\r\n *\r\n * Uses SQLiteStorage's embedding storage methods for persistence.\r\n *\r\n * @example\r\n * ```typescript\r\n * const store = new SQLiteVectorStore(sqliteStorage);\r\n * await store.initialize();\r\n * store.add(\"entity1\", [0.1, 0.2, 0.3]);\r\n * const results = store.search([0.1, 0.2, 0.3], 5);\r\n * ```\r\n */\r\nexport class SQLiteVectorStore implements IVectorStore {\r\n /** In-memory cache for fast search */\r\n private memoryStore: InMemoryVectorStore = new InMemoryVectorStore();\r\n\r\n /** SQLite storage reference for persistence */\r\n private storage: SQLiteStorageWithEmbeddings | null = null;\r\n\r\n /** Whether the store has been initialized */\r\n private initialized = false;\r\n\r\n /** Model name used for embeddings */\r\n private embeddingModel: string = '';\r\n\r\n /**\r\n * Create a SQLite vector store.\r\n *\r\n * @param storage - SQLite storage instance with embedding support\r\n * @param embeddingModel - Model name used for embeddings\r\n */\r\n constructor(storage?: SQLiteStorageWithEmbeddings, embeddingModel: string = 'unknown') {\r\n this.storage = storage || null;\r\n this.embeddingModel = embeddingModel;\r\n }\r\n\r\n /**\r\n * Initialize the store by loading vectors from SQLite.\r\n */\r\n async initialize(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n if (this.storage) {\r\n // Load all embeddings from SQLite\r\n const embeddings = await this.storage.loadAllEmbeddings();\r\n this.memoryStore.loadFrom(embeddings);\r\n }\r\n\r\n this.initialized = true;\r\n }\r\n\r\n /**\r\n * Add a vector for an entity.\r\n * Stores in both memory and SQLite for persistence.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param vector - Embedding vector\r\n */\r\n add(entityName: string, vector: number[]): void {\r\n // Add to in-memory cache\r\n this.memoryStore.add(entityName, vector);\r\n\r\n // Persist to SQLite if available\r\n if (this.storage) {\r\n this.storage.storeEmbedding(entityName, vector, this.embeddingModel);\r\n }\r\n }\r\n\r\n /**\r\n * Search for similar vectors using cosine similarity.\r\n *\r\n * @param queryVector - Query embedding vector\r\n * @param k - Number of results to return\r\n * @returns Array of results with entity name and similarity score\r\n */\r\n search(queryVector: number[], k: number): VectorSearchResult[] {\r\n return this.memoryStore.search(queryVector, k);\r\n }\r\n\r\n /**\r\n * Remove a vector by entity name.\r\n *\r\n * @param entityName - Name of the entity to remove\r\n * @returns True if found and removed\r\n */\r\n remove(entityName: string): boolean {\r\n const removed = this.memoryStore.remove(entityName);\r\n\r\n // Remove from SQLite if available\r\n if (this.storage && removed) {\r\n this.storage.removeEmbedding(entityName);\r\n }\r\n\r\n return removed;\r\n }\r\n\r\n /**\r\n * Get the number of vectors stored.\r\n *\r\n * @returns Number of vectors\r\n */\r\n size(): number {\r\n return this.memoryStore.size();\r\n }\r\n\r\n /**\r\n * Clear all vectors from the store.\r\n */\r\n clear(): void {\r\n this.memoryStore.clear();\r\n\r\n if (this.storage) {\r\n this.storage.clearAllEmbeddings();\r\n }\r\n }\r\n\r\n /**\r\n * Check if a vector exists for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @returns True if vector exists\r\n */\r\n has(entityName: string): boolean {\r\n return this.memoryStore.has(entityName);\r\n }\r\n\r\n /**\r\n * Get the vector for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @returns Vector if found, undefined otherwise\r\n */\r\n get(entityName: string): number[] | undefined {\r\n return this.memoryStore.get(entityName);\r\n }\r\n\r\n /**\r\n * Set the SQLite storage reference.\r\n *\r\n * @param storage - SQLite storage instance\r\n */\r\n setStorage(storage: SQLiteStorageWithEmbeddings): void {\r\n this.storage = storage;\r\n }\r\n\r\n /**\r\n * Set the embedding model name.\r\n *\r\n * @param model - Model name\r\n */\r\n setEmbeddingModel(model: string): void {\r\n this.embeddingModel = model;\r\n }\r\n}\r\n\r\n/**\r\n * Interface for SQLite storage with embedding support.\r\n *\r\n * This is a subset of SQLiteStorage that only includes embedding-related methods.\r\n * Allows for loose coupling between VectorStore and SQLiteStorage.\r\n */\r\nexport interface SQLiteStorageWithEmbeddings {\r\n /**\r\n * Store an embedding for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n * @param vector - Embedding vector\r\n * @param model - Model name used for the embedding\r\n */\r\n storeEmbedding(entityName: string, vector: number[], model: string): void;\r\n\r\n /**\r\n * Load all embeddings from storage.\r\n *\r\n * @returns Array of [entityName, vector] pairs\r\n */\r\n loadAllEmbeddings(): Promise<[string, number[]][]>;\r\n\r\n /**\r\n * Remove an embedding for an entity.\r\n *\r\n * @param entityName - Name of the entity\r\n */\r\n removeEmbedding(entityName: string): void;\r\n\r\n /**\r\n * Clear all embeddings from storage.\r\n */\r\n clearAllEmbeddings(): void;\r\n}\r\n\r\n/**\r\n * Create a vector store based on storage type.\r\n *\r\n * @param storageType - Storage type: 'jsonl' or 'sqlite'\r\n * @param storage - Optional SQLite storage reference for 'sqlite' type\r\n * @param embeddingModel - Optional model name for embedding tracking\r\n * @returns Vector store instance\r\n */\r\nexport function createVectorStore(\r\n storageType: 'jsonl' | 'sqlite' = 'jsonl',\r\n storage?: SQLiteStorageWithEmbeddings,\r\n embeddingModel?: string\r\n): IVectorStore {\r\n switch (storageType) {\r\n case 'sqlite':\r\n return new SQLiteVectorStore(storage, embeddingModel);\r\n case 'jsonl':\r\n default:\r\n return new InMemoryVectorStore();\r\n }\r\n}\r\n","/**\r\n * Semantic Search Manager\r\n *\r\n * Phase 4 Sprint 12: Orchestrates embedding service and vector store\r\n * to provide semantic similarity search capabilities.\r\n *\r\n * @module search/SemanticSearch\r\n */\r\n\r\nimport type {\r\n Entity,\r\n EmbeddingService,\r\n IVectorStore,\r\n SemanticSearchResult,\r\n SemanticIndexOptions,\r\n ReadonlyKnowledgeGraph,\r\n} from '../types/index.js';\r\nimport { InMemoryVectorStore } from './VectorStore.js';\r\nimport { EMBEDDING_DEFAULTS, SEMANTIC_SEARCH_LIMITS } from '../utils/constants.js';\r\nimport { checkCancellation } from '../utils/index.js';\r\n\r\n/**\r\n * Convert an entity to a text representation for embedding.\r\n *\r\n * Creates a structured text that captures the entity's key information\r\n * for generating meaningful embeddings.\r\n *\r\n * @param entity - Entity to convert\r\n * @returns Text representation suitable for embedding\r\n */\r\nexport function entityToText(entity: Entity): string {\r\n const parts: string[] = [];\r\n\r\n // Name and type are most important\r\n parts.push(`${entity.name} (${entity.entityType})`);\r\n\r\n // Add observations (limited to prevent overly long text)\r\n if (entity.observations.length > 0) {\r\n const observationText = entity.observations.slice(0, 10).join('. ');\r\n parts.push(observationText);\r\n }\r\n\r\n // Add tags if present\r\n if (entity.tags && entity.tags.length > 0) {\r\n parts.push(`Tags: ${entity.tags.join(', ')}`);\r\n }\r\n\r\n return parts.join('\\n');\r\n}\r\n\r\n/**\r\n * Semantic Search Manager\r\n *\r\n * Provides semantic similarity search by converting entities to embeddings\r\n * and storing them in a vector store. Supports search by query text and\r\n * finding similar entities.\r\n *\r\n * @example\r\n * ```typescript\r\n * const semanticSearch = new SemanticSearch(embeddingService, vectorStore);\r\n * await semanticSearch.indexAll(graph);\r\n * const results = await semanticSearch.search(graph, \"machine learning\");\r\n * ```\r\n */\r\nexport class SemanticSearch {\r\n /** Embedding service for generating vectors */\r\n private embeddingService: EmbeddingService;\r\n\r\n /** Vector store for storing and searching embeddings */\r\n private vectorStore: IVectorStore;\r\n\r\n /** Whether embeddings have been indexed */\r\n private indexed = false;\r\n\r\n /** Number of entities currently indexed */\r\n private indexedCount = 0;\r\n\r\n /**\r\n * Create a semantic search manager.\r\n *\r\n * @param embeddingService - Service for generating embeddings\r\n * @param vectorStore - Store for vector storage and search\r\n */\r\n constructor(embeddingService: EmbeddingService, vectorStore?: IVectorStore) {\r\n this.embeddingService = embeddingService;\r\n this.vectorStore = vectorStore || new InMemoryVectorStore();\r\n }\r\n\r\n /**\r\n * Index all entities in the knowledge graph.\r\n *\r\n * Generates embeddings for all entities and stores them in the vector store.\r\n * Can be called incrementally - only indexes entities that aren't already indexed.\r\n *\r\n * Phase 9B: Supports cancellation via AbortSignal in options.\r\n *\r\n * @param graph - Knowledge graph to index\r\n * @param options - Indexing options (includes signal for cancellation)\r\n * @returns Index statistics\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n */\r\n async indexAll(\r\n graph: ReadonlyKnowledgeGraph,\r\n options: SemanticIndexOptions = {}\r\n ): Promise<{ indexed: number; skipped: number; errors: number }> {\r\n const {\r\n forceReindex = false,\r\n onProgress,\r\n batchSize = EMBEDDING_DEFAULTS.DEFAULT_BATCH_SIZE,\r\n signal,\r\n } = options;\r\n\r\n // Check for early cancellation\r\n checkCancellation(signal, 'indexAll');\r\n\r\n let indexed = 0;\r\n let skipped = 0;\r\n let errors = 0;\r\n\r\n const entities = graph.entities;\r\n const total = entities.length;\r\n\r\n // Collect entities to index\r\n const toIndex: Entity[] = [];\r\n for (const entity of entities) {\r\n if (forceReindex || !this.vectorStore.has(entity.name)) {\r\n toIndex.push(entity);\r\n } else {\r\n skipped++;\r\n }\r\n }\r\n\r\n // Process in batches\r\n for (let i = 0; i < toIndex.length; i += batchSize) {\r\n // Check for cancellation between batches\r\n checkCancellation(signal, 'indexAll');\r\n\r\n const batch = toIndex.slice(i, i + batchSize);\r\n const texts = batch.map(entityToText);\r\n\r\n try {\r\n const embeddings = await this.embeddingService.embedBatch(texts);\r\n\r\n for (let j = 0; j < batch.length; j++) {\r\n this.vectorStore.add(batch[j].name, embeddings[j]);\r\n indexed++;\r\n }\r\n } catch (error) {\r\n // Try individual embeddings on batch failure\r\n for (const entity of batch) {\r\n // Check for cancellation during fallback\r\n checkCancellation(signal, 'indexAll');\r\n\r\n try {\r\n const text = entityToText(entity);\r\n const embedding = await this.embeddingService.embed(text);\r\n this.vectorStore.add(entity.name, embedding);\r\n indexed++;\r\n } catch {\r\n errors++;\r\n }\r\n }\r\n }\r\n\r\n // Report progress\r\n if (onProgress) {\r\n onProgress(indexed + skipped + errors, total);\r\n }\r\n }\r\n\r\n this.indexed = true;\r\n this.indexedCount = this.vectorStore.size();\r\n\r\n return { indexed, skipped, errors };\r\n }\r\n\r\n /**\r\n * Index a single entity.\r\n *\r\n * @param entity - Entity to index\r\n * @returns True if indexed successfully\r\n */\r\n async indexEntity(entity: Entity): Promise<boolean> {\r\n try {\r\n const text = entityToText(entity);\r\n const embedding = await this.embeddingService.embed(text);\r\n this.vectorStore.add(entity.name, embedding);\r\n this.indexedCount = this.vectorStore.size();\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Remove an entity from the index.\r\n *\r\n * @param entityName - Name of entity to remove\r\n * @returns True if found and removed\r\n */\r\n removeEntity(entityName: string): boolean {\r\n const removed = this.vectorStore.remove(entityName);\r\n if (removed) {\r\n this.indexedCount = this.vectorStore.size();\r\n }\r\n return removed;\r\n }\r\n\r\n /**\r\n * Search for entities semantically similar to a query.\r\n *\r\n * @param graph - Knowledge graph to search in\r\n * @param query - Search query text\r\n * @param limit - Maximum number of results (default: 10)\r\n * @param minSimilarity - Minimum similarity threshold (default: 0)\r\n * @returns Array of search results with similarity scores\r\n */\r\n async search(\r\n graph: ReadonlyKnowledgeGraph,\r\n query: string,\r\n limit: number = SEMANTIC_SEARCH_LIMITS.DEFAULT_LIMIT,\r\n minSimilarity: number = SEMANTIC_SEARCH_LIMITS.MIN_SIMILARITY\r\n ): Promise<SemanticSearchResult[]> {\r\n // Ensure limit is within bounds\r\n const effectiveLimit = Math.min(limit, SEMANTIC_SEARCH_LIMITS.MAX_LIMIT);\r\n\r\n // Generate embedding for query\r\n const queryEmbedding = await this.embeddingService.embed(query);\r\n\r\n // Search vector store\r\n const vectorResults = this.vectorStore.search(queryEmbedding, effectiveLimit * 2); // Get extra for filtering\r\n\r\n // Convert to SemanticSearchResult with entity lookup\r\n const entityMap = new Map<string, Entity>();\r\n for (const entity of graph.entities) {\r\n entityMap.set(entity.name, entity);\r\n }\r\n\r\n const results: SemanticSearchResult[] = [];\r\n for (const result of vectorResults) {\r\n if (result.score < minSimilarity) {\r\n continue;\r\n }\r\n\r\n const entity = entityMap.get(result.name);\r\n if (entity) {\r\n results.push({\r\n entity,\r\n similarity: result.score,\r\n });\r\n }\r\n\r\n if (results.length >= effectiveLimit) {\r\n break;\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Find entities similar to a given entity.\r\n *\r\n * @param graph - Knowledge graph to search in\r\n * @param entityName - Name of entity to find similar entities for\r\n * @param limit - Maximum number of results (default: 10)\r\n * @param minSimilarity - Minimum similarity threshold (default: 0)\r\n * @returns Array of search results with similarity scores\r\n */\r\n async findSimilar(\r\n graph: ReadonlyKnowledgeGraph,\r\n entityName: string,\r\n limit: number = SEMANTIC_SEARCH_LIMITS.DEFAULT_LIMIT,\r\n minSimilarity: number = SEMANTIC_SEARCH_LIMITS.MIN_SIMILARITY\r\n ): Promise<SemanticSearchResult[]> {\r\n // Get the entity's embedding\r\n const embedding = this.vectorStore.get(entityName);\r\n if (!embedding) {\r\n // Try to find and index the entity\r\n const entity = graph.entities.find(e => e.name === entityName);\r\n if (entity) {\r\n await this.indexEntity(entity);\r\n return this.findSimilar(graph, entityName, limit, minSimilarity);\r\n }\r\n return [];\r\n }\r\n\r\n // Ensure limit is within bounds\r\n const effectiveLimit = Math.min(limit, SEMANTIC_SEARCH_LIMITS.MAX_LIMIT);\r\n\r\n // Search vector store (request extra to filter out self)\r\n const vectorResults = this.vectorStore.search(embedding, effectiveLimit + 1);\r\n\r\n // Convert to SemanticSearchResult with entity lookup\r\n const entityMap = new Map<string, Entity>();\r\n for (const entity of graph.entities) {\r\n entityMap.set(entity.name, entity);\r\n }\r\n\r\n const results: SemanticSearchResult[] = [];\r\n for (const result of vectorResults) {\r\n // Skip self\r\n if (result.name === entityName) {\r\n continue;\r\n }\r\n\r\n if (result.score < minSimilarity) {\r\n continue;\r\n }\r\n\r\n const entity = entityMap.get(result.name);\r\n if (entity) {\r\n results.push({\r\n entity,\r\n similarity: result.score,\r\n });\r\n }\r\n\r\n if (results.length >= effectiveLimit) {\r\n break;\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Get the embedding service.\r\n *\r\n * @returns Embedding service instance\r\n */\r\n getEmbeddingService(): EmbeddingService {\r\n return this.embeddingService;\r\n }\r\n\r\n /**\r\n * Get the vector store.\r\n *\r\n * @returns Vector store instance\r\n */\r\n getVectorStore(): IVectorStore {\r\n return this.vectorStore;\r\n }\r\n\r\n /**\r\n * Check if the index has been built.\r\n *\r\n * @returns True if indexAll has been called\r\n */\r\n isIndexed(): boolean {\r\n return this.indexed;\r\n }\r\n\r\n /**\r\n * Get the number of indexed entities.\r\n *\r\n * @returns Number of entities in the vector store\r\n */\r\n getIndexedCount(): number {\r\n return this.indexedCount;\r\n }\r\n\r\n /**\r\n * Clear all indexed embeddings.\r\n */\r\n clearIndex(): void {\r\n this.vectorStore.clear();\r\n this.indexed = false;\r\n this.indexedCount = 0;\r\n }\r\n\r\n /**\r\n * Check if semantic search is available.\r\n *\r\n * @returns True if embedding service is ready\r\n */\r\n async isAvailable(): Promise<boolean> {\r\n return this.embeddingService.isReady();\r\n }\r\n\r\n /**\r\n * Get semantic search statistics.\r\n *\r\n * @returns Statistics about the semantic search index\r\n */\r\n getStats(): {\r\n indexed: boolean;\r\n indexedCount: number;\r\n provider: string;\r\n model: string;\r\n dimensions: number;\r\n } {\r\n return {\r\n indexed: this.indexed,\r\n indexedCount: this.indexedCount,\r\n provider: this.embeddingService.provider,\r\n model: this.embeddingService.model,\r\n dimensions: this.embeddingService.dimensions,\r\n };\r\n }\r\n}\r\n","/**\r\n * TF-IDF Event Sync\r\n *\r\n * Phase 10 Sprint 3: Hooks TFIDFIndexManager to graph events for automatic\r\n * incremental index updates when entities change.\r\n *\r\n * @module search/TFIDFEventSync\r\n */\r\n\r\nimport type { GraphEventEmitter } from '../core/GraphEventEmitter.js';\r\nimport type { TFIDFIndexManager } from './TFIDFIndexManager.js';\r\nimport type { IGraphStorage } from '../types/index.js';\r\nimport type {\r\n EntityCreatedEvent,\r\n EntityUpdatedEvent,\r\n EntityDeletedEvent,\r\n} from '../types/types.js';\r\n\r\n/**\r\n * Phase 10 Sprint 3: Synchronizes TF-IDF index with graph changes via events.\r\n *\r\n * Listens to graph events and triggers incremental index updates automatically.\r\n * More efficient than rebuilding the entire index on every change.\r\n *\r\n * @example\r\n * ```typescript\r\n * const storage = new GraphStorage('/data/memory.jsonl');\r\n * const indexManager = new TFIDFIndexManager('/data');\r\n *\r\n * // Load or build index\r\n * await indexManager.loadIndex();\r\n *\r\n * // Enable automatic sync\r\n * const sync = new TFIDFEventSync(indexManager, storage.events, storage);\r\n * sync.enable();\r\n *\r\n * // Now entities added to storage will automatically update the index\r\n * await storage.appendEntity({ name: 'New', entityType: 'test', observations: [] });\r\n *\r\n * // Disable when done\r\n * sync.disable();\r\n * ```\r\n */\r\nexport class TFIDFEventSync {\r\n private indexManager: TFIDFIndexManager;\r\n private eventEmitter: GraphEventEmitter;\r\n private storage: IGraphStorage;\r\n private unsubscribers: Array<() => void> = [];\r\n private enabled: boolean = false;\r\n\r\n /**\r\n * Create a new TFIDFEventSync instance.\r\n *\r\n * @param indexManager - TFIDFIndexManager to sync\r\n * @param eventEmitter - GraphEventEmitter to listen to\r\n * @param storage - Storage to fetch entity data from (for updates)\r\n */\r\n constructor(\r\n indexManager: TFIDFIndexManager,\r\n eventEmitter: GraphEventEmitter,\r\n storage: IGraphStorage\r\n ) {\r\n this.indexManager = indexManager;\r\n this.eventEmitter = eventEmitter;\r\n this.storage = storage;\r\n }\r\n\r\n /**\r\n * Enable automatic index synchronization.\r\n *\r\n * Subscribes to entity:created, entity:updated, and entity:deleted events.\r\n */\r\n enable(): void {\r\n if (this.enabled) {\r\n return;\r\n }\r\n\r\n // Subscribe to entity events\r\n this.unsubscribers.push(\r\n this.eventEmitter.on('entity:created', this.handleEntityCreated.bind(this))\r\n );\r\n\r\n this.unsubscribers.push(\r\n this.eventEmitter.on('entity:updated', this.handleEntityUpdated.bind(this))\r\n );\r\n\r\n this.unsubscribers.push(\r\n this.eventEmitter.on('entity:deleted', this.handleEntityDeleted.bind(this))\r\n );\r\n\r\n this.enabled = true;\r\n }\r\n\r\n /**\r\n * Disable automatic index synchronization.\r\n *\r\n * Unsubscribes from all events.\r\n */\r\n disable(): void {\r\n if (!this.enabled) {\r\n return;\r\n }\r\n\r\n for (const unsubscribe of this.unsubscribers) {\r\n unsubscribe();\r\n }\r\n this.unsubscribers = [];\r\n this.enabled = false;\r\n }\r\n\r\n /**\r\n * Check if synchronization is enabled.\r\n *\r\n * @returns True if enabled\r\n */\r\n isEnabled(): boolean {\r\n return this.enabled;\r\n }\r\n\r\n /**\r\n * Handle entity:created event.\r\n * @private\r\n */\r\n private handleEntityCreated(event: EntityCreatedEvent): void {\r\n if (!this.indexManager.isInitialized()) {\r\n return;\r\n }\r\n\r\n this.indexManager.addDocument({\r\n name: event.entity.name,\r\n entityType: event.entity.entityType,\r\n observations: event.entity.observations,\r\n });\r\n }\r\n\r\n /**\r\n * Handle entity:updated event.\r\n * @private\r\n */\r\n private async handleEntityUpdated(event: EntityUpdatedEvent): Promise<void> {\r\n if (!this.indexManager.isInitialized()) {\r\n return;\r\n }\r\n\r\n // Fetch the current entity state\r\n const graph = await this.storage.loadGraph();\r\n const entity = graph.entities.find(e => e.name === event.entityName);\r\n\r\n if (entity) {\r\n this.indexManager.updateDocument({\r\n name: entity.name,\r\n entityType: entity.entityType,\r\n observations: entity.observations,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Handle entity:deleted event.\r\n * @private\r\n */\r\n private handleEntityDeleted(event: EntityDeletedEvent): void {\r\n if (!this.indexManager.isInitialized()) {\r\n return;\r\n }\r\n\r\n this.indexManager.removeDocument(event.entityName);\r\n }\r\n}\r\n","/**\n * Symbolic Search Layer\n *\n * Phase 11: Provides metadata-based filtering using structured predicates.\n * Part of the three-layer hybrid search architecture.\n *\n * @module search/SymbolicSearch\n */\n\nimport type { Entity, SymbolicFilters } from '../types/index.js';\n\n/**\n * Result from symbolic search with match score.\n */\nexport interface SymbolicResult {\n entity: Entity;\n score: number;\n matchedFilters: string[];\n}\n\n/**\n * Symbolic Search provides metadata-based filtering.\n *\n * Filters entities using structured predicates on tags, types,\n * dates, importance, and hierarchy.\n *\n * @example\n * ```typescript\n * const symbolic = new SymbolicSearch();\n * const results = symbolic.search(entities, {\n * tags: ['important'],\n * entityTypes: ['person'],\n * importance: { min: 5 }\n * });\n * ```\n */\nexport class SymbolicSearch {\n /**\n * Filter entities using structured metadata predicates.\n * All filters are AND-combined.\n *\n * @param entities - Entities to filter\n * @param filters - Symbolic filter criteria\n * @returns Filtered entities with match scores\n */\n search(entities: readonly Entity[], filters: SymbolicFilters): SymbolicResult[] {\n const results: SymbolicResult[] = [];\n\n for (const entity of entities) {\n const { matches, score, matchedFilters } = this.evaluateFilters(entity, filters);\n if (matches) {\n results.push({ entity, score, matchedFilters });\n }\n }\n\n // Sort by score descending\n return results.sort((a, b) => b.score - a.score);\n }\n\n /**\n * Evaluate all filters against an entity.\n */\n private evaluateFilters(\n entity: Entity,\n filters: SymbolicFilters\n ): { matches: boolean; score: number; matchedFilters: string[] } {\n const matchedFilters: string[] = [];\n let totalFilters = 0;\n let matchedCount = 0;\n\n // Tag filter\n if (filters.tags && filters.tags.length > 0) {\n totalFilters++;\n const entityTags = entity.tags ?? [];\n const matchingTags = filters.tags.filter((t: string) =>\n entityTags.some((et: string) => et.toLowerCase() === t.toLowerCase())\n );\n if (matchingTags.length > 0) {\n matchedCount++;\n matchedFilters.push(`tags:${matchingTags.join(',')}`);\n } else {\n return { matches: false, score: 0, matchedFilters: [] };\n }\n }\n\n // Entity type filter\n if (filters.entityTypes && filters.entityTypes.length > 0) {\n totalFilters++;\n if (filters.entityTypes.some((t: string) =>\n t.toLowerCase() === entity.entityType.toLowerCase()\n )) {\n matchedCount++;\n matchedFilters.push(`type:${entity.entityType}`);\n } else {\n return { matches: false, score: 0, matchedFilters: [] };\n }\n }\n\n // Date range filter\n if (filters.dateRange) {\n totalFilters++;\n const entityDate = entity.createdAt || entity.lastModified;\n if (!entityDate) {\n // Entities without dates are excluded when date filter is applied\n return { matches: false, score: 0, matchedFilters: [] };\n }\n const date = new Date(entityDate);\n const start = filters.dateRange.start ? new Date(filters.dateRange.start) : null;\n const end = filters.dateRange.end ? new Date(filters.dateRange.end) : null;\n\n const inRange = (!start || date >= start) && (!end || date <= end);\n if (inRange) {\n matchedCount++;\n matchedFilters.push('dateRange');\n } else {\n return { matches: false, score: 0, matchedFilters: [] };\n }\n }\n\n // Importance filter\n if (filters.importance) {\n totalFilters++;\n const importance = entity.importance ?? 5;\n const { min, max } = filters.importance;\n const inRange = (min === undefined || importance >= min) &&\n (max === undefined || importance <= max);\n if (inRange) {\n matchedCount++;\n matchedFilters.push(`importance:${importance}`);\n } else {\n return { matches: false, score: 0, matchedFilters: [] };\n }\n }\n\n // Parent filter\n if (filters.parentId !== undefined) {\n totalFilters++;\n if (entity.parentId === filters.parentId) {\n matchedCount++;\n matchedFilters.push(`parent:${filters.parentId}`);\n } else {\n return { matches: false, score: 0, matchedFilters: [] };\n }\n }\n\n // Has observations filter\n if (filters.hasObservations !== undefined) {\n totalFilters++;\n const hasObs = entity.observations.length > 0;\n if (hasObs === filters.hasObservations) {\n matchedCount++;\n matchedFilters.push('hasObservations');\n } else {\n return { matches: false, score: 0, matchedFilters: [] };\n }\n }\n\n // If no filters specified, match all with base score\n if (totalFilters === 0) {\n return { matches: true, score: 0.5, matchedFilters: [] };\n }\n\n // Score based on proportion of filters matched\n const score = matchedCount / totalFilters;\n return { matches: true, score, matchedFilters };\n }\n\n /**\n * Get entities matching a specific tag.\n */\n byTag(entities: readonly Entity[], tag: string): Entity[] {\n return entities.filter(e =>\n e.tags?.some(t => t.toLowerCase() === tag.toLowerCase())\n );\n }\n\n /**\n * Get entities of a specific type.\n */\n byType(entities: readonly Entity[], entityType: string): Entity[] {\n return entities.filter(e =>\n e.entityType.toLowerCase() === entityType.toLowerCase()\n );\n }\n\n /**\n * Get entities within importance range.\n */\n byImportance(entities: readonly Entity[], min: number, max: number): Entity[] {\n return entities.filter(e => {\n const imp = e.importance ?? 5;\n return imp >= min && imp <= max;\n });\n }\n}\n","/**\n * Hybrid Search Manager\n *\n * Phase 11: Orchestrates three-layer hybrid search combining\n * semantic, lexical, and symbolic signals.\n *\n * @module search/HybridSearchManager\n */\n\nimport type {\n Entity,\n HybridSearchOptions,\n HybridSearchResult,\n ReadonlyKnowledgeGraph,\n SymbolicFilters,\n} from '../types/index.js';\nimport type { SemanticSearch } from './SemanticSearch.js';\nimport type { RankedSearch } from './RankedSearch.js';\nimport { SymbolicSearch } from './SymbolicSearch.js';\nimport { SEMANTIC_SEARCH_LIMITS } from '../utils/constants.js';\n\n/**\n * Default weights for hybrid search layers.\n */\nexport const DEFAULT_HYBRID_WEIGHTS = {\n semantic: 0.5,\n lexical: 0.3,\n symbolic: 0.2,\n};\n\n/**\n * Hybrid Search Manager\n *\n * Combines three search layers:\n * 1. Semantic: Vector similarity via embeddings\n * 2. Lexical: Keyword matching via TF-IDF/BM25\n * 3. Symbolic: Structured metadata filtering\n *\n * @example\n * ```typescript\n * const hybrid = new HybridSearchManager(semanticSearch, rankedSearch);\n * const results = await hybrid.search(graph, 'machine learning', {\n * semanticWeight: 0.5,\n * lexicalWeight: 0.3,\n * symbolicWeight: 0.2,\n * symbolic: { tags: ['ai'] }\n * });\n * ```\n */\nexport class HybridSearchManager {\n private symbolicSearch: SymbolicSearch;\n\n constructor(\n private semanticSearch: SemanticSearch | null,\n private rankedSearch: RankedSearch\n ) {\n this.symbolicSearch = new SymbolicSearch();\n }\n\n /**\n * Perform hybrid search combining all three layers.\n *\n * @param graph - Knowledge graph to search\n * @param query - Search query text\n * @param options - Hybrid search options with weights\n * @returns Combined and ranked results\n */\n async search(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: Partial<HybridSearchOptions> = {}\n ): Promise<HybridSearchResult[]> {\n const {\n semanticWeight = DEFAULT_HYBRID_WEIGHTS.semantic,\n lexicalWeight = DEFAULT_HYBRID_WEIGHTS.lexical,\n symbolicWeight = DEFAULT_HYBRID_WEIGHTS.symbolic,\n semantic = {},\n lexical = {},\n symbolic = {},\n limit = SEMANTIC_SEARCH_LIMITS.DEFAULT_LIMIT,\n } = options;\n\n // Normalize weights\n const totalWeight = semanticWeight + lexicalWeight + symbolicWeight;\n const normSemantic = semanticWeight / totalWeight;\n const normLexical = lexicalWeight / totalWeight;\n const normSymbolic = symbolicWeight / totalWeight;\n\n // Execute searches in parallel\n const [semanticResults, lexicalResults, symbolicResults] = await Promise.all([\n this.executeSemanticSearch(graph, query, semantic, limit * 2),\n this.executeLexicalSearch(query, lexical, limit * 2),\n this.executeSymbolicSearch(graph.entities, symbolic),\n ]);\n\n // Merge results\n const merged = this.mergeResults(\n graph.entities,\n semanticResults,\n lexicalResults,\n symbolicResults,\n { semantic: normSemantic, lexical: normLexical, symbolic: normSymbolic }\n );\n\n // Sort by combined score and limit\n return merged\n .sort((a, b) => b.scores.combined - a.scores.combined)\n .slice(0, limit);\n }\n\n /**\n * Execute semantic search layer.\n */\n private async executeSemanticSearch(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: { minSimilarity?: number; topK?: number },\n limit: number\n ): Promise<Map<string, number>> {\n const results = new Map<string, number>();\n\n if (!this.semanticSearch) {\n return results; // Semantic search not available\n }\n\n try {\n const semanticResults = await this.semanticSearch.search(\n graph,\n query,\n options.topK ?? limit,\n options.minSimilarity ?? 0\n );\n\n for (const result of semanticResults) {\n results.set(result.entity.name, result.similarity);\n }\n } catch {\n // Semantic search may fail if not indexed\n }\n\n return results;\n }\n\n /**\n * Execute lexical search layer (TF-IDF/BM25).\n */\n private async executeLexicalSearch(\n query: string,\n _options: { useStopwords?: boolean; useStemming?: boolean },\n limit: number\n ): Promise<Map<string, number>> {\n const results = new Map<string, number>();\n\n try {\n const lexicalResults = await this.rankedSearch.searchNodesRanked(\n query,\n undefined, // tags\n undefined, // minImportance\n undefined, // maxImportance\n limit\n );\n\n // Normalize scores to 0-1 range\n const maxScore = Math.max(...lexicalResults.map(r => r.score), 1);\n for (const result of lexicalResults) {\n results.set(result.entity.name, result.score / maxScore);\n }\n } catch {\n // Lexical search may fail\n }\n\n return results;\n }\n\n /**\n * Execute symbolic search layer.\n */\n private executeSymbolicSearch(\n entities: readonly Entity[],\n filters: SymbolicFilters | undefined\n ): Map<string, number> {\n const results = new Map<string, number>();\n\n if (!filters || Object.keys(filters).length === 0) {\n // No symbolic filters, give all entities base score\n for (const entity of entities) {\n results.set(entity.name, 0.5);\n }\n return results;\n }\n\n const symbolicResults = this.symbolicSearch.search(entities, filters);\n for (const result of symbolicResults) {\n results.set(result.entity.name, result.score);\n }\n\n return results;\n }\n\n /**\n * Merge results from all three layers.\n */\n private mergeResults(\n entities: readonly Entity[],\n semanticScores: Map<string, number>,\n lexicalScores: Map<string, number>,\n symbolicScores: Map<string, number>,\n weights: { semantic: number; lexical: number; symbolic: number }\n ): HybridSearchResult[] {\n // Collect all unique entity names that have at least one non-zero score\n const allNames = new Set([\n ...semanticScores.keys(),\n ...lexicalScores.keys(),\n ...symbolicScores.keys(),\n ]);\n\n // Create entity lookup map\n const entityMap = new Map(entities.map(e => [e.name, e]));\n\n const results: HybridSearchResult[] = [];\n\n for (const name of allNames) {\n const entity = entityMap.get(name);\n if (!entity) continue;\n\n const semantic = semanticScores.get(name) ?? 0;\n const lexical = lexicalScores.get(name) ?? 0;\n const symbolic = symbolicScores.get(name) ?? 0;\n\n const combined =\n semantic * weights.semantic +\n lexical * weights.lexical +\n symbolic * weights.symbolic;\n\n const matchedLayers: ('semantic' | 'lexical' | 'symbolic')[] = [];\n if (semantic > 0) matchedLayers.push('semantic');\n if (lexical > 0) matchedLayers.push('lexical');\n if (symbolic > 0) matchedLayers.push('symbolic');\n\n // Skip if no layers matched meaningfully\n if (matchedLayers.length === 0) continue;\n\n results.push({\n entity,\n scores: { semantic, lexical, symbolic, combined },\n matchedLayers,\n });\n }\n\n return results;\n }\n\n /**\n * Search with full entity resolution.\n * Alias for search() since we now always resolve entities.\n */\n async searchWithEntities(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: Partial<HybridSearchOptions> = {}\n ): Promise<HybridSearchResult[]> {\n return this.search(graph, query, options);\n }\n\n /**\n * Get the symbolic search instance for direct access.\n */\n getSymbolicSearch(): SymbolicSearch {\n return this.symbolicSearch;\n }\n}\n","/**\n * Query Analyzer\n *\n * Phase 11: Extracts structured information from natural language queries\n * to enable intelligent search planning.\n *\n * @module search/QueryAnalyzer\n */\n\nimport type { QueryAnalysis, ExtractedEntity, TemporalRange } from '../types/index.js';\n\n/**\n * Query Analyzer extracts structured information from queries.\n *\n * Uses rule-based heuristics for reliable extraction of:\n * - Person names\n * - Location names\n * - Organization names\n * - Temporal references\n * - Question type\n * - Query complexity\n *\n * @example\n * ```typescript\n * const analyzer = new QueryAnalyzer();\n * const analysis = analyzer.analyze(\n * 'What projects did Alice work on last month?'\n * );\n * // { query: '...', entities: [...], persons: ['Alice'], temporalRange: { relative: 'last month' }, ... }\n * ```\n */\nexport class QueryAnalyzer {\n private personIndicators = ['Mr.', 'Mrs.', 'Ms.', 'Dr.', 'Prof.'];\n private temporalKeywords = [\n 'yesterday', 'today', 'tomorrow',\n 'last week', 'last month', 'last year',\n 'this week', 'this month', 'this year',\n 'next week', 'next month', 'next year',\n ];\n private questionKeywords = {\n factual: ['what', 'who', 'where', 'which'],\n temporal: ['when', 'how long', 'since', 'until'],\n comparative: ['compare', 'difference', 'vs', 'versus', 'better', 'worse'],\n aggregation: ['how many', 'count', 'total', 'sum', 'average'],\n 'multi-hop': ['and then', 'which means', 'therefore', 'related to'],\n conceptual: ['explain', 'why', 'how does', 'what is the meaning', 'understand'],\n };\n\n /**\n * Analyze a query using rule-based heuristics.\n * Main entry point - returns full QueryAnalysis.\n */\n analyze(query: string): QueryAnalysis {\n const lowerQuery = query.toLowerCase();\n const persons = this.extractPersons(query);\n const locations = this.extractLocations(query);\n const organizations = this.extractOrganizations(query);\n const questionType = this.detectQuestionType(lowerQuery);\n const complexity = this.estimateComplexity(query);\n\n // Build entities array from extracted names\n const entities: ExtractedEntity[] = [\n ...persons.map(name => ({ name, type: 'person' as const })),\n ...locations.map(name => ({ name, type: 'location' as const })),\n ...organizations.map(name => ({ name, type: 'organization' as const })),\n ];\n\n // Calculate confidence based on extraction quality\n const confidence = this.calculateConfidence(entities, complexity, questionType);\n\n return {\n query,\n entities,\n persons,\n locations,\n organizations,\n temporalRange: this.extractTemporalRange(query) ?? null,\n questionType,\n complexity,\n confidence,\n requiredInfoTypes: this.detectRequiredInfoTypes(lowerQuery),\n subQueries: this.decomposeQuery(query),\n };\n }\n\n /**\n * Calculate confidence score for the analysis.\n */\n private calculateConfidence(\n entities: ExtractedEntity[],\n complexity: 'low' | 'medium' | 'high',\n questionType: QueryAnalysis['questionType']\n ): number {\n let confidence = 0.5;\n\n // Higher confidence for simple queries\n if (complexity === 'low') confidence += 0.3;\n else if (complexity === 'medium') confidence += 0.1;\n\n // Higher confidence when entities are detected\n if (entities.length > 0) confidence += 0.1;\n\n // Lower confidence for conceptual queries (harder to satisfy)\n if (questionType === 'conceptual') confidence -= 0.2;\n\n return Math.max(0, Math.min(1, confidence));\n }\n\n /**\n * Extract person names from query.\n */\n private extractPersons(query: string): string[] {\n const persons: string[] = [];\n const words = query.split(/\\s+/);\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n // Check for titles followed by names\n if (this.personIndicators.some(ind => word.startsWith(ind))) {\n if (i + 1 < words.length) {\n persons.push(words[i + 1].replace(/[^a-zA-Z]/g, ''));\n }\n }\n // Check for capitalized words that might be names\n if (/^[A-Z][a-z]+$/.test(word) && i > 0 && !/^[A-Z]/.test(words[i - 1])) {\n persons.push(word);\n }\n }\n\n return [...new Set(persons)];\n }\n\n /**\n * Extract location names from query.\n */\n private extractLocations(query: string): string[] {\n const locationIndicators = ['in', 'at', 'from', 'to', 'near'];\n const locations: string[] = [];\n const words = query.split(/\\s+/);\n\n for (let i = 0; i < words.length; i++) {\n if (locationIndicators.includes(words[i].toLowerCase())) {\n if (i + 1 < words.length && /^[A-Z]/.test(words[i + 1])) {\n locations.push(words[i + 1].replace(/[^a-zA-Z]/g, ''));\n }\n }\n }\n\n return [...new Set(locations)];\n }\n\n /**\n * Extract organization names from query.\n */\n private extractOrganizations(query: string): string[] {\n const orgIndicators = ['Inc.', 'Corp.', 'LLC', 'Ltd.', 'Company', 'Co.'];\n const organizations: string[] = [];\n\n for (const indicator of orgIndicators) {\n const regex = new RegExp(`([A-Z][a-zA-Z]*)\\\\s*${indicator.replace('.', '\\\\.')}`, 'g');\n const matches = query.match(regex);\n if (matches) {\n organizations.push(...matches);\n }\n }\n\n return [...new Set(organizations)];\n }\n\n /**\n * Extract temporal range from query.\n */\n private extractTemporalRange(query: string): TemporalRange | undefined {\n const lowerQuery = query.toLowerCase();\n\n for (const keyword of this.temporalKeywords) {\n if (lowerQuery.includes(keyword)) {\n return { relative: keyword };\n }\n }\n\n // Check for date patterns\n const datePattern = /\\d{4}-\\d{2}-\\d{2}/g;\n const dates = query.match(datePattern);\n if (dates && dates.length >= 1) {\n return {\n start: dates[0],\n end: dates.length > 1 ? dates[1] : undefined,\n };\n }\n\n return undefined;\n }\n\n /**\n * Detect the type of question.\n */\n private detectQuestionType(query: string): QueryAnalysis['questionType'] {\n for (const [type, keywords] of Object.entries(this.questionKeywords)) {\n if (keywords.some(kw => query.includes(kw))) {\n return type as QueryAnalysis['questionType'];\n }\n }\n return 'factual';\n }\n\n /**\n * Estimate query complexity.\n */\n private estimateComplexity(query: string): QueryAnalysis['complexity'] {\n const wordCount = query.split(/\\s+/).length;\n const hasConjunctions = /\\b(and|or|but|then|therefore)\\b/i.test(query);\n const hasMultipleClauses = /[,;]/.test(query);\n\n if (wordCount > 20 || (hasConjunctions && hasMultipleClauses)) {\n return 'high';\n }\n if (wordCount > 10 || hasConjunctions || hasMultipleClauses) {\n return 'medium';\n }\n return 'low';\n }\n\n /**\n * Detect what types of information are being requested.\n */\n private detectRequiredInfoTypes(query: string): string[] {\n const infoTypes: string[] = [];\n\n if (/\\b(who|person|people|name)\\b/.test(query)) infoTypes.push('person');\n if (/\\b(where|location|place|city)\\b/.test(query)) infoTypes.push('location');\n if (/\\b(when|date|time|year|month)\\b/.test(query)) infoTypes.push('temporal');\n if (/\\b(how many|count|number|total)\\b/.test(query)) infoTypes.push('quantity');\n if (/\\b(why|reason|because)\\b/.test(query)) infoTypes.push('reason');\n if (/\\b(what|which|project|task)\\b/.test(query)) infoTypes.push('entity');\n\n return infoTypes;\n }\n\n /**\n * Decompose complex queries into sub-queries.\n */\n private decomposeQuery(query: string): string[] | undefined {\n // Split on conjunctions\n const parts = query.split(/\\b(and then|and|but|or)\\b/i)\n .map(p => p.trim())\n .filter(p => p && !/^(and then|and|but|or)$/i.test(p));\n\n if (parts.length > 1) {\n return parts;\n }\n\n return undefined;\n }\n}\n","/**\n * Query Planner\n *\n * Phase 11: Generates execution plans for queries based on analysis.\n *\n * @module search/QueryPlanner\n */\n\nimport type { QueryAnalysis, QueryPlan, SubQuery, SymbolicFilters } from '../types/index.js';\n\n/**\n * Query Planner generates execution plans from query analysis.\n *\n * Creates optimized plans that:\n * - Select appropriate search layers\n * - Determine execution strategy (parallel/sequential)\n * - Set up query dependencies\n * - Configure merge strategies\n *\n * @example\n * ```typescript\n * const analyzer = new QueryAnalyzer();\n * const planner = new QueryPlanner();\n *\n * const analysis = analyzer.analyze('Find projects by Alice');\n * const plan = planner.createPlan('Find projects by Alice', analysis);\n * // { originalQuery: '...', subQueries: [...], executionStrategy: 'iterative', ... }\n * ```\n */\nexport class QueryPlanner {\n /**\n * Create an execution plan from query analysis.\n */\n createPlan(query: string, analysis: QueryAnalysis): QueryPlan {\n const subQueries = this.createSubQueries(query, analysis);\n const executionStrategy = this.selectExecutionStrategy(subQueries);\n const mergeStrategy = this.selectMergeStrategy(analysis);\n\n return {\n originalQuery: query,\n subQueries,\n executionStrategy,\n mergeStrategy,\n estimatedComplexity: this.calculateComplexity(subQueries),\n };\n }\n\n /**\n * Create sub-queries from analysis.\n */\n private createSubQueries(query: string, analysis: QueryAnalysis): SubQuery[] {\n const subQueries: SubQuery[] = [];\n let id = 0;\n\n // If query was decomposed, create sub-query for each part\n if (analysis.subQueries && analysis.subQueries.length > 1) {\n for (const sq of analysis.subQueries) {\n subQueries.push({\n id: `sq_${id++}`,\n query: sq,\n targetLayer: this.selectLayer(analysis),\n priority: id === 1 ? 1 : 2,\n dependsOn: id > 1 ? [`sq_${id - 2}`] : undefined,\n });\n }\n } else {\n // Single query\n subQueries.push({\n id: `sq_${id}`,\n query,\n targetLayer: this.selectLayer(analysis),\n priority: 1,\n filters: this.buildFilters(analysis),\n });\n }\n\n return subQueries;\n }\n\n /**\n * Select the most appropriate search layer.\n */\n private selectLayer(analysis: QueryAnalysis): SubQuery['targetLayer'] {\n // Use symbolic for tag/type/date filtered queries\n if (analysis.temporalRange || analysis.requiredInfoTypes.includes('temporal')) {\n return 'symbolic';\n }\n // Use semantic for complex concept queries\n if (analysis.complexity === 'high' || analysis.questionType === 'comparative') {\n return 'semantic';\n }\n // Use hybrid for balanced approach\n return 'hybrid';\n }\n\n /**\n * Select execution strategy based on sub-queries.\n */\n private selectExecutionStrategy(subQueries: SubQuery[]): QueryPlan['executionStrategy'] {\n const hasDependencies = subQueries.some(sq => sq.dependsOn && sq.dependsOn.length > 0);\n if (hasDependencies) return 'sequential';\n if (subQueries.length > 1) return 'parallel';\n return 'iterative';\n }\n\n /**\n * Select merge strategy based on question type.\n */\n private selectMergeStrategy(analysis: QueryAnalysis): QueryPlan['mergeStrategy'] {\n switch (analysis.questionType) {\n case 'aggregation': return 'union';\n case 'comparative': return 'intersection';\n default: return 'weighted';\n }\n }\n\n /**\n * Build symbolic filters from analysis.\n */\n private buildFilters(analysis: QueryAnalysis): SymbolicFilters | undefined {\n const filters: SymbolicFilters = {};\n let hasFilters = false;\n\n if (analysis.temporalRange) {\n filters.dateRange = {\n start: analysis.temporalRange.start || '',\n end: analysis.temporalRange.end || '',\n };\n hasFilters = true;\n }\n\n return hasFilters ? filters : undefined;\n }\n\n /**\n * Calculate plan complexity score.\n */\n private calculateComplexity(subQueries: SubQuery[]): number {\n let complexity = subQueries.length;\n for (const sq of subQueries) {\n if (sq.dependsOn) complexity += sq.dependsOn.length * 0.5;\n if (sq.filters) complexity += 0.5;\n }\n return Math.min(complexity, 10);\n }\n}\n","/**\n * Reflection Manager\n *\n * Phase 11: Implements reflection-based iterative retrieval\n * that refines results until adequate.\n *\n * @module search/ReflectionManager\n */\n\nimport type {\n ReadonlyKnowledgeGraph,\n QueryAnalysis,\n HybridSearchResult,\n HybridSearchOptions,\n} from '../types/index.js';\nimport type { HybridSearchManager } from './HybridSearchManager.js';\nimport type { QueryAnalyzer } from './QueryAnalyzer.js';\n\n/**\n * Options for reflection-based retrieval.\n */\nexport interface ReflectionOptions {\n /** Maximum number of reflection iterations (default: 3) */\n maxIterations?: number;\n /** Adequacy threshold 0-1 (default: 0.7) */\n adequacyThreshold?: number;\n /** Minimum results required (default: 3) */\n minResults?: number;\n /** Hybrid search options */\n searchOptions?: Partial<HybridSearchOptions>;\n /** Phase 12 Sprint 4: Progressive limit increase factor (default: 1.5) */\n limitIncreaseFactor?: number;\n /** Phase 12 Sprint 4: Initial search limit (default: 10) */\n initialLimit?: number;\n /** Phase 12 Sprint 4: Focus on specific missing info types */\n focusMissingTypes?: boolean;\n}\n\n/**\n * Phase 12 Sprint 4: Refinement history entry.\n */\nexport interface RefinementHistoryEntry {\n /** Iteration number (1-based) */\n iteration: number;\n /** Query used in this iteration */\n query: string;\n /** Search limit used */\n limit: number;\n /** Results found in this iteration */\n resultsFound: number;\n /** Adequacy score after this iteration */\n adequacyScore: number;\n /** Reason for refinement (if not final) */\n refinementReason?: string;\n /** Missing info types that triggered refinement */\n missingInfoTypes?: string[];\n}\n\n/**\n * Result of reflection-based retrieval.\n */\nexport interface ReflectionResult {\n results: HybridSearchResult[];\n iterations: number;\n adequate: boolean;\n refinements: string[];\n adequacyScore: number;\n /** Phase 12 Sprint 4: Detailed refinement history */\n refinementHistory: RefinementHistoryEntry[];\n /** Phase 12 Sprint 4: Final search limit used */\n finalLimit: number;\n}\n\n/**\n * Reflection Manager for iterative retrieval refinement.\n *\n * Implements the SimpleMem-inspired reflection loop:\n * 1. Execute initial search\n * 2. Check result adequacy\n * 3. If inadequate, refine query and repeat\n * 4. Combine results from all iterations\n *\n * @example\n * ```typescript\n * const reflection = new ReflectionManager(hybridSearch, analyzer);\n * const result = await reflection.retrieveWithReflection(\n * graph,\n * 'What projects did Alice work on?',\n * { maxIterations: 3 }\n * );\n * ```\n */\nexport class ReflectionManager {\n constructor(\n private hybridSearch: HybridSearchManager,\n private analyzer: QueryAnalyzer\n ) {}\n\n /**\n * Perform retrieval with reflection-based refinement.\n *\n * Phase 12 Sprint 4 enhancements:\n * - Progressive limit increase per round\n * - Focused query refinement based on missing information\n * - Detailed refinement history tracking\n */\n async retrieveWithReflection(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: ReflectionOptions = {}\n ): Promise<ReflectionResult> {\n const {\n maxIterations = 3,\n adequacyThreshold = 0.7,\n minResults = 3,\n searchOptions = {},\n limitIncreaseFactor = 1.5,\n initialLimit = 10,\n focusMissingTypes = true,\n } = options;\n\n const allResults: HybridSearchResult[] = [];\n const refinements: string[] = [];\n const refinementHistory: RefinementHistoryEntry[] = [];\n let currentQuery = query;\n let iteration = 0;\n let adequacyScore = 0;\n let currentLimit = initialLimit;\n\n // Get initial analysis for tracking\n const analysis = this.analyzer.analyze(query);\n\n while (iteration < maxIterations) {\n iteration++;\n\n // Calculate current limit with progressive increase\n currentLimit = Math.round(initialLimit * Math.pow(limitIncreaseFactor, iteration - 1));\n\n // Execute search with current limit\n const results = await this.hybridSearch.searchWithEntities(\n graph,\n currentQuery,\n { ...searchOptions, limit: currentLimit }\n );\n\n // Track results found this iteration (before deduplication)\n const newResultsCount = results.filter(\n r => !allResults.some(existing => existing.entity.name === r.entity.name)\n ).length;\n\n // Add new results (deduplicated)\n for (const result of results) {\n if (!allResults.some(r => r.entity.name === result.entity.name)) {\n allResults.push(result);\n }\n }\n\n // Check adequacy\n const iterAnalysis = this.analyzer.analyze(currentQuery);\n adequacyScore = this.calculateAdequacy(allResults, iterAnalysis, minResults);\n\n // Determine missing info types\n const missingInfoTypes = this.findMissingInfoTypes(allResults, analysis);\n\n // Record history entry\n const historyEntry: RefinementHistoryEntry = {\n iteration,\n query: currentQuery,\n limit: currentLimit,\n resultsFound: newResultsCount,\n adequacyScore,\n };\n\n if (adequacyScore >= adequacyThreshold) {\n refinementHistory.push(historyEntry);\n break;\n }\n\n // Refine query if not adequate\n const refinedQuery = focusMissingTypes && missingInfoTypes.length > 0\n ? this.refineQueryFocused(currentQuery, allResults, analysis, missingInfoTypes)\n : this.refineQuery(currentQuery, allResults, analysis);\n\n if (refinedQuery === currentQuery) {\n // No refinement possible\n historyEntry.refinementReason = 'No further refinement possible';\n refinementHistory.push(historyEntry);\n break;\n }\n\n // Record refinement details in history\n historyEntry.refinementReason = this.getRefinementReason(\n currentQuery,\n refinedQuery,\n missingInfoTypes\n );\n historyEntry.missingInfoTypes = missingInfoTypes;\n refinementHistory.push(historyEntry);\n\n refinements.push(refinedQuery);\n currentQuery = refinedQuery;\n }\n\n return {\n results: allResults.sort((a, b) => b.scores.combined - a.scores.combined),\n iterations: iteration,\n adequate: adequacyScore >= adequacyThreshold,\n refinements,\n adequacyScore,\n refinementHistory,\n finalLimit: currentLimit,\n };\n }\n\n /**\n * Find missing information types based on analysis requirements.\n * @private\n */\n private findMissingInfoTypes(\n results: HybridSearchResult[],\n analysis: QueryAnalysis\n ): string[] {\n const coveredTypes = new Set<string>();\n\n for (const result of results) {\n const entityType = result.entity.entityType.toLowerCase();\n coveredTypes.add(entityType);\n\n // Map entity types to info types\n if (['person', 'people', 'user', 'employee'].includes(entityType)) {\n coveredTypes.add('person');\n }\n if (['location', 'place', 'city', 'country', 'address'].includes(entityType)) {\n coveredTypes.add('location');\n }\n if (result.entity.createdAt || result.entity.lastModified) {\n coveredTypes.add('temporal');\n }\n // Entity type covers 'entity' requirement\n coveredTypes.add('entity');\n }\n\n // Find which required types are missing\n return analysis.requiredInfoTypes.filter(type => !coveredTypes.has(type));\n }\n\n /**\n * Refine query with focus on missing information types.\n * @private\n */\n private refineQueryFocused(\n query: string,\n results: HybridSearchResult[],\n analysis: QueryAnalysis,\n missingInfoTypes: string[]\n ): string {\n const additions: string[] = [];\n\n for (const missingType of missingInfoTypes) {\n switch (missingType) {\n case 'person':\n additions.push('person people who');\n break;\n case 'location':\n additions.push('location place where');\n break;\n case 'temporal':\n additions.push('when date time');\n break;\n case 'quantity':\n additions.push('count number how many');\n break;\n case 'reason':\n additions.push('reason why because');\n break;\n }\n }\n\n if (additions.length > 0) {\n // Add relevant keywords to query\n return `${query} ${additions.slice(0, 2).join(' ')}`;\n }\n\n // Fallback to standard refinement\n return this.refineQuery(query, results, analysis);\n }\n\n /**\n * Get human-readable reason for refinement.\n * @private\n */\n private getRefinementReason(\n _originalQuery: string,\n refinedQuery: string,\n missingInfoTypes: string[]\n ): string {\n if (missingInfoTypes.length > 0) {\n return `Added keywords for missing info types: ${missingInfoTypes.join(', ')}`;\n }\n\n if (refinedQuery.includes('person people')) {\n return 'Expanded query to find person-related entities';\n }\n\n if (refinedQuery.includes('recent history timeline')) {\n return 'Added temporal context to query';\n }\n\n return 'Broadened query by removing constraints';\n }\n\n /**\n * Calculate result adequacy score.\n */\n private calculateAdequacy(\n results: HybridSearchResult[],\n analysis: QueryAnalysis,\n minResults: number\n ): number {\n let score = 0;\n const weights = { quantity: 0.4, diversity: 0.3, relevance: 0.3 };\n\n // Quantity: Do we have enough results?\n const quantityScore = Math.min(results.length / minResults, 1);\n score += quantityScore * weights.quantity;\n\n // Diversity: Do results cover different entity types?\n const types = new Set(results.map(r => r.entity.entityType));\n const diversityScore = Math.min(types.size / 3, 1);\n score += diversityScore * weights.diversity;\n\n // Relevance: Average combined score\n const avgScore = results.length > 0\n ? results.reduce((sum, r) => sum + r.scores.combined, 0) / results.length\n : 0;\n score += avgScore * weights.relevance;\n\n // Suppress unused parameter warning - analysis reserved for future enhancements\n void analysis;\n\n return score;\n }\n\n /**\n * Refine query based on current results and analysis.\n */\n private refineQuery(\n query: string,\n results: HybridSearchResult[],\n analysis: QueryAnalysis\n ): string {\n // Check what information types are missing\n const coveredTypes = new Set<string>();\n for (const result of results) {\n coveredTypes.add(result.entity.entityType.toLowerCase());\n }\n\n // If looking for persons but no person results, refine\n if (analysis.requiredInfoTypes.includes('person') && !coveredTypes.has('person')) {\n return `${query} person people`;\n }\n\n // If temporal query but no temporal context, add time hint\n if (analysis.temporalRange && results.length < 3) {\n return `${query} recent history timeline`;\n }\n\n // Broaden query by removing constraints\n const broader = query.replace(/\\b(only|just|exactly|specific)\\b/gi, '');\n if (broader !== query) {\n return broader.trim();\n }\n\n return query;\n }\n}\n","/**\n * BM25 Search\n *\n * BM25 (Best Matching 25) relevance scoring algorithm for lexical search.\n * Provides improved ranking over TF-IDF by incorporating document length normalization.\n *\n * Phase 12 Sprint 3: Search Algorithm Optimization\n *\n * @module search/BM25Search\n */\n\nimport type { Entity, SearchResult } from '../types/index.js';\nimport type { GraphStorage } from '../core/GraphStorage.js';\nimport { SEARCH_LIMITS } from '../utils/constants.js';\n\n/**\n * Common English stopwords to filter from queries and documents.\n * These words are too common to provide meaningful ranking signal.\n */\nexport const STOPWORDS = new Set([\n 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for', 'from',\n 'has', 'he', 'in', 'is', 'it', 'its', 'of', 'on', 'or', 'that',\n 'the', 'to', 'was', 'were', 'will', 'with', 'you', 'your',\n 'this', 'but', 'they', 'have', 'had', 'what', 'when', 'where',\n 'who', 'which', 'why', 'how', 'all', 'each', 'every', 'both',\n 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'not',\n 'only', 'own', 'same', 'so', 'than', 'too', 'very', 'can',\n 'just', 'should', 'now', 'also', 'being', 'been', 'would',\n 'could', 'into', 'over', 'after', 'before', 'between', 'under',\n 'again', 'then', 'once', 'here', 'there', 'any', 'about',\n]);\n\n/**\n * BM25 index entry for a single document.\n */\nexport interface BM25DocumentEntry {\n /** Entity name */\n entityName: string;\n /** Term frequencies in this document */\n termFreqs: Map<string, number>;\n /** Total number of tokens in document */\n docLength: number;\n}\n\n/**\n * BM25 index structure.\n */\nexport interface BM25Index {\n /** Document entries keyed by entity name */\n documents: Map<string, BM25DocumentEntry>;\n /** Document frequency for each term (number of docs containing term) */\n documentFrequency: Map<string, number>;\n /** Average document length */\n avgDocLength: number;\n /** Total number of documents */\n totalDocs: number;\n}\n\n/**\n * BM25 configuration parameters.\n */\nexport interface BM25Config {\n /** Term frequency saturation parameter (default: 1.2) */\n k1: number;\n /** Length normalization parameter (default: 0.75) */\n b: number;\n}\n\n/**\n * Default BM25 parameters based on research recommendations.\n */\nexport const DEFAULT_BM25_CONFIG: BM25Config = {\n k1: 1.2,\n b: 0.75,\n};\n\n/**\n * BM25 Search implementation.\n *\n * BM25 improves over TF-IDF by:\n * 1. Saturating term frequency - prevents long documents from dominating\n * 2. Document length normalization - accounts for varying document sizes\n *\n * Formula:\n * score(D,Q) = sum_i( IDF(qi) * (f(qi,D) * (k1 + 1)) / (f(qi,D) + k1 * (1 - b + b * |D|/avgdl)) )\n *\n * Where:\n * - f(qi,D) is the term frequency of qi in document D\n * - |D| is the length of document D\n * - avgdl is the average document length\n * - k1 and b are free parameters\n *\n * @example\n * ```typescript\n * const bm25 = new BM25Search(storage);\n * await bm25.buildIndex();\n * const results = await bm25.search('machine learning');\n * ```\n */\nexport class BM25Search {\n private index: BM25Index | null = null;\n private config: BM25Config;\n\n constructor(\n private storage: GraphStorage,\n config: Partial<BM25Config> = {}\n ) {\n this.config = { ...DEFAULT_BM25_CONFIG, ...config };\n }\n\n /**\n * Get the current configuration.\n */\n getConfig(): BM25Config {\n return { ...this.config };\n }\n\n /**\n * Update configuration parameters.\n *\n * @param config - New configuration values\n */\n setConfig(config: Partial<BM25Config>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Tokenize text into lowercase terms with stopword filtering.\n *\n * @param text - Text to tokenize\n * @param filterStopwords - Whether to filter stopwords (default: true)\n * @returns Array of lowercase tokens\n */\n tokenize(text: string, filterStopwords: boolean = true): string[] {\n const tokens = text\n .toLowerCase()\n .replace(/[^\\w\\s]/g, ' ')\n .split(/\\s+/)\n .filter(token => token.length > 0);\n\n if (filterStopwords) {\n return tokens.filter(token => !STOPWORDS.has(token));\n }\n return tokens;\n }\n\n /**\n * Build the BM25 index from the current graph.\n *\n * Should be called after significant graph changes.\n */\n async buildIndex(): Promise<void> {\n const graph = await this.storage.loadGraph();\n const documents = new Map<string, BM25DocumentEntry>();\n const documentFrequency = new Map<string, number>();\n const termsSeen = new Set<string>();\n let totalDocLength = 0;\n\n // First pass: tokenize all documents and count term frequencies\n for (const entity of graph.entities) {\n const text = this.entityToText(entity);\n const tokens = this.tokenize(text);\n const termFreqs = new Map<string, number>();\n\n // Count term frequencies for this document\n for (const token of tokens) {\n termFreqs.set(token, (termFreqs.get(token) || 0) + 1);\n }\n\n // Track which terms appear in this document (for IDF calculation)\n termsSeen.clear();\n for (const token of tokens) {\n if (!termsSeen.has(token)) {\n termsSeen.add(token);\n documentFrequency.set(token, (documentFrequency.get(token) || 0) + 1);\n }\n }\n\n const entry: BM25DocumentEntry = {\n entityName: entity.name,\n termFreqs,\n docLength: tokens.length,\n };\n\n documents.set(entity.name, entry);\n totalDocLength += tokens.length;\n }\n\n const totalDocs = documents.size;\n const avgDocLength = totalDocs > 0 ? totalDocLength / totalDocs : 0;\n\n this.index = {\n documents,\n documentFrequency,\n avgDocLength,\n totalDocs,\n };\n }\n\n /**\n * Search using the BM25 algorithm.\n *\n * @param query - Search query\n * @param limit - Maximum results to return\n * @returns Array of search results sorted by BM25 score\n */\n async search(query: string, limit: number = SEARCH_LIMITS.DEFAULT): Promise<SearchResult[]> {\n const effectiveLimit = Math.min(limit, SEARCH_LIMITS.MAX);\n\n // Ensure index is built\n if (!this.index) {\n await this.buildIndex();\n }\n\n if (!this.index || this.index.documents.size === 0) {\n return [];\n }\n\n const graph = await this.storage.loadGraph();\n const entityMap = new Map(graph.entities.map(e => [e.name, e]));\n\n // Tokenize query\n const queryTerms = this.tokenize(query);\n if (queryTerms.length === 0) {\n return [];\n }\n\n const { k1, b } = this.config;\n const { documents, documentFrequency, avgDocLength, totalDocs } = this.index;\n const results: SearchResult[] = [];\n\n // Calculate BM25 score for each document\n for (const [entityName, docEntry] of documents) {\n const entity = entityMap.get(entityName);\n if (!entity) continue;\n\n let score = 0;\n const matchedFields: SearchResult['matchedFields'] = {};\n\n for (const term of queryTerms) {\n const tf = docEntry.termFreqs.get(term) || 0;\n if (tf === 0) continue;\n\n // Calculate IDF\n const df = documentFrequency.get(term) || 0;\n const idf = df > 0 ? Math.log((totalDocs - df + 0.5) / (df + 0.5) + 1) : 0;\n\n // Calculate BM25 score component\n const numerator = tf * (k1 + 1);\n const denominator = tf + k1 * (1 - b + b * (docEntry.docLength / avgDocLength));\n const termScore = idf * (numerator / denominator);\n\n score += termScore;\n\n // Track which fields matched\n if (entity.name.toLowerCase().includes(term)) {\n matchedFields.name = true;\n }\n if (entity.entityType.toLowerCase().includes(term)) {\n matchedFields.entityType = true;\n }\n const matchedObs = entity.observations.filter(o =>\n o.toLowerCase().includes(term)\n );\n if (matchedObs.length > 0) {\n matchedFields.observations = matchedObs;\n }\n }\n\n if (score > 0) {\n results.push({\n entity,\n score,\n matchedFields,\n });\n }\n }\n\n // Sort by score descending and limit\n return results\n .sort((a, b) => b.score - a.score)\n .slice(0, effectiveLimit);\n }\n\n /**\n * Update the index for changed entities.\n *\n * @param changedEntityNames - Names of entities that changed\n */\n async update(changedEntityNames: Set<string>): Promise<void> {\n if (!this.index) {\n await this.buildIndex();\n return;\n }\n\n const graph = await this.storage.loadGraph();\n const entityMap = new Map(graph.entities.map(e => [e.name, e]));\n\n // Process each changed entity\n for (const entityName of changedEntityNames) {\n const entity = entityMap.get(entityName);\n const existingEntry = this.index.documents.get(entityName);\n\n if (existingEntry) {\n // Remove old term frequencies from document frequency counts\n for (const [term] of existingEntry.termFreqs) {\n const df = this.index.documentFrequency.get(term) || 0;\n if (df <= 1) {\n this.index.documentFrequency.delete(term);\n } else {\n this.index.documentFrequency.set(term, df - 1);\n }\n }\n this.index.documents.delete(entityName);\n }\n\n if (entity) {\n // Add new entry\n const text = this.entityToText(entity);\n const tokens = this.tokenize(text);\n const termFreqs = new Map<string, number>();\n const termsSeen = new Set<string>();\n\n for (const token of tokens) {\n termFreqs.set(token, (termFreqs.get(token) || 0) + 1);\n if (!termsSeen.has(token)) {\n termsSeen.add(token);\n this.index.documentFrequency.set(\n token,\n (this.index.documentFrequency.get(token) || 0) + 1\n );\n }\n }\n\n const entry: BM25DocumentEntry = {\n entityName: entity.name,\n termFreqs,\n docLength: tokens.length,\n };\n\n this.index.documents.set(entityName, entry);\n }\n }\n\n // Recalculate average document length\n this.index.totalDocs = this.index.documents.size;\n let totalLength = 0;\n for (const doc of this.index.documents.values()) {\n totalLength += doc.docLength;\n }\n this.index.avgDocLength = this.index.totalDocs > 0\n ? totalLength / this.index.totalDocs\n : 0;\n }\n\n /**\n * Remove an entity from the index.\n *\n * @param entityName - Name of entity to remove\n */\n remove(entityName: string): boolean {\n if (!this.index) {\n return false;\n }\n\n const entry = this.index.documents.get(entityName);\n if (!entry) {\n return false;\n }\n\n // Update document frequency counts\n for (const [term] of entry.termFreqs) {\n const df = this.index.documentFrequency.get(term) || 0;\n if (df <= 1) {\n this.index.documentFrequency.delete(term);\n } else {\n this.index.documentFrequency.set(term, df - 1);\n }\n }\n\n this.index.documents.delete(entityName);\n\n // Update totals\n this.index.totalDocs = this.index.documents.size;\n let totalLength = 0;\n for (const doc of this.index.documents.values()) {\n totalLength += doc.docLength;\n }\n this.index.avgDocLength = this.index.totalDocs > 0\n ? totalLength / this.index.totalDocs\n : 0;\n\n return true;\n }\n\n /**\n * Clear the index.\n */\n clearIndex(): void {\n this.index = null;\n }\n\n /**\n * Check if the index is built.\n */\n isIndexed(): boolean {\n return this.index !== null;\n }\n\n /**\n * Get index statistics.\n */\n getIndexStats(): { documents: number; terms: number; avgDocLength: number } | null {\n if (!this.index) {\n return null;\n }\n return {\n documents: this.index.documents.size,\n terms: this.index.documentFrequency.size,\n avgDocLength: this.index.avgDocLength,\n };\n }\n\n /**\n * Convert an entity to searchable text.\n */\n private entityToText(entity: Entity): string {\n return [entity.name, entity.entityType, ...entity.observations].join(' ');\n }\n}\n","/**\n * Optimized Inverted Index\n *\n * Memory-efficient inverted index using integer IDs and Uint32Array\n * for fast multi-term intersection queries.\n *\n * Phase 12 Sprint 3: Search Algorithm Optimization\n *\n * @module search/OptimizedInvertedIndex\n */\n\n/**\n * Statistics about memory usage.\n */\nexport interface IndexMemoryUsage {\n /** Total bytes used by posting lists */\n postingListBytes: number;\n /** Total bytes used by ID map */\n idMapBytes: number;\n /** Total bytes used by term index */\n termIndexBytes: number;\n /** Total estimated memory usage in bytes */\n totalBytes: number;\n /** Number of unique terms */\n termCount: number;\n /** Number of documents indexed */\n documentCount: number;\n}\n\n/**\n * Result from a posting list lookup.\n */\nexport interface PostingListResult {\n /** Term that was looked up */\n term: string;\n /** Document IDs containing the term (sorted) */\n docIds: Uint32Array;\n}\n\n/**\n * Optimized Inverted Index using integer document IDs.\n *\n * Memory Optimizations:\n * 1. Uses integer IDs instead of string entity names\n * 2. Stores posting lists as Uint32Array (4 bytes per ID vs ~20+ bytes per string)\n * 3. Maintains sorted posting lists for efficient intersection\n *\n * Performance Optimizations:\n * 1. Sorted array intersection is O(n+m) where n,m are posting list lengths\n * 2. Early termination when one list is exhausted\n * 3. Binary search available for unbalanced list sizes\n *\n * @example\n * ```typescript\n * const index = new OptimizedInvertedIndex();\n * index.addDocument('entity1', ['machine', 'learning', 'ai']);\n * index.addDocument('entity2', ['deep', 'learning', 'neural']);\n *\n * // Find documents containing both 'machine' AND 'learning'\n * const results = index.intersect(['machine', 'learning']);\n * console.log(results); // ['entity1']\n * ```\n */\nexport class OptimizedInvertedIndex {\n /** Map from entity name to integer ID */\n private entityToId: Map<string, number> = new Map();\n\n /** Map from integer ID to entity name */\n private idToEntity: Map<number, string> = new Map();\n\n /** Next available ID */\n private nextId: number = 0;\n\n /** Inverted index: term -> sorted array of document IDs */\n private postingLists: Map<string, Uint32Array> = new Map();\n\n /** Temporary posting lists (before finalization) */\n private tempPostingLists: Map<string, number[]> = new Map();\n\n /** Whether the index is finalized (posting lists converted to Uint32Array) */\n private finalized: boolean = false;\n\n /**\n * Add a document to the index.\n *\n * @param entityName - Unique document identifier\n * @param terms - Array of terms in the document (should be lowercase)\n */\n addDocument(entityName: string, terms: string[]): void {\n // Unfinalize if already finalized (allows incremental updates)\n if (this.finalized) {\n this.unfinalize();\n }\n\n // Get or assign document ID\n let docId = this.entityToId.get(entityName);\n if (docId === undefined) {\n docId = this.nextId++;\n this.entityToId.set(entityName, docId);\n this.idToEntity.set(docId, entityName);\n }\n\n // Add unique terms to posting lists\n const seenTerms = new Set<string>();\n for (const term of terms) {\n if (seenTerms.has(term)) continue;\n seenTerms.add(term);\n\n let postingList = this.tempPostingLists.get(term);\n if (!postingList) {\n postingList = [];\n this.tempPostingLists.set(term, postingList);\n }\n\n // Only add if not already present (maintains sorted order if added in order)\n if (postingList.length === 0 || postingList[postingList.length - 1] !== docId) {\n postingList.push(docId);\n }\n }\n }\n\n /**\n * Remove a document from the index.\n *\n * @param entityName - Document to remove\n * @returns True if document was found and removed\n */\n removeDocument(entityName: string): boolean {\n const docId = this.entityToId.get(entityName);\n if (docId === undefined) {\n return false;\n }\n\n // Unfinalize if needed\n if (this.finalized) {\n this.unfinalize();\n }\n\n // Remove from all posting lists\n for (const [term, postingList] of this.tempPostingLists) {\n const idx = postingList.indexOf(docId);\n if (idx !== -1) {\n postingList.splice(idx, 1);\n if (postingList.length === 0) {\n this.tempPostingLists.delete(term);\n }\n }\n }\n\n // Remove ID mappings\n this.entityToId.delete(entityName);\n this.idToEntity.delete(docId);\n\n return true;\n }\n\n /**\n * Finalize the index by converting posting lists to Uint32Array.\n *\n * This should be called after bulk indexing for optimal memory usage.\n * The index can still be updated after finalization, but it will\n * temporarily use more memory during updates.\n */\n finalize(): void {\n if (this.finalized) return;\n\n // Convert temp posting lists to Uint32Array and sort\n this.postingLists.clear();\n for (const [term, list] of this.tempPostingLists) {\n // Sort and convert to Uint32Array\n list.sort((a, b) => a - b);\n const arr = new Uint32Array(list);\n this.postingLists.set(term, arr);\n }\n\n // Clear temp posting lists to save memory\n this.tempPostingLists.clear();\n this.finalized = true;\n }\n\n /**\n * Convert finalized index back to mutable format.\n */\n private unfinalize(): void {\n if (!this.finalized) return;\n\n // Convert Uint32Array back to regular arrays\n this.tempPostingLists.clear();\n for (const [term, arr] of this.postingLists) {\n this.tempPostingLists.set(term, Array.from(arr));\n }\n\n this.postingLists.clear();\n this.finalized = false;\n }\n\n /**\n * Get posting list for a term.\n *\n * @param term - Term to look up\n * @returns Posting list result or null if term not found\n */\n getPostingList(term: string): PostingListResult | null {\n if (this.finalized) {\n const arr = this.postingLists.get(term);\n if (!arr) return null;\n return { term, docIds: arr };\n } else {\n const list = this.tempPostingLists.get(term);\n if (!list) return null;\n // Sort and return as Uint32Array\n const sorted = list.slice().sort((a, b) => a - b);\n return { term, docIds: new Uint32Array(sorted) };\n }\n }\n\n /**\n * Perform intersection of posting lists for multiple terms.\n *\n * Returns entity names that contain ALL specified terms.\n *\n * @param terms - Array of terms to intersect\n * @returns Array of entity names containing all terms\n */\n intersect(terms: string[]): string[] {\n if (terms.length === 0) {\n return [];\n }\n\n // Ensure finalized for optimal performance\n if (!this.finalized) {\n this.finalize();\n }\n\n // Get posting lists for all terms\n const postingLists: Uint32Array[] = [];\n for (const term of terms) {\n const list = this.postingLists.get(term);\n if (!list || list.length === 0) {\n // If any term has no posting list, intersection is empty\n return [];\n }\n postingLists.push(list);\n }\n\n // Sort by length (smallest first for early termination)\n postingLists.sort((a, b) => a.length - b.length);\n\n // Perform multi-way sorted intersection\n let result = postingLists[0];\n for (let i = 1; i < postingLists.length; i++) {\n result = this.intersectTwo(result, postingLists[i]);\n if (result.length === 0) {\n return [];\n }\n }\n\n // Convert IDs back to entity names\n return Array.from(result).map(id => this.idToEntity.get(id)!);\n }\n\n /**\n * Perform union of posting lists for multiple terms.\n *\n * Returns entity names that contain ANY of the specified terms.\n *\n * @param terms - Array of terms to union\n * @returns Array of entity names containing any term\n */\n union(terms: string[]): string[] {\n if (terms.length === 0) {\n return [];\n }\n\n // Ensure finalized for optimal performance\n if (!this.finalized) {\n this.finalize();\n }\n\n // Collect all unique document IDs\n const allIds = new Set<number>();\n for (const term of terms) {\n const list = this.postingLists.get(term);\n if (list) {\n for (const id of list) {\n allIds.add(id);\n }\n }\n }\n\n // Convert IDs back to entity names\n return Array.from(allIds).map(id => this.idToEntity.get(id)!);\n }\n\n /**\n * Get entities containing a single term.\n *\n * @param term - Term to search for\n * @returns Array of entity names containing the term\n */\n search(term: string): string[] {\n if (!this.finalized) {\n const list = this.tempPostingLists.get(term);\n if (!list) return [];\n return list.map(id => this.idToEntity.get(id)!);\n }\n\n const list = this.postingLists.get(term);\n if (!list) return [];\n return Array.from(list).map(id => this.idToEntity.get(id)!);\n }\n\n /**\n * Intersect two sorted Uint32Arrays.\n *\n * Uses merge-style intersection which is O(n+m).\n */\n private intersectTwo(a: Uint32Array, b: Uint32Array): Uint32Array {\n const result: number[] = [];\n let i = 0;\n let j = 0;\n\n while (i < a.length && j < b.length) {\n if (a[i] === b[j]) {\n result.push(a[i]);\n i++;\n j++;\n } else if (a[i] < b[j]) {\n i++;\n } else {\n j++;\n }\n }\n\n return new Uint32Array(result);\n }\n\n /**\n * Get memory usage statistics.\n */\n getMemoryUsage(): IndexMemoryUsage {\n let postingListBytes = 0;\n let termCount = 0;\n\n if (this.finalized) {\n for (const arr of this.postingLists.values()) {\n // Uint32Array uses 4 bytes per element\n postingListBytes += arr.byteLength;\n termCount++;\n }\n } else {\n for (const list of this.tempPostingLists.values()) {\n // Regular array uses 8 bytes per element (64-bit numbers in V8)\n // Plus array overhead\n postingListBytes += list.length * 8 + 32; // Approximate overhead\n termCount++;\n }\n }\n\n // Estimate ID map overhead\n // Map has ~100 bytes overhead + ~50 bytes per entry for string keys\n const idMapBytes =\n 100 +\n this.entityToId.size * 50 +\n this.idToEntity.size * 8; // number key is ~8 bytes\n\n // Estimate term index overhead\n // ~50 bytes per term entry (key + pointer)\n const termIndexBytes = termCount * 50;\n\n return {\n postingListBytes,\n idMapBytes,\n termIndexBytes,\n totalBytes: postingListBytes + idMapBytes + termIndexBytes,\n termCount,\n documentCount: this.entityToId.size,\n };\n }\n\n /**\n * Clear the entire index.\n */\n clear(): void {\n this.entityToId.clear();\n this.idToEntity.clear();\n this.postingLists.clear();\n this.tempPostingLists.clear();\n this.nextId = 0;\n this.finalized = false;\n }\n\n /**\n * Get the number of documents in the index.\n */\n get documentCount(): number {\n return this.entityToId.size;\n }\n\n /**\n * Get the number of unique terms in the index.\n */\n get termCount(): number {\n return this.finalized\n ? this.postingLists.size\n : this.tempPostingLists.size;\n }\n\n /**\n * Check if an entity is indexed.\n */\n hasDocument(entityName: string): boolean {\n return this.entityToId.has(entityName);\n }\n\n /**\n * Check if a term exists in the index.\n */\n hasTerm(term: string): boolean {\n return this.finalized\n ? this.postingLists.has(term)\n : this.tempPostingLists.has(term);\n }\n}\n","/**\n * Hybrid Scorer\n *\n * Combines semantic, lexical, and symbolic search scores with\n * min-max normalization and configurable weights.\n *\n * Phase 12 Sprint 3: Search Algorithm Optimization\n *\n * @module search/HybridScorer\n */\n\nimport type { Entity } from '../types/index.js';\n\n/**\n * Result from semantic search layer.\n */\nexport interface SemanticLayerResult {\n /** Entity name */\n entityName: string;\n /** Similarity score (typically 0-1 for cosine similarity) */\n similarity: number;\n /** The matched entity (if resolved) */\n entity?: Entity;\n}\n\n/**\n * Result from lexical search layer (TF-IDF or BM25).\n */\nexport interface LexicalSearchResult {\n /** Entity name */\n entityName: string;\n /** Relevance score (unbounded, higher is better) */\n score: number;\n /** The matched entity (if resolved) */\n entity?: Entity;\n}\n\n/**\n * Result from symbolic search layer.\n */\nexport interface SymbolicSearchResult {\n /** Entity name */\n entityName: string;\n /** Match score (typically 0-1) */\n score: number;\n /** The matched entity (if resolved) */\n entity?: Entity;\n}\n\n/**\n * Combined result with scores from all layers.\n */\nexport interface ScoredResult {\n /** Entity name */\n entityName: string;\n /** The matched entity */\n entity: Entity;\n /** Individual layer scores (normalized 0-1) */\n scores: {\n semantic: number;\n lexical: number;\n symbolic: number;\n combined: number;\n };\n /** Which layers contributed to this result */\n matchedLayers: ('semantic' | 'lexical' | 'symbolic')[];\n /** Original raw scores before normalization */\n rawScores: {\n semantic?: number;\n lexical?: number;\n symbolic?: number;\n };\n}\n\n/**\n * Configurable weights for hybrid scoring.\n */\nexport interface HybridWeights {\n /** Weight for semantic layer (default: 0.4) */\n semantic: number;\n /** Weight for lexical layer (default: 0.4) */\n lexical: number;\n /** Weight for symbolic layer (default: 0.2) */\n symbolic: number;\n}\n\n/**\n * Default weights for hybrid search.\n */\nexport const DEFAULT_SCORER_WEIGHTS: HybridWeights = {\n semantic: 0.4,\n lexical: 0.4,\n symbolic: 0.2,\n};\n\n/**\n * Options for the HybridScorer.\n */\nexport interface HybridScorerOptions {\n /** Weights for each layer */\n weights?: Partial<HybridWeights>;\n /** Minimum score to include in results (default: 0) */\n minScore?: number;\n /** Whether to normalize weights to sum to 1 (default: true) */\n normalizeWeights?: boolean;\n}\n\n/**\n * HybridScorer combines multiple search signals using min-max normalization.\n *\n * Features:\n * 1. Min-max normalization brings all scores to 0-1 range\n * 2. Configurable weights for each layer\n * 3. Handles missing layers gracefully (redistributes weights)\n * 4. Tracks which layers contributed to each result\n *\n * @example\n * ```typescript\n * const scorer = new HybridScorer({\n * weights: { semantic: 0.5, lexical: 0.3, symbolic: 0.2 }\n * });\n *\n * const results = scorer.combine(\n * semanticResults,\n * lexicalResults,\n * symbolicResults,\n * entityMap\n * );\n * ```\n */\nexport class HybridScorer {\n private weights: HybridWeights;\n private minScore: number;\n private normalizeWeights: boolean;\n\n constructor(options: HybridScorerOptions = {}) {\n this.weights = {\n ...DEFAULT_SCORER_WEIGHTS,\n ...options.weights,\n };\n this.minScore = options.minScore ?? 0;\n this.normalizeWeights = options.normalizeWeights ?? true;\n }\n\n /**\n * Get current weights configuration.\n */\n getWeights(): HybridWeights {\n return { ...this.weights };\n }\n\n /**\n * Update weights configuration.\n */\n setWeights(weights: Partial<HybridWeights>): void {\n this.weights = { ...this.weights, ...weights };\n }\n\n /**\n * Perform min-max normalization on scores.\n *\n * Formula: normalized = (x - min) / (max - min)\n *\n * @param scores - Map of entity name to raw score\n * @returns Map of entity name to normalized score (0-1)\n */\n minMaxNormalize(scores: Map<string, number>): Map<string, number> {\n if (scores.size === 0) {\n return new Map();\n }\n\n // Find min and max\n let min = Infinity;\n let max = -Infinity;\n for (const score of scores.values()) {\n if (score < min) min = score;\n if (score > max) max = score;\n }\n\n // Handle edge case where all scores are the same\n if (max === min) {\n const normalized = new Map<string, number>();\n for (const name of scores.keys()) {\n // If all scores are zero, keep them zero; otherwise normalize to 1\n normalized.set(name, max === 0 ? 0 : 1);\n }\n return normalized;\n }\n\n // Normalize\n const range = max - min;\n const normalized = new Map<string, number>();\n for (const [name, score] of scores) {\n normalized.set(name, (score - min) / range);\n }\n\n return normalized;\n }\n\n /**\n * Combine results from all three search layers.\n *\n * @param semanticResults - Results from semantic search\n * @param lexicalResults - Results from lexical search\n * @param symbolicResults - Results from symbolic search\n * @param entityMap - Map of entity names to Entity objects\n * @returns Array of combined results sorted by score\n */\n combine(\n semanticResults: SemanticLayerResult[],\n lexicalResults: LexicalSearchResult[],\n symbolicResults: SymbolicSearchResult[],\n entityMap: Map<string, Entity>\n ): ScoredResult[] {\n // Build score maps\n const semanticScores = new Map<string, number>();\n for (const result of semanticResults) {\n semanticScores.set(result.entityName, result.similarity);\n }\n\n const lexicalScores = new Map<string, number>();\n for (const result of lexicalResults) {\n lexicalScores.set(result.entityName, result.score);\n }\n\n const symbolicScores = new Map<string, number>();\n for (const result of symbolicResults) {\n symbolicScores.set(result.entityName, result.score);\n }\n\n // Normalize scores\n const normalizedSemantic = this.minMaxNormalize(semanticScores);\n const normalizedLexical = this.minMaxNormalize(lexicalScores);\n const normalizedSymbolic = this.minMaxNormalize(symbolicScores);\n\n // Calculate effective weights\n let effectiveWeights = { ...this.weights };\n if (this.normalizeWeights) {\n effectiveWeights = this.getNormalizedWeights(\n semanticResults.length > 0,\n lexicalResults.length > 0,\n symbolicResults.length > 0\n );\n }\n\n // Collect all unique entity names\n const allNames = new Set<string>([\n ...normalizedSemantic.keys(),\n ...normalizedLexical.keys(),\n ...normalizedSymbolic.keys(),\n ]);\n\n // Calculate combined scores\n const results: ScoredResult[] = [];\n for (const entityName of allNames) {\n const entity = entityMap.get(entityName);\n if (!entity) continue;\n\n const semanticScore = normalizedSemantic.get(entityName) ?? 0;\n const lexicalScore = normalizedLexical.get(entityName) ?? 0;\n const symbolicScore = normalizedSymbolic.get(entityName) ?? 0;\n\n // Calculate weighted combination\n const combined =\n semanticScore * effectiveWeights.semantic +\n lexicalScore * effectiveWeights.lexical +\n symbolicScore * effectiveWeights.symbolic;\n\n // Track matched layers\n const matchedLayers: ('semantic' | 'lexical' | 'symbolic')[] = [];\n const rawScores: ScoredResult['rawScores'] = {};\n\n if (semanticScores.has(entityName)) {\n matchedLayers.push('semantic');\n rawScores.semantic = semanticScores.get(entityName);\n }\n if (lexicalScores.has(entityName)) {\n matchedLayers.push('lexical');\n rawScores.lexical = lexicalScores.get(entityName);\n }\n if (symbolicScores.has(entityName)) {\n matchedLayers.push('symbolic');\n rawScores.symbolic = symbolicScores.get(entityName);\n }\n\n // Skip if below minimum score or no layers matched\n if (combined < this.minScore || matchedLayers.length === 0) {\n continue;\n }\n\n results.push({\n entityName,\n entity,\n scores: {\n semantic: semanticScore,\n lexical: lexicalScore,\n symbolic: symbolicScore,\n combined,\n },\n matchedLayers,\n rawScores,\n });\n }\n\n // Sort by combined score descending\n return results.sort((a, b) => b.scores.combined - a.scores.combined);\n }\n\n /**\n * Get weights normalized to sum to 1, redistributing for missing layers.\n *\n * @param hasSemantic - Whether semantic results are available\n * @param hasLexical - Whether lexical results are available\n * @param hasSymbolic - Whether symbolic results are available\n * @returns Normalized weights\n */\n getNormalizedWeights(\n hasSemantic: boolean,\n hasLexical: boolean,\n hasSymbolic: boolean\n ): HybridWeights {\n let totalActiveWeight = 0;\n if (hasSemantic) totalActiveWeight += this.weights.semantic;\n if (hasLexical) totalActiveWeight += this.weights.lexical;\n if (hasSymbolic) totalActiveWeight += this.weights.symbolic;\n\n // If no layers are active, return zero weights\n if (totalActiveWeight === 0) {\n return { semantic: 0, lexical: 0, symbolic: 0 };\n }\n\n // Normalize active weights to sum to 1\n return {\n semantic: hasSemantic ? this.weights.semantic / totalActiveWeight : 0,\n lexical: hasLexical ? this.weights.lexical / totalActiveWeight : 0,\n symbolic: hasSymbolic ? this.weights.symbolic / totalActiveWeight : 0,\n };\n }\n\n /**\n * Combine scores from maps directly (alternative interface).\n *\n * @param semanticScores - Map of entity name to semantic score\n * @param lexicalScores - Map of entity name to lexical score\n * @param symbolicScores - Map of entity name to symbolic score\n * @param entityMap - Map of entity names to Entity objects\n * @returns Array of combined results sorted by score\n */\n combineFromMaps(\n semanticScores: Map<string, number>,\n lexicalScores: Map<string, number>,\n symbolicScores: Map<string, number>,\n entityMap: Map<string, Entity>\n ): ScoredResult[] {\n // Convert maps to result arrays\n const semanticResults: SemanticLayerResult[] = [];\n for (const [entityName, similarity] of semanticScores) {\n semanticResults.push({ entityName, similarity });\n }\n\n const lexicalResults: LexicalSearchResult[] = [];\n for (const [entityName, score] of lexicalScores) {\n lexicalResults.push({ entityName, score });\n }\n\n const symbolicResults: SymbolicSearchResult[] = [];\n for (const [entityName, score] of symbolicScores) {\n symbolicResults.push({ entityName, score });\n }\n\n return this.combine(semanticResults, lexicalResults, symbolicResults, entityMap);\n }\n\n /**\n * Calculate combined score for a single entity.\n *\n * Useful for scoring individual results without full normalization.\n *\n * @param semanticScore - Normalized semantic score (0-1)\n * @param lexicalScore - Normalized lexical score (0-1)\n * @param symbolicScore - Normalized symbolic score (0-1)\n * @returns Combined weighted score\n */\n calculateScore(\n semanticScore: number,\n lexicalScore: number,\n symbolicScore: number\n ): number {\n return (\n semanticScore * this.weights.semantic +\n lexicalScore * this.weights.lexical +\n symbolicScore * this.weights.symbolic\n );\n }\n}\n","/**\n * Parallel Search Executor\n *\n * Phase 12 Sprint 2: Executes search layers (semantic, lexical, symbolic)\n * concurrently using Promise.all with graceful fallback on failures.\n *\n * @module search/ParallelSearchExecutor\n */\n\nimport type {\n Entity,\n ReadonlyKnowledgeGraph,\n SymbolicFilters,\n} from '../types/index.js';\nimport type { SemanticSearch } from './SemanticSearch.js';\nimport type { RankedSearch } from './RankedSearch.js';\nimport { SymbolicSearch } from './SymbolicSearch.js';\nimport { SEMANTIC_SEARCH_LIMITS } from '../utils/constants.js';\n\n/**\n * Timing information for a search layer.\n */\nexport interface LayerTiming {\n /** Layer identifier */\n layer: 'semantic' | 'lexical' | 'symbolic';\n /** Start timestamp */\n startTime: number;\n /** End timestamp */\n endTime: number;\n /** Duration in milliseconds */\n durationMs: number;\n /** Whether the layer succeeded */\n success: boolean;\n /** Error message if failed */\n error?: string;\n /** Number of results returned */\n resultCount: number;\n}\n\n/**\n * Result from parallel search execution with timing metadata.\n */\nexport interface ParallelSearchResult {\n /** Semantic search results: entity name -> similarity score */\n semanticResults: Map<string, number>;\n /** Lexical search results: entity name -> normalized score */\n lexicalResults: Map<string, number>;\n /** Symbolic search results: entity name -> filter match score */\n symbolicResults: Map<string, number>;\n /** Timing information for each layer */\n timings: LayerTiming[];\n /** Total execution time in milliseconds */\n totalTimeMs: number;\n /** Whether all layers succeeded */\n allSucceeded: boolean;\n /** Summary of failed layers */\n failedLayers: string[];\n}\n\n/**\n * Options for parallel search execution.\n */\nexport interface ParallelSearchOptions {\n /** Semantic search options */\n semantic?: {\n minSimilarity?: number;\n topK?: number;\n };\n /** Lexical search options */\n lexical?: {\n useStopwords?: boolean;\n useStemming?: boolean;\n };\n /** Symbolic filter criteria */\n symbolic?: SymbolicFilters;\n /** Maximum results per layer */\n limit?: number;\n /** Timeout per layer in milliseconds (default: 30000) */\n timeoutMs?: number;\n}\n\n/**\n * ParallelSearchExecutor - Execute search layers concurrently\n *\n * Orchestrates parallel execution of semantic, lexical, and symbolic search\n * layers using Promise.all. Provides:\n * - Concurrent execution for improved latency\n * - Per-layer timing metadata\n * - Graceful fallback on individual layer failures\n * - Layer-specific error isolation\n *\n * @example\n * ```typescript\n * const executor = new ParallelSearchExecutor(semanticSearch, rankedSearch);\n *\n * const result = await executor.execute(graph, 'machine learning', {\n * semantic: { minSimilarity: 0.5 },\n * symbolic: { tags: ['ai'] }\n * });\n *\n * console.log(`Total time: ${result.totalTimeMs}ms`);\n * for (const timing of result.timings) {\n * console.log(`${timing.layer}: ${timing.durationMs}ms, ${timing.resultCount} results`);\n * }\n * ```\n */\nexport class ParallelSearchExecutor {\n private symbolicSearch: SymbolicSearch;\n\n constructor(\n private semanticSearch: SemanticSearch | null,\n private rankedSearch: RankedSearch\n ) {\n this.symbolicSearch = new SymbolicSearch();\n }\n\n /**\n * Execute all search layers in parallel.\n *\n * @param graph - Knowledge graph to search\n * @param query - Search query text\n * @param options - Search options for each layer\n * @returns Parallel search results with timing metadata\n */\n async execute(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: ParallelSearchOptions = {}\n ): Promise<ParallelSearchResult> {\n const {\n semantic = {},\n lexical = {},\n symbolic = {},\n limit = SEMANTIC_SEARCH_LIMITS.DEFAULT_LIMIT,\n timeoutMs = 30000,\n } = options;\n\n const overallStart = Date.now();\n const timings: LayerTiming[] = [];\n const failedLayers: string[] = [];\n\n // Execute all three layers in parallel\n const [semanticResult, lexicalResult, symbolicResult] = await Promise.all([\n this.executeSemanticLayer(graph, query, semantic, limit * 2, timeoutMs),\n this.executeLexicalLayer(query, lexical, limit * 2, timeoutMs),\n this.executeSymbolicLayer(graph.entities, symbolic, timeoutMs),\n ]);\n\n // Collect timing information\n timings.push(semanticResult.timing);\n timings.push(lexicalResult.timing);\n timings.push(symbolicResult.timing);\n\n // Track failed layers\n if (!semanticResult.timing.success) failedLayers.push('semantic');\n if (!lexicalResult.timing.success) failedLayers.push('lexical');\n if (!symbolicResult.timing.success) failedLayers.push('symbolic');\n\n const totalTimeMs = Date.now() - overallStart;\n\n return {\n semanticResults: semanticResult.results,\n lexicalResults: lexicalResult.results,\n symbolicResults: symbolicResult.results,\n timings,\n totalTimeMs,\n allSucceeded: failedLayers.length === 0,\n failedLayers,\n };\n }\n\n /**\n * Execute semantic search layer with timing.\n */\n private async executeSemanticLayer(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: { minSimilarity?: number; topK?: number },\n limit: number,\n _timeoutMs: number\n ): Promise<{ results: Map<string, number>; timing: LayerTiming }> {\n const startTime = Date.now();\n const results = new Map<string, number>();\n\n let success = true;\n let error: string | undefined;\n\n if (!this.semanticSearch) {\n // Semantic search not available - treat as graceful degradation\n return {\n results,\n timing: {\n layer: 'semantic',\n startTime,\n endTime: Date.now(),\n durationMs: Date.now() - startTime,\n success: true, // Not an error, just not configured\n resultCount: 0,\n },\n };\n }\n\n try {\n // Execute semantic search with timeout\n const semanticResults = await Promise.race([\n this.semanticSearch.search(\n graph,\n query,\n options.topK ?? limit,\n options.minSimilarity ?? 0\n ),\n this.createTimeout<never>(_timeoutMs, 'Semantic search timeout'),\n ]);\n\n for (const result of semanticResults) {\n results.set(result.entity.name, result.similarity);\n }\n } catch (err) {\n success = false;\n error = err instanceof Error ? err.message : String(err);\n }\n\n const endTime = Date.now();\n return {\n results,\n timing: {\n layer: 'semantic',\n startTime,\n endTime,\n durationMs: endTime - startTime,\n success,\n error,\n resultCount: results.size,\n },\n };\n }\n\n /**\n * Execute lexical search layer (TF-IDF/BM25) with timing.\n */\n private async executeLexicalLayer(\n query: string,\n _options: { useStopwords?: boolean; useStemming?: boolean },\n limit: number,\n _timeoutMs: number\n ): Promise<{ results: Map<string, number>; timing: LayerTiming }> {\n const startTime = Date.now();\n const results = new Map<string, number>();\n\n let success = true;\n let error: string | undefined;\n\n try {\n // Execute lexical search with timeout\n const lexicalResults = await Promise.race([\n this.rankedSearch.searchNodesRanked(\n query,\n undefined, // tags\n undefined, // minImportance\n undefined, // maxImportance\n limit\n ),\n this.createTimeout<never>(_timeoutMs, 'Lexical search timeout'),\n ]);\n\n // Normalize scores to 0-1 range\n const maxScore = Math.max(...lexicalResults.map(r => r.score), 1);\n for (const result of lexicalResults) {\n results.set(result.entity.name, result.score / maxScore);\n }\n } catch (err) {\n success = false;\n error = err instanceof Error ? err.message : String(err);\n }\n\n const endTime = Date.now();\n return {\n results,\n timing: {\n layer: 'lexical',\n startTime,\n endTime,\n durationMs: endTime - startTime,\n success,\n error,\n resultCount: results.size,\n },\n };\n }\n\n /**\n * Execute symbolic search layer with timing.\n */\n private async executeSymbolicLayer(\n entities: readonly Entity[],\n filters: SymbolicFilters | undefined,\n _timeoutMs: number\n ): Promise<{ results: Map<string, number>; timing: LayerTiming }> {\n const startTime = Date.now();\n const results = new Map<string, number>();\n\n let success = true;\n let error: string | undefined;\n\n try {\n // Symbolic search is synchronous but wrap for consistency\n if (!filters || Object.keys(filters).length === 0) {\n // No filters - give all entities base score\n for (const entity of entities) {\n results.set(entity.name, 0.5);\n }\n } else {\n const symbolicResults = this.symbolicSearch.search(entities, filters);\n for (const result of symbolicResults) {\n results.set(result.entity.name, result.score);\n }\n }\n } catch (err) {\n success = false;\n error = err instanceof Error ? err.message : String(err);\n }\n\n const endTime = Date.now();\n return {\n results,\n timing: {\n layer: 'symbolic',\n startTime,\n endTime,\n durationMs: endTime - startTime,\n success,\n error,\n resultCount: results.size,\n },\n };\n }\n\n /**\n * Create a timeout promise.\n */\n private createTimeout<T>(ms: number, message: string): Promise<T> {\n return new Promise((_, reject) => {\n setTimeout(() => reject(new Error(message)), ms);\n });\n }\n\n /**\n * Execute a single layer independently.\n *\n * @param layer - Layer to execute\n * @param graph - Knowledge graph\n * @param query - Search query\n * @param options - Layer-specific options\n * @returns Layer results with timing\n */\n async executeLayer(\n layer: 'semantic' | 'lexical' | 'symbolic',\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: ParallelSearchOptions = {}\n ): Promise<{ results: Map<string, number>; timing: LayerTiming }> {\n const limit = options.limit ?? SEMANTIC_SEARCH_LIMITS.DEFAULT_LIMIT;\n const timeoutMs = options.timeoutMs ?? 30000;\n\n switch (layer) {\n case 'semantic':\n return this.executeSemanticLayer(\n graph,\n query,\n options.semantic ?? {},\n limit * 2,\n timeoutMs\n );\n case 'lexical':\n return this.executeLexicalLayer(\n query,\n options.lexical ?? {},\n limit * 2,\n timeoutMs\n );\n case 'symbolic':\n return this.executeSymbolicLayer(\n graph.entities,\n options.symbolic,\n timeoutMs\n );\n }\n }\n\n /**\n * Execute only specific layers in parallel.\n *\n * @param layers - Layers to execute\n * @param graph - Knowledge graph\n * @param query - Search query\n * @param options - Search options\n * @returns Partial results for requested layers\n */\n async executeSelectedLayers(\n layers: Array<'semantic' | 'lexical' | 'symbolic'>,\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: ParallelSearchOptions = {}\n ): Promise<{\n results: Map<'semantic' | 'lexical' | 'symbolic', Map<string, number>>;\n timings: LayerTiming[];\n totalTimeMs: number;\n }> {\n const overallStart = Date.now();\n const results = new Map<'semantic' | 'lexical' | 'symbolic', Map<string, number>>();\n const timings: LayerTiming[] = [];\n\n const layerPromises = layers.map(layer =>\n this.executeLayer(layer, graph, query, options).then(result => ({\n layer,\n ...result,\n }))\n );\n\n const layerExecutionResults = await Promise.all(layerPromises);\n\n for (const { layer, results: layerData, timing } of layerExecutionResults) {\n results.set(layer, layerData);\n timings.push(timing);\n }\n\n return {\n results,\n timings,\n totalTimeMs: Date.now() - overallStart,\n };\n }\n\n /**\n * Get timing summary from results.\n *\n * @param timings - Array of layer timings\n * @returns Formatted timing summary\n */\n static formatTimingSummary(timings: LayerTiming[]): string {\n const lines: string[] = [];\n let totalTime = 0;\n let maxTime = 0;\n\n for (const timing of timings) {\n const status = timing.success ? 'OK' : `FAILED: ${timing.error}`;\n lines.push(\n ` ${timing.layer}: ${timing.durationMs}ms (${timing.resultCount} results) [${status}]`\n );\n totalTime += timing.durationMs;\n maxTime = Math.max(maxTime, timing.durationMs);\n }\n\n lines.unshift('Search Layer Timings:');\n lines.push(` Total (sequential): ${totalTime}ms`);\n lines.push(` Max (parallel bottleneck): ${maxTime}ms`);\n lines.push(` Speedup: ${(totalTime / maxTime).toFixed(2)}x`);\n\n return lines.join('\\n');\n }\n\n /**\n * Calculate potential speedup from parallel execution.\n *\n * @param timings - Array of layer timings\n * @returns Speedup metrics\n */\n static calculateSpeedup(timings: LayerTiming[]): {\n sequentialTime: number;\n parallelTime: number;\n speedup: number;\n } {\n const sequentialTime = timings.reduce((sum, t) => sum + t.durationMs, 0);\n const parallelTime = Math.max(...timings.map(t => t.durationMs));\n const speedup = sequentialTime / parallelTime;\n\n return { sequentialTime, parallelTime, speedup };\n }\n}\n","/**\n * Early Termination Manager\n *\n * Phase 12 Sprint 4: Manages early termination of hybrid search when\n * results are adequate, executing layers in cost-order for efficiency.\n *\n * @module search/EarlyTerminationManager\n */\n\nimport type {\n HybridSearchResult,\n QueryAnalysis,\n ReadonlyKnowledgeGraph,\n} from '../types/index.js';\nimport type { HybridSearchManager } from './HybridSearchManager.js';\nimport type { SearchLayer } from './QueryCostEstimator.js';\nimport { QueryCostEstimator } from './QueryCostEstimator.js';\n\n/**\n * Adequacy check result for a set of search results.\n */\nexport interface AdequacyCheck {\n /** Whether results are adequate */\n adequate: boolean;\n /** Adequacy score (0-1) */\n score: number;\n /** Reasons for the adequacy determination */\n reasons: string[];\n /** Layers that contributed to results */\n contributingLayers: SearchLayer[];\n}\n\n/**\n * Options for early termination.\n */\nexport interface EarlyTerminationOptions {\n /** Adequacy threshold (0-1, default: 0.7) */\n adequacyThreshold?: number;\n /** Minimum results required (default: 3) */\n minResults?: number;\n /** Maximum results to collect (default: 20) */\n maxResults?: number;\n /** Whether semantic search is available (default: true) */\n semanticAvailable?: boolean;\n /** Query analysis for smarter adequacy checking */\n analysis?: QueryAnalysis;\n /** Minimum diversity score (0-1, default: 0.3) */\n minDiversity?: number;\n /** Minimum average relevance score (0-1, default: 0.4) */\n minRelevance?: number;\n}\n\n/**\n * Result from early termination search.\n */\nexport interface EarlyTerminationResult {\n /** Final search results */\n results: HybridSearchResult[];\n /** Layers that were executed (in order) */\n executedLayers: SearchLayer[];\n /** Adequacy check details */\n adequacy: AdequacyCheck;\n /** Whether early termination occurred */\n earlyTerminated: boolean;\n /** Total time in milliseconds */\n executionTimeMs: number;\n}\n\n/**\n * Default options for early termination.\n */\nconst DEFAULT_OPTIONS: Required<Omit<EarlyTerminationOptions, 'analysis'>> = {\n adequacyThreshold: 0.7,\n minResults: 3,\n maxResults: 20,\n semanticAvailable: true,\n minDiversity: 0.3,\n minRelevance: 0.4,\n};\n\n/**\n * Early Termination Manager\n *\n * Executes search layers in cost-order (fastest first) and terminates\n * early when results are adequate, saving computation time.\n *\n * @example\n * ```typescript\n * const manager = new EarlyTerminationManager(hybridSearch);\n * const result = await manager.searchWithEarlyTermination(\n * graph,\n * 'machine learning',\n * { adequacyThreshold: 0.8, minResults: 5 }\n * );\n *\n * if (result.earlyTerminated) {\n * console.log(`Terminated after ${result.executedLayers.length} layers`);\n * }\n * ```\n */\nexport class EarlyTerminationManager {\n private costEstimator: QueryCostEstimator;\n\n constructor(\n private hybridSearch: HybridSearchManager,\n costEstimator?: QueryCostEstimator\n ) {\n this.costEstimator = costEstimator ?? new QueryCostEstimator();\n }\n\n /**\n * Execute search with early termination support.\n *\n * Executes layers in cost-order and terminates early when results\n * meet the adequacy threshold.\n *\n * @param graph - Knowledge graph to search\n * @param query - Search query\n * @param options - Early termination options\n * @returns Search results with termination details\n */\n async searchWithEarlyTermination(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n options: EarlyTerminationOptions = {}\n ): Promise<EarlyTerminationResult> {\n const startTime = Date.now();\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const { semanticAvailable, maxResults } = opts;\n\n // Get layers sorted by cost (fastest first)\n const orderedLayers = this.costEstimator.getLayersByCost(\n query,\n graph.entities.length,\n semanticAvailable\n );\n\n const allResults: HybridSearchResult[] = [];\n const executedLayers: SearchLayer[] = [];\n let earlyTerminated = false;\n\n // Execute layers in cost order\n for (const { layer } of orderedLayers) {\n executedLayers.push(layer);\n\n // Execute single layer search\n const layerResults = await this.executeLayerSearch(\n graph,\n query,\n layer,\n maxResults\n );\n\n // Merge results (deduplicate by entity name)\n for (const result of layerResults) {\n const existing = allResults.find(r => r.entity.name === result.entity.name);\n if (existing) {\n // Update scores if this layer has higher score\n if (result.scores.combined > existing.scores.combined) {\n Object.assign(existing, result);\n }\n // Add to matched layers\n if (!existing.matchedLayers.includes(layer)) {\n existing.matchedLayers.push(layer);\n }\n } else {\n // Ensure matchedLayers includes current layer\n if (!result.matchedLayers.includes(layer)) {\n result.matchedLayers = [...result.matchedLayers, layer];\n }\n allResults.push(result);\n }\n }\n\n // Check adequacy\n const adequacy = this.checkAdequacy(allResults, opts, executedLayers);\n\n if (adequacy.adequate) {\n earlyTerminated = executedLayers.length < orderedLayers.length;\n break;\n }\n }\n\n // Final sort by combined score\n const sortedResults = allResults\n .sort((a, b) => b.scores.combined - a.scores.combined)\n .slice(0, maxResults);\n\n const finalAdequacy = this.checkAdequacy(sortedResults, opts, executedLayers);\n\n return {\n results: sortedResults,\n executedLayers,\n adequacy: finalAdequacy,\n earlyTerminated,\n executionTimeMs: Date.now() - startTime,\n };\n }\n\n /**\n * Execute a single layer search.\n * @private\n */\n private async executeLayerSearch(\n graph: ReadonlyKnowledgeGraph,\n query: string,\n layer: SearchLayer,\n limit: number\n ): Promise<HybridSearchResult[]> {\n // Configure weights to emphasize the current layer\n const weights = this.getLayerWeights(layer);\n\n try {\n return await this.hybridSearch.search(graph, query, {\n ...weights,\n limit: limit * 2, // Over-fetch for better merging\n });\n } catch {\n // Layer failed, return empty results\n return [];\n }\n }\n\n /**\n * Get weight configuration for a specific layer.\n * @private\n */\n private getLayerWeights(layer: SearchLayer): {\n semanticWeight: number;\n lexicalWeight: number;\n symbolicWeight: number;\n } {\n switch (layer) {\n case 'semantic':\n return { semanticWeight: 1.0, lexicalWeight: 0.0, symbolicWeight: 0.0 };\n case 'lexical':\n return { semanticWeight: 0.0, lexicalWeight: 1.0, symbolicWeight: 0.0 };\n case 'symbolic':\n return { semanticWeight: 0.0, lexicalWeight: 0.0, symbolicWeight: 1.0 };\n }\n }\n\n /**\n * Check if results are adequate based on configured thresholds.\n *\n * @param results - Current search results\n * @param options - Adequacy options\n * @param executedLayers - Layers that have been executed\n * @returns Adequacy check result\n */\n checkAdequacy(\n results: HybridSearchResult[],\n options: EarlyTerminationOptions,\n executedLayers: SearchLayer[]\n ): AdequacyCheck {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const {\n adequacyThreshold,\n minResults,\n minDiversity,\n minRelevance,\n analysis,\n } = opts;\n\n const reasons: string[] = [];\n let score = 0;\n\n // Weight components\n const weights = {\n quantity: 0.35,\n diversity: 0.25,\n relevance: 0.25,\n coverage: 0.15,\n };\n\n // 1. Quantity score (do we have enough results?)\n const quantityScore = Math.min(results.length / minResults, 1);\n score += quantityScore * weights.quantity;\n\n if (quantityScore < 1) {\n reasons.push(`Insufficient results: ${results.length}/${minResults}`);\n } else {\n reasons.push(`Sufficient results: ${results.length}`);\n }\n\n // 2. Diversity score (variety of entity types and layers)\n const diversityScore = this.calculateDiversityScore(results);\n score += Math.min(diversityScore / minDiversity, 1) * weights.diversity;\n\n if (diversityScore < minDiversity) {\n reasons.push(`Low diversity: ${diversityScore.toFixed(2)}/${minDiversity}`);\n } else {\n reasons.push(`Good diversity: ${diversityScore.toFixed(2)}`);\n }\n\n // 3. Relevance score (average combined score)\n const avgRelevance = results.length > 0\n ? results.reduce((sum, r) => sum + r.scores.combined, 0) / results.length\n : 0;\n const relevanceScore = Math.min(avgRelevance / minRelevance, 1);\n score += relevanceScore * weights.relevance;\n\n if (avgRelevance < minRelevance) {\n reasons.push(`Low relevance: ${avgRelevance.toFixed(2)}/${minRelevance}`);\n } else {\n reasons.push(`Good relevance: ${avgRelevance.toFixed(2)}`);\n }\n\n // 4. Coverage score (how many layers contributed)\n const contributingLayers = this.getContributingLayers(results);\n const coverageScore = contributingLayers.length / executedLayers.length;\n score += coverageScore * weights.coverage;\n\n if (coverageScore < 0.5) {\n reasons.push(`Low layer coverage: ${contributingLayers.length}/${executedLayers.length}`);\n } else {\n reasons.push(`Good layer coverage: ${contributingLayers.length}/${executedLayers.length}`);\n }\n\n // Bonus for matching required info types (if analysis provided)\n if (analysis && analysis.requiredInfoTypes.length > 0) {\n const entityTypes = new Set(results.map(r => r.entity.entityType.toLowerCase()));\n const matchedTypes = analysis.requiredInfoTypes.filter(t =>\n entityTypes.has(t) ||\n (t === 'person' && entityTypes.has('person')) ||\n (t === 'entity' && entityTypes.size > 0)\n );\n const typeMatchScore = matchedTypes.length / analysis.requiredInfoTypes.length;\n score += typeMatchScore * 0.1; // Bonus weight\n reasons.push(`Info type coverage: ${matchedTypes.length}/${analysis.requiredInfoTypes.length}`);\n }\n\n // Normalize score\n score = Math.min(score, 1);\n\n return {\n adequate: score >= adequacyThreshold,\n score,\n reasons,\n contributingLayers,\n };\n }\n\n /**\n * Calculate diversity score from results.\n * @private\n */\n private calculateDiversityScore(results: HybridSearchResult[]): number {\n if (results.length === 0) return 0;\n\n // Type diversity\n const entityTypes = new Set(results.map(r => r.entity.entityType));\n const typeDiversity = Math.min(entityTypes.size / 3, 1);\n\n // Layer diversity\n const layerCounts = { semantic: 0, lexical: 0, symbolic: 0 };\n for (const result of results) {\n for (const layer of result.matchedLayers) {\n layerCounts[layer]++;\n }\n }\n const activeLayers = Object.values(layerCounts).filter(c => c > 0).length;\n const layerDiversity = activeLayers / 3;\n\n // Combined diversity\n return (typeDiversity + layerDiversity) / 2;\n }\n\n /**\n * Get layers that contributed to results.\n * @private\n */\n private getContributingLayers(results: HybridSearchResult[]): SearchLayer[] {\n const layers = new Set<SearchLayer>();\n for (const result of results) {\n for (const layer of result.matchedLayers) {\n layers.add(layer);\n }\n }\n return Array.from(layers);\n }\n\n /**\n * Calculate adequacy score for a set of results.\n *\n * Standalone method for checking result adequacy without full search.\n *\n * @param results - Results to evaluate\n * @param options - Adequacy options\n * @returns Adequacy score (0-1)\n */\n calculateAdequacyScore(\n results: HybridSearchResult[],\n options: EarlyTerminationOptions = {}\n ): number {\n const allLayers: SearchLayer[] = ['semantic', 'lexical', 'symbolic'];\n return this.checkAdequacy(results, options, allLayers).score;\n }\n\n /**\n * Get the cost estimator for external use.\n */\n getCostEstimator(): QueryCostEstimator {\n return this.costEstimator;\n }\n}\n","/**\n * Query Plan Cache\n *\n * Phase 12 Sprint 4: Caches query analysis and planning results\n * with LRU eviction for improved performance.\n *\n * @module search/QueryPlanCache\n */\n\nimport type { QueryAnalysis, QueryPlan } from '../types/index.js';\n\n/**\n * Cached query entry with metadata.\n */\nexport interface CachedQueryEntry {\n /** Normalized query string */\n normalizedQuery: string;\n /** Original query string */\n originalQuery: string;\n /** Cached analysis result */\n analysis: QueryAnalysis;\n /** Cached plan result (if available) */\n plan?: QueryPlan;\n /** Cache entry creation time */\n createdAt: number;\n /** Last access time for LRU */\n lastAccessed: number;\n /** Number of times this entry was accessed */\n hitCount: number;\n}\n\n/**\n * Cache statistics for monitoring.\n */\nexport interface QueryPlanCacheStats {\n /** Total cache entries */\n size: number;\n /** Maximum cache size */\n maxSize: number;\n /** Total cache hits */\n hits: number;\n /** Total cache misses */\n misses: number;\n /** Hit rate (0-1) */\n hitRate: number;\n /** Total evictions */\n evictions: number;\n /** Average entry age in milliseconds */\n averageEntryAgeMs: number;\n}\n\n/**\n * Options for the query plan cache.\n */\nexport interface QueryPlanCacheOptions {\n /** Maximum number of entries (default: 1000) */\n maxSize?: number;\n /** Entry TTL in milliseconds (default: 5 minutes) */\n ttlMs?: number;\n /** Whether to normalize queries for better hit rate (default: true) */\n normalizeQueries?: boolean;\n /** Whether to enable cache statistics (default: true) */\n enableStats?: boolean;\n}\n\n/**\n * Default cache options.\n */\nconst DEFAULT_OPTIONS: Required<QueryPlanCacheOptions> = {\n maxSize: 1000,\n ttlMs: 5 * 60 * 1000, // 5 minutes\n normalizeQueries: true,\n enableStats: true,\n};\n\n/**\n * Query Plan Cache with LRU eviction.\n *\n * Caches query analysis and planning results to avoid redundant computation.\n * Uses LRU (Least Recently Used) eviction when cache is full.\n *\n * @example\n * ```typescript\n * const cache = new QueryPlanCache({ maxSize: 500 });\n *\n * // Cache an analysis\n * cache.setAnalysis('Find Alice', analysis);\n *\n * // Retrieve cached analysis\n * const cached = cache.getAnalysis('Find Alice');\n * if (cached) {\n * console.log('Cache hit!', cached);\n * }\n *\n * // Get statistics\n * const stats = cache.getStats();\n * console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);\n * ```\n */\nexport class QueryPlanCache {\n private cache: Map<string, CachedQueryEntry>;\n private options: Required<QueryPlanCacheOptions>;\n\n // Statistics\n private hits = 0;\n private misses = 0;\n private evictions = 0;\n\n constructor(options?: QueryPlanCacheOptions) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.cache = new Map();\n }\n\n /**\n * Get cached analysis for a query.\n *\n * @param query - The search query\n * @returns Cached analysis or undefined if not found\n */\n getAnalysis(query: string): QueryAnalysis | undefined {\n const entry = this.getEntry(query);\n return entry?.analysis;\n }\n\n /**\n * Get cached plan for a query.\n *\n * @param query - The search query\n * @returns Cached plan or undefined if not found\n */\n getPlan(query: string): QueryPlan | undefined {\n const entry = this.getEntry(query);\n return entry?.plan;\n }\n\n /**\n * Get full cached entry for a query.\n *\n * @param query - The search query\n * @returns Cached entry or undefined if not found\n */\n getEntry(query: string): CachedQueryEntry | undefined {\n const key = this.normalizeQuery(query);\n const entry = this.cache.get(key);\n\n if (!entry) {\n if (this.options.enableStats) this.misses++;\n return undefined;\n }\n\n // Check TTL\n if (this.isExpired(entry)) {\n this.cache.delete(key);\n if (this.options.enableStats) this.misses++;\n return undefined;\n }\n\n // Update access time and count\n entry.lastAccessed = Date.now();\n entry.hitCount++;\n\n if (this.options.enableStats) this.hits++;\n\n return entry;\n }\n\n /**\n * Cache analysis results for a query.\n *\n * @param query - The search query\n * @param analysis - The analysis result to cache\n */\n setAnalysis(query: string, analysis: QueryAnalysis): void {\n const key = this.normalizeQuery(query);\n const now = Date.now();\n\n const existing = this.cache.get(key);\n if (existing) {\n // Update existing entry\n existing.analysis = analysis;\n existing.lastAccessed = now;\n } else {\n // Evict if necessary\n this.evictIfNeeded();\n\n // Create new entry\n this.cache.set(key, {\n normalizedQuery: key,\n originalQuery: query,\n analysis,\n createdAt: now,\n lastAccessed: now,\n hitCount: 0,\n });\n }\n }\n\n /**\n * Cache plan results for a query.\n *\n * @param query - The search query\n * @param analysis - The analysis result to cache\n * @param plan - The plan result to cache\n */\n setPlan(query: string, analysis: QueryAnalysis, plan: QueryPlan): void {\n const key = this.normalizeQuery(query);\n const now = Date.now();\n\n const existing = this.cache.get(key);\n if (existing) {\n // Update existing entry\n existing.analysis = analysis;\n existing.plan = plan;\n existing.lastAccessed = now;\n } else {\n // Evict if necessary\n this.evictIfNeeded();\n\n // Create new entry\n this.cache.set(key, {\n normalizedQuery: key,\n originalQuery: query,\n analysis,\n plan,\n createdAt: now,\n lastAccessed: now,\n hitCount: 0,\n });\n }\n }\n\n /**\n * Check if a query is cached.\n *\n * @param query - The search query\n * @returns True if cached (and not expired)\n */\n has(query: string): boolean {\n const key = this.normalizeQuery(query);\n const entry = this.cache.get(key);\n\n if (!entry) return false;\n if (this.isExpired(entry)) {\n this.cache.delete(key);\n return false;\n }\n\n return true;\n }\n\n /**\n * Invalidate a specific query from cache.\n *\n * @param query - The search query to invalidate\n * @returns True if entry was found and removed\n */\n invalidate(query: string): boolean {\n const key = this.normalizeQuery(query);\n return this.cache.delete(key);\n }\n\n /**\n * Invalidate all entries matching a pattern.\n *\n * @param pattern - Regex pattern to match against queries\n * @returns Number of entries invalidated\n */\n invalidatePattern(pattern: RegExp): number {\n let count = 0;\n for (const [key, entry] of this.cache) {\n if (pattern.test(entry.originalQuery) || pattern.test(key)) {\n this.cache.delete(key);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Clear all cache entries.\n */\n clear(): void {\n this.cache.clear();\n this.hits = 0;\n this.misses = 0;\n this.evictions = 0;\n }\n\n /**\n * Get cache statistics.\n *\n * @returns Cache statistics\n */\n getStats(): QueryPlanCacheStats {\n const now = Date.now();\n let totalAge = 0;\n\n for (const entry of this.cache.values()) {\n totalAge += now - entry.createdAt;\n }\n\n const total = this.hits + this.misses;\n\n return {\n size: this.cache.size,\n maxSize: this.options.maxSize,\n hits: this.hits,\n misses: this.misses,\n hitRate: total > 0 ? this.hits / total : 0,\n evictions: this.evictions,\n averageEntryAgeMs: this.cache.size > 0 ? totalAge / this.cache.size : 0,\n };\n }\n\n /**\n * Get the current cache size.\n */\n get size(): number {\n return this.cache.size;\n }\n\n /**\n * Normalize a query for cache lookup.\n *\n * Normalization helps improve cache hit rate by treating\n * similar queries as equivalent.\n *\n * @param query - The query to normalize\n * @returns Normalized query string\n */\n normalizeQuery(query: string): string {\n if (!this.options.normalizeQueries) {\n return query;\n }\n\n return query\n .toLowerCase()\n .trim()\n // Normalize whitespace\n .replace(/\\s+/g, ' ')\n // Remove punctuation that doesn't affect meaning\n .replace(/[.,!?;:]+$/g, '')\n // Sort operators for consistent matching\n .replace(/\\b(AND|OR|NOT)\\b/gi, match => match.toUpperCase());\n }\n\n /**\n * Check if an entry has expired.\n * @private\n */\n private isExpired(entry: CachedQueryEntry): boolean {\n return Date.now() - entry.createdAt > this.options.ttlMs;\n }\n\n /**\n * Evict entries if cache is at capacity.\n * Uses LRU (Least Recently Used) eviction.\n * @private\n */\n private evictIfNeeded(): void {\n if (this.cache.size < this.options.maxSize) {\n return;\n }\n\n // Find the least recently used entry\n let oldestKey: string | null = null;\n let oldestTime = Infinity;\n\n for (const [key, entry] of this.cache) {\n // Also check for expired entries during eviction\n if (this.isExpired(entry)) {\n this.cache.delete(key);\n if (this.options.enableStats) this.evictions++;\n if (this.cache.size < this.options.maxSize) return;\n continue;\n }\n\n if (entry.lastAccessed < oldestTime) {\n oldestTime = entry.lastAccessed;\n oldestKey = key;\n }\n }\n\n // Evict the oldest entry\n if (oldestKey) {\n this.cache.delete(oldestKey);\n if (this.options.enableStats) this.evictions++;\n }\n }\n\n /**\n * Clean up expired entries.\n *\n * Call this periodically to remove stale entries.\n *\n * @returns Number of entries removed\n */\n cleanup(): number {\n let count = 0;\n for (const [key, entry] of this.cache) {\n if (this.isExpired(entry)) {\n this.cache.delete(key);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Get all cache keys (for debugging).\n */\n keys(): string[] {\n return Array.from(this.cache.keys());\n }\n\n /**\n * Preload cache with common queries.\n *\n * @param queries - Array of query-analysis pairs to preload\n */\n preload(queries: Array<{ query: string; analysis: QueryAnalysis; plan?: QueryPlan }>): void {\n for (const { query, analysis, plan } of queries) {\n if (plan) {\n this.setPlan(query, analysis, plan);\n } else {\n this.setAnalysis(query, analysis);\n }\n }\n }\n\n /**\n * Export cache entries for persistence.\n *\n * @returns Array of cache entries\n */\n export(): CachedQueryEntry[] {\n const entries: CachedQueryEntry[] = [];\n for (const entry of this.cache.values()) {\n if (!this.isExpired(entry)) {\n entries.push({ ...entry });\n }\n }\n return entries;\n }\n\n /**\n * Import cache entries from persistence.\n *\n * @param entries - Array of cache entries to import\n * @param preserveTimestamps - Whether to preserve original timestamps\n */\n import(entries: CachedQueryEntry[], preserveTimestamps = false): void {\n const now = Date.now();\n for (const entry of entries) {\n if (!preserveTimestamps) {\n entry.createdAt = now;\n entry.lastAccessed = now;\n }\n\n // Skip if expired\n if (this.isExpired(entry)) continue;\n\n // Evict if needed\n this.evictIfNeeded();\n\n this.cache.set(entry.normalizedQuery, entry);\n }\n }\n}\n","/**\n * Quantized Vector Store\n *\n * Phase 12 Sprint 6: 8-bit scalar quantization for 4x vector memory reduction.\n * Uses asymmetric similarity computation for improved accuracy.\n *\n * @module search/QuantizedVectorStore\n */\n\n/**\n * Quantization parameters for a vector set.\n */\nexport interface QuantizationParams {\n /** Minimum value in the original vectors */\n min: number;\n /** Maximum value in the original vectors */\n max: number;\n /** Scale factor for quantization */\n scale: number;\n /** Dimension of vectors */\n dimension: number;\n}\n\n/**\n * Statistics for the quantized vector store.\n */\nexport interface QuantizedVectorStoreStats {\n /** Number of stored vectors */\n vectorCount: number;\n /** Vector dimension */\n dimension: number;\n /** Full precision memory usage (bytes) */\n fullPrecisionBytes: number;\n /** Quantized memory usage (bytes) */\n quantizedBytes: number;\n /** Memory reduction ratio */\n memoryReductionRatio: number;\n /** Average quantization error */\n avgQuantizationError: number;\n}\n\n/**\n * Search result from quantized vector store.\n */\nexport interface QuantizedSearchResult {\n /** Entity ID */\n id: string;\n /** Similarity score (0-1) */\n similarity: number;\n /** Whether result used quantized computation */\n quantized: boolean;\n}\n\n/**\n * Configuration options for QuantizedVectorStore.\n */\nexport interface QuantizedVectorStoreOptions {\n /** Use asymmetric similarity (query in full precision) */\n asymmetric?: boolean;\n /** Minimum vectors before enabling quantization */\n minVectorsForQuantization?: number;\n /** Enable accuracy tracking */\n trackAccuracy?: boolean;\n}\n\nconst DEFAULT_OPTIONS: Required<QuantizedVectorStoreOptions> = {\n asymmetric: true,\n minVectorsForQuantization: 100,\n trackAccuracy: false,\n};\n\n/**\n * Quantized Vector Store with 8-bit scalar quantization.\n *\n * Provides 4x memory reduction while maintaining >95% accuracy\n * using asymmetric similarity computation.\n *\n * @example\n * ```typescript\n * const store = new QuantizedVectorStore();\n *\n * // Add vectors\n * store.add('entity1', [0.1, 0.2, 0.3, ...]);\n * store.add('entity2', [0.4, 0.5, 0.6, ...]);\n *\n * // Search\n * const results = store.search([0.15, 0.25, 0.35, ...], 10);\n *\n * // Get stats\n * const stats = store.getStats();\n * console.log(`Memory reduction: ${stats.memoryReductionRatio}x`);\n * ```\n */\nexport class QuantizedVectorStore {\n private fullPrecisionVectors: Map<string, Float32Array>;\n private quantizedVectors: Map<string, Uint8Array>;\n private quantizationParams: QuantizationParams | null = null;\n private options: Required<QuantizedVectorStoreOptions>;\n private isQuantized = false;\n private quantizationErrors: number[] = [];\n\n constructor(options?: QuantizedVectorStoreOptions) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n this.fullPrecisionVectors = new Map();\n this.quantizedVectors = new Map();\n }\n\n /**\n * Add a vector to the store.\n *\n * @param id - Entity identifier\n * @param vector - Float vector (any dimension, must be consistent)\n */\n add(id: string, vector: number[]): void {\n const float32 = new Float32Array(vector);\n this.fullPrecisionVectors.set(id, float32);\n\n // Check if we should quantize\n if (\n !this.isQuantized &&\n this.fullPrecisionVectors.size >= this.options.minVectorsForQuantization\n ) {\n this.quantize();\n } else if (this.isQuantized) {\n // Add to quantized store\n const quantized = this.quantizeVector(float32);\n this.quantizedVectors.set(id, quantized);\n\n // Track error if enabled\n if (this.options.trackAccuracy) {\n const reconstructed = this.dequantizeVector(quantized);\n this.quantizationErrors.push(this.computeError(float32, reconstructed));\n }\n }\n }\n\n /**\n * Remove a vector from the store.\n *\n * @param id - Entity identifier\n * @returns True if vector was removed\n */\n remove(id: string): boolean {\n const existed = this.fullPrecisionVectors.delete(id);\n this.quantizedVectors.delete(id);\n return existed;\n }\n\n /**\n * Check if a vector exists.\n *\n * @param id - Entity identifier\n */\n has(id: string): boolean {\n return this.fullPrecisionVectors.has(id);\n }\n\n /**\n * Get a vector (dequantized if necessary).\n *\n * @param id - Entity identifier\n * @returns Vector or undefined\n */\n get(id: string): number[] | undefined {\n const vector = this.fullPrecisionVectors.get(id);\n return vector ? Array.from(vector) : undefined;\n }\n\n /**\n * Search for similar vectors.\n *\n * @param query - Query vector\n * @param k - Number of results to return\n * @returns Top k similar vectors with scores\n */\n search(query: number[], k: number): QuantizedSearchResult[] {\n const queryVector = new Float32Array(query);\n const results: QuantizedSearchResult[] = [];\n\n if (this.isQuantized && this.options.asymmetric) {\n // Asymmetric search: query in full precision, stored vectors quantized\n for (const [id, quantized] of this.quantizedVectors) {\n const reconstructed = this.dequantizeVector(quantized);\n const similarity = this.cosineSimilarity(queryVector, reconstructed);\n results.push({ id, similarity, quantized: true });\n }\n } else {\n // Full precision search\n for (const [id, vector] of this.fullPrecisionVectors) {\n const similarity = this.cosineSimilarity(queryVector, vector);\n results.push({ id, similarity, quantized: false });\n }\n }\n\n // Sort by similarity descending and take top k\n return results\n .sort((a, b) => b.similarity - a.similarity)\n .slice(0, k);\n }\n\n /**\n * Compute similarity between a query and specific entity.\n *\n * @param query - Query vector\n * @param id - Entity identifier\n * @returns Similarity score or undefined if not found\n */\n computeSimilarity(query: number[], id: string): number | undefined {\n const queryVector = new Float32Array(query);\n\n if (this.isQuantized && this.options.asymmetric) {\n const quantized = this.quantizedVectors.get(id);\n if (!quantized) return undefined;\n const reconstructed = this.dequantizeVector(quantized);\n return this.cosineSimilarity(queryVector, reconstructed);\n } else {\n const vector = this.fullPrecisionVectors.get(id);\n if (!vector) return undefined;\n return this.cosineSimilarity(queryVector, vector);\n }\n }\n\n /**\n * Force quantization of all vectors.\n */\n quantize(): void {\n if (this.fullPrecisionVectors.size === 0) return;\n\n // Compute quantization parameters\n this.quantizationParams = this.computeQuantizationParams();\n this.isQuantized = true;\n\n // Quantize all vectors\n this.quantizedVectors.clear();\n for (const [id, vector] of this.fullPrecisionVectors) {\n const quantized = this.quantizeVector(vector);\n this.quantizedVectors.set(id, quantized);\n\n // Track error if enabled\n if (this.options.trackAccuracy) {\n const reconstructed = this.dequantizeVector(quantized);\n this.quantizationErrors.push(this.computeError(vector, reconstructed));\n }\n }\n }\n\n /**\n * Get store statistics.\n */\n getStats(): QuantizedVectorStoreStats {\n const vectorCount = this.fullPrecisionVectors.size;\n const dimension = this.quantizationParams?.dimension ??\n (vectorCount > 0 ? this.fullPrecisionVectors.values().next().value!.length : 0);\n\n const fullPrecisionBytes = vectorCount * dimension * 4; // Float32\n const quantizedBytes = vectorCount * dimension * 1; // Uint8\n\n const avgQuantizationError = this.quantizationErrors.length > 0\n ? this.quantizationErrors.reduce((a, b) => a + b, 0) / this.quantizationErrors.length\n : 0;\n\n return {\n vectorCount,\n dimension,\n fullPrecisionBytes,\n quantizedBytes,\n memoryReductionRatio: fullPrecisionBytes > 0 ? fullPrecisionBytes / quantizedBytes : 1,\n avgQuantizationError,\n };\n }\n\n /**\n * Check if store is currently using quantization.\n */\n isUsingQuantization(): boolean {\n return this.isQuantized;\n }\n\n /**\n * Get the number of stored vectors.\n */\n size(): number {\n return this.fullPrecisionVectors.size;\n }\n\n /**\n * Clear all vectors from the store.\n */\n clear(): void {\n this.fullPrecisionVectors.clear();\n this.quantizedVectors.clear();\n this.quantizationParams = null;\n this.isQuantized = false;\n this.quantizationErrors = [];\n }\n\n /**\n * Export all vectors.\n */\n export(): Map<string, number[]> {\n const result = new Map<string, number[]>();\n for (const [id, vector] of this.fullPrecisionVectors) {\n result.set(id, Array.from(vector));\n }\n return result;\n }\n\n /**\n * Import vectors from a map.\n *\n * @param vectors - Map of id to vector\n * @param quantize - Whether to quantize after import\n */\n import(vectors: Map<string, number[]>, quantize = true): void {\n for (const [id, vector] of vectors) {\n const float32 = new Float32Array(vector);\n this.fullPrecisionVectors.set(id, float32);\n }\n\n if (quantize && this.fullPrecisionVectors.size >= this.options.minVectorsForQuantization) {\n this.quantize();\n }\n }\n\n // Private methods\n\n private computeQuantizationParams(): QuantizationParams {\n let min = Infinity;\n let max = -Infinity;\n let dimension = 0;\n\n for (const vector of this.fullPrecisionVectors.values()) {\n dimension = vector.length;\n for (let i = 0; i < vector.length; i++) {\n if (vector[i] < min) min = vector[i];\n if (vector[i] > max) max = vector[i];\n }\n }\n\n const scale = (max - min) / 255;\n\n return { min, max, scale, dimension };\n }\n\n private quantizeVector(vector: Float32Array): Uint8Array {\n if (!this.quantizationParams) {\n throw new Error('Quantization params not initialized');\n }\n\n const { min, scale } = this.quantizationParams;\n const quantized = new Uint8Array(vector.length);\n\n for (let i = 0; i < vector.length; i++) {\n // Clamp to 0-255 range\n const normalized = (vector[i] - min) / scale;\n quantized[i] = Math.max(0, Math.min(255, Math.round(normalized)));\n }\n\n return quantized;\n }\n\n private dequantizeVector(quantized: Uint8Array): Float32Array {\n if (!this.quantizationParams) {\n throw new Error('Quantization params not initialized');\n }\n\n const { min, scale } = this.quantizationParams;\n const vector = new Float32Array(quantized.length);\n\n for (let i = 0; i < quantized.length; i++) {\n vector[i] = quantized[i] * scale + min;\n }\n\n return vector;\n }\n\n private cosineSimilarity(a: Float32Array, b: Float32Array): number {\n if (a.length !== b.length) return 0;\n\n let dotProduct = 0;\n let normA = 0;\n let normB = 0;\n\n for (let i = 0; i < a.length; i++) {\n dotProduct += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n\n const denominator = Math.sqrt(normA) * Math.sqrt(normB);\n return denominator === 0 ? 0 : dotProduct / denominator;\n }\n\n private computeError(original: Float32Array, reconstructed: Float32Array): number {\n let sumSquaredError = 0;\n for (let i = 0; i < original.length; i++) {\n const diff = original[i] - reconstructed[i];\n sumSquaredError += diff * diff;\n }\n return Math.sqrt(sumSquaredError / original.length);\n }\n}\n","/**\n * Query Logger\n *\n * Provides structured logging for search operations.\n * Supports console, file, and callback outputs.\n * Phase 1 Sprint 6: Query Logging and Tracing.\n *\n * @module search/QueryLogger\n */\n\nimport { appendFileSync } from 'fs';\nimport type { LogLevel, QueryLogEntry } from '../types/search.js';\n\n/**\n * Configuration for QueryLogger.\n */\nexport interface QueryLoggerConfig {\n /** Minimum log level to output */\n level?: LogLevel;\n /** Log to console */\n console?: boolean;\n /** Log to file path */\n filePath?: string;\n /** Custom callback for log entries */\n callback?: (entry: QueryLogEntry) => void;\n /** Include timestamps in console output */\n timestamps?: boolean;\n}\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\n/**\n * Query logger for search operations.\n *\n * @example\n * ```typescript\n * const logger = new QueryLogger({ level: 'debug', console: true });\n * logger.logQueryStart('q123', 'hello world', 'ranked');\n * // ... search executes ...\n * logger.logQueryEnd('q123', 150, 10);\n * ```\n */\nexport class QueryLogger {\n private readonly config: Required<Omit<QueryLoggerConfig, 'callback'>> & {\n callback?: (entry: QueryLogEntry) => void;\n };\n\n constructor(config: QueryLoggerConfig = {}) {\n this.config = {\n level: config.level ?? (process.env.MEMORY_QUERY_LOG_LEVEL as LogLevel) ?? 'info',\n console: config.console ?? process.env.MEMORY_QUERY_LOGGING === 'true',\n filePath: config.filePath ?? process.env.MEMORY_QUERY_LOG_FILE ?? '',\n callback: config.callback,\n timestamps: config.timestamps ?? true,\n };\n }\n\n /**\n * Check if logging is enabled (any output configured).\n */\n isEnabled(): boolean {\n return this.config.console || !!this.config.filePath || !!this.config.callback;\n }\n\n /**\n * Log the start of a query.\n */\n logQueryStart(queryId: string, queryText: string, queryType: string): void {\n this.log('info', 'query_start', {\n queryId,\n queryText,\n queryType,\n });\n }\n\n /**\n * Log the end of a query.\n */\n logQueryEnd(queryId: string, durationMs: number, resultCount: number): void {\n this.log('info', 'query_end', {\n queryId,\n duration: durationMs,\n resultCount,\n });\n }\n\n /**\n * Log a query stage.\n */\n logStage(\n queryId: string,\n stage: string,\n durationMs: number,\n metadata?: Record<string, unknown>\n ): void {\n this.log('debug', `stage_${stage}`, {\n queryId,\n duration: durationMs,\n metadata,\n });\n }\n\n /**\n * Log a debug message.\n */\n debug(queryId: string, message: string, metadata?: Record<string, unknown>): void {\n this.log('debug', message, { queryId, metadata });\n }\n\n /**\n * Log an info message.\n */\n info(queryId: string, message: string, metadata?: Record<string, unknown>): void {\n this.log('info', message, { queryId, metadata });\n }\n\n /**\n * Log a warning.\n */\n warn(queryId: string, message: string, metadata?: Record<string, unknown>): void {\n this.log('warn', message, { queryId, metadata });\n }\n\n /**\n * Log an error.\n */\n error(queryId: string, message: string, error?: Error): void {\n this.log('error', message, {\n queryId,\n metadata: error ? { error: error.message, stack: error.stack } : undefined,\n });\n }\n\n private log(level: LogLevel, event: string, data: Partial<QueryLogEntry>): void {\n if (LOG_LEVELS[level] < LOG_LEVELS[this.config.level]) {\n return;\n }\n\n const entry: QueryLogEntry = {\n timestamp: new Date().toISOString(),\n queryId: data.queryId ?? 'unknown',\n level,\n event,\n ...data,\n };\n\n // Console output\n if (this.config.console) {\n const prefix = this.config.timestamps ? `[${entry.timestamp}] ` : '';\n const msg = `${prefix}[${level.toUpperCase()}] ${event} - ${JSON.stringify(this.formatConsoleData(data))}`;\n switch (level) {\n case 'debug':\n console.debug(msg);\n break;\n case 'info':\n console.info(msg);\n break;\n case 'warn':\n console.warn(msg);\n break;\n case 'error':\n console.error(msg);\n break;\n }\n }\n\n // File output\n if (this.config.filePath) {\n try {\n appendFileSync(this.config.filePath, JSON.stringify(entry) + '\\n');\n } catch {\n // Silently fail file writes to not disrupt search operations\n }\n }\n\n // Callback\n if (this.config.callback) {\n try {\n this.config.callback(entry);\n } catch {\n // Silently fail callbacks to not disrupt search operations\n }\n }\n }\n\n /**\n * Format data for console output (remove undefined values).\n */\n private formatConsoleData(data: Partial<QueryLogEntry>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (value !== undefined) {\n result[key] = value;\n }\n }\n return result;\n }\n\n /**\n * Generate a unique query ID.\n */\n static generateQueryId(): string {\n return `q_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n }\n}\n","/**\n * Query Parser\n *\n * Parses search query strings into structured query objects.\n * Supports: phrases (\"...\"), wildcards (*?), proximity (~N), fields (field:).\n * Phase 1 Sprint 8: Full-Text Search Operators.\n *\n * @module search/QueryParser\n */\n\nimport type { QueryNode, BooleanOpNode } from '../types/search.js';\n\n/**\n * Query parser for advanced search syntax.\n *\n * @example\n * ```typescript\n * const parser = new QueryParser();\n *\n * // Phrase search\n * parser.parse('\"machine learning\"');\n *\n * // Wildcard search\n * parser.parse('data*');\n *\n * // Proximity search\n * parser.parse('\"machine learning\"~3');\n *\n * // Field-specific search\n * parser.parse('name:Alice');\n *\n * // Combined\n * parser.parse('name:\"John Doe\" AND type:person');\n * ```\n */\nexport class QueryParser {\n /**\n * Parse a query string into a QueryNode tree.\n */\n parse(query: string): QueryNode {\n const trimmed = query.trim();\n if (!trimmed) {\n return { type: 'term', value: '' };\n }\n\n // Check for boolean operators first\n const booleanNode = this.parseBooleanExpression(trimmed);\n if (booleanNode) {\n return booleanNode;\n }\n\n // Parse as simple query\n const tokens = this.tokenize(trimmed);\n return this.parseTokens(tokens);\n }\n\n /**\n * Check if a query uses advanced operators.\n */\n hasAdvancedOperators(query: string): boolean {\n // Check for quotes (phrase/proximity)\n if (query.includes('\"')) return true;\n\n // Check for wildcards\n if (query.includes('*') || query.includes('?')) return true;\n\n // Check for field specifiers\n if (/\\w+:/.test(query)) return true;\n\n // Check for boolean operators\n if (/\\b(AND|OR|NOT)\\b/i.test(query)) return true;\n\n return false;\n }\n\n /**\n * Parse boolean expressions (AND, OR, NOT).\n */\n private parseBooleanExpression(query: string): BooleanOpNode | null {\n // Simple regex-based parsing for AND/OR/NOT\n // More sophisticated parsing would use proper lexer/parser\n\n // Split by OR (lowest precedence)\n const orParts = this.splitByOperator(query, 'OR');\n if (orParts.length > 1) {\n return {\n type: 'boolean',\n operator: 'OR',\n operands: orParts.map((p) => this.parse(p.trim())),\n };\n }\n\n // Split by AND\n const andParts = this.splitByOperator(query, 'AND');\n if (andParts.length > 1) {\n return {\n type: 'boolean',\n operator: 'AND',\n operands: andParts.map((p) => this.parse(p.trim())),\n };\n }\n\n // Check for NOT prefix\n const notMatch = query.match(/^NOT\\s+(.+)$/i);\n if (notMatch) {\n return {\n type: 'boolean',\n operator: 'NOT',\n operands: [this.parse(notMatch[1].trim())],\n };\n }\n\n return null;\n }\n\n /**\n * Split query by operator, respecting quotes.\n */\n private splitByOperator(query: string, operator: string): string[] {\n const parts: string[] = [];\n let current = '';\n let inQuotes = false;\n let depth = 0;\n const regex = new RegExp(`\\\\b${operator}\\\\b`, 'i');\n\n for (let i = 0; i < query.length; i++) {\n const char = query[i];\n\n if (char === '\"') {\n inQuotes = !inQuotes;\n current += char;\n } else if (char === '(') {\n depth++;\n current += char;\n } else if (char === ')') {\n depth--;\n current += char;\n } else if (!inQuotes && depth === 0) {\n // Check if we're at the operator\n const remaining = query.slice(i);\n const match = remaining.match(regex);\n if (match && match.index === 0) {\n parts.push(current.trim());\n current = '';\n i += operator.length - 1; // Skip operator\n continue;\n }\n current += char;\n } else {\n current += char;\n }\n }\n\n if (current.trim()) {\n parts.push(current.trim());\n }\n\n return parts;\n }\n\n /**\n * Tokenize query string, handling quotes.\n */\n private tokenize(query: string): string[] {\n const tokens: string[] = [];\n let current = '';\n let inQuotes = false;\n\n for (let i = 0; i < query.length; i++) {\n const char = query[i];\n\n if (char === '\"') {\n if (inQuotes) {\n // End of quote - check for proximity suffix\n const remaining = query.slice(i + 1);\n const proximityMatch = remaining.match(/^~(\\d+)/);\n if (proximityMatch) {\n tokens.push(`\"${current}\"~${proximityMatch[1]}`);\n i += proximityMatch[0].length;\n } else {\n tokens.push(`\"${current}\"`);\n }\n current = '';\n }\n inQuotes = !inQuotes;\n } else if (char === ' ' && !inQuotes) {\n if (current) {\n tokens.push(current);\n current = '';\n }\n } else {\n current += char;\n }\n }\n\n if (current) {\n tokens.push(current);\n }\n\n return tokens;\n }\n\n /**\n * Parse tokenized query.\n */\n private parseTokens(tokens: string[]): QueryNode {\n // Handle single token\n if (tokens.length === 1) {\n return this.parseToken(tokens[0]);\n }\n\n // Default to AND for multiple tokens\n const operands = tokens.map((t) => this.parseToken(t));\n return {\n type: 'boolean',\n operator: 'AND',\n operands,\n };\n }\n\n /**\n * Parse a single token into a QueryNode.\n */\n private parseToken(token: string): QueryNode {\n // Proximity: \"term1 term2\"~5\n const proximityMatch = token.match(/^\"(.+)\"~(\\d+)$/);\n if (proximityMatch) {\n return {\n type: 'proximity',\n terms: proximityMatch[1].toLowerCase().split(/\\s+/),\n distance: parseInt(proximityMatch[2], 10),\n };\n }\n\n // Phrase: \"exact phrase\"\n if (token.startsWith('\"') && token.endsWith('\"')) {\n const phrase = token.slice(1, -1);\n return {\n type: 'phrase',\n terms: phrase.toLowerCase().split(/\\s+/),\n };\n }\n\n // Field: field:value\n const fieldMatch = token.match(/^(\\w+):(.+)$/);\n if (fieldMatch) {\n return {\n type: 'field',\n field: fieldMatch[1].toLowerCase(),\n query: this.parseToken(fieldMatch[2]),\n };\n }\n\n // Wildcard: contains * or ?\n if (token.includes('*') || token.includes('?')) {\n return {\n type: 'wildcard',\n pattern: token,\n regex: this.wildcardToRegex(token),\n };\n }\n\n // Plain term\n return {\n type: 'term',\n value: token.toLowerCase(),\n };\n }\n\n /**\n * Convert wildcard pattern to regex.\n */\n private wildcardToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n return new RegExp(`^${escaped}$`, 'i');\n }\n}\n\n/**\n * Check if text contains the phrase (terms in order).\n */\nexport function matchesPhrase(text: string, terms: string[]): boolean {\n if (terms.length === 0) return false;\n\n const lowerText = text.toLowerCase();\n const words = lowerText.split(/\\s+/);\n\n for (let i = 0; i <= words.length - terms.length; i++) {\n let match = true;\n for (let j = 0; j < terms.length; j++) {\n if (words[i + j] !== terms[j]) {\n match = false;\n break;\n }\n }\n if (match) return true;\n }\n\n return false;\n}\n\n/**\n * Check if pattern is a simple prefix (e.g., 'foo*').\n */\nexport function isPrefixPattern(pattern: string): boolean {\n const starIndex = pattern.indexOf('*');\n return starIndex === pattern.length - 1 && !pattern.includes('?');\n}\n\n/**\n * Match text against prefix pattern.\n */\nexport function matchesPrefix(text: string, pattern: string): boolean {\n const prefix = pattern.slice(0, -1).toLowerCase();\n return text.toLowerCase().startsWith(prefix);\n}\n","/**\n * Proximity Search\n *\n * Finds entities where terms appear within a specified distance.\n * Phase 1 Sprint 8: Full-Text Search Operators.\n *\n * @module search/ProximitySearch\n */\n\nimport type { Entity } from '../types/types.js';\nimport type { ProximityNode } from '../types/search.js';\n\n/**\n * Result of a proximity match.\n */\nexport interface ProximityMatch {\n /** The matched entity */\n entity: Entity;\n /** Score based on proximity (closer = higher) */\n score: number;\n /** Minimum distance found between terms */\n minDistance: number;\n /** Detailed match locations */\n matches: ProximityMatchLocation[];\n}\n\n/**\n * Location where proximity match was found.\n */\nexport interface ProximityMatchLocation {\n /** Field where match was found */\n field: string;\n /** Term positions in the text */\n positions: Map<string, number[]>;\n /** Distance between terms */\n distance: number;\n}\n\n/**\n * Proximity search implementation.\n *\n * @example\n * ```typescript\n * const search = new ProximitySearch();\n *\n * // Find entities where \"machine\" and \"learning\" appear within 3 words\n * const results = search.search(entities, {\n * type: 'proximity',\n * terms: ['machine', 'learning'],\n * distance: 3\n * });\n * ```\n */\nexport class ProximitySearch {\n /**\n * Search for entities where terms appear within distance.\n */\n search(entities: Entity[], node: ProximityNode): ProximityMatch[] {\n const results: ProximityMatch[] = [];\n\n for (const entity of entities) {\n const match = this.matchEntity(entity, node);\n if (match) {\n results.push(match);\n }\n }\n\n // Sort by score (closer = higher)\n return results.sort((a, b) => b.score - a.score);\n }\n\n /**\n * Check if entity matches the proximity query.\n */\n private matchEntity(entity: Entity, node: ProximityNode): ProximityMatch | null {\n const locations: ProximityMatchLocation[] = [];\n let minDistance = Infinity;\n\n // Check entity name\n const nameDistance = this.findProximity(entity.name, node.terms);\n if (nameDistance !== null && nameDistance <= node.distance) {\n locations.push({\n field: 'name',\n positions: this.getPositions(entity.name, node.terms),\n distance: nameDistance,\n });\n minDistance = Math.min(minDistance, nameDistance);\n }\n\n // Check observations\n for (const obs of entity.observations || []) {\n const distance = this.findProximity(obs, node.terms);\n if (distance !== null && distance <= node.distance) {\n locations.push({\n field: 'observation',\n positions: this.getPositions(obs, node.terms),\n distance,\n });\n minDistance = Math.min(minDistance, distance);\n }\n }\n\n if (locations.length === 0) return null;\n\n // Score: higher for closer matches\n // 1.0 for adjacent terms, decreasing as distance increases\n const score = 1 / (1 + minDistance);\n\n return {\n entity,\n score,\n minDistance,\n matches: locations,\n };\n }\n\n /**\n * Find minimum distance between all terms in text.\n * Returns null if any term is not found.\n */\n private findProximity(text: string, terms: string[]): number | null {\n if (terms.length === 0) return null;\n if (terms.length === 1) return 0; // Single term is always \"adjacent\" to itself\n\n const words = text.toLowerCase().split(/\\s+/);\n const positions = new Map<string, number[]>();\n\n // Find all positions of each term\n for (const term of terms) {\n const termPositions: number[] = [];\n for (let i = 0; i < words.length; i++) {\n // Check for exact match or contains\n if (words[i] === term || words[i].includes(term)) {\n termPositions.push(i);\n }\n }\n if (termPositions.length === 0) return null; // Term not found\n positions.set(term, termPositions);\n }\n\n // Find minimum span containing all terms\n return this.findMinSpan(positions, terms);\n }\n\n /**\n * Find minimum span containing all terms.\n */\n private findMinSpan(positions: Map<string, number[]>, terms: string[]): number {\n const allPositions = terms.map((t) => positions.get(t)!);\n let minSpan = Infinity;\n\n // Use recursive approach to find minimum span\n // (For small number of terms, this is efficient enough)\n const findMin = (index: number, selected: number[]): void => {\n if (index === allPositions.length) {\n const span = Math.max(...selected) - Math.min(...selected);\n minSpan = Math.min(minSpan, span);\n return;\n }\n\n for (const pos of allPositions[index]) {\n findMin(index + 1, [...selected, pos]);\n }\n };\n\n findMin(0, []);\n return minSpan;\n }\n\n /**\n * Get positions of all terms in text.\n */\n private getPositions(text: string, terms: string[]): Map<string, number[]> {\n const words = text.toLowerCase().split(/\\s+/);\n const positions = new Map<string, number[]>();\n\n for (const term of terms) {\n const termPositions: number[] = [];\n for (let i = 0; i < words.length; i++) {\n if (words[i] === term || words[i].includes(term)) {\n termPositions.push(i);\n }\n }\n positions.set(term, termPositions);\n }\n\n return positions;\n }\n\n /**\n * Calculate proximity score between two specific terms.\n */\n static calculateProximityScore(\n text: string,\n term1: string,\n term2: string,\n maxDistance: number\n ): number | null {\n const words = text.toLowerCase().split(/\\s+/);\n const positions1: number[] = [];\n const positions2: number[] = [];\n\n for (let i = 0; i < words.length; i++) {\n if (words[i].includes(term1.toLowerCase())) {\n positions1.push(i);\n }\n if (words[i].includes(term2.toLowerCase())) {\n positions2.push(i);\n }\n }\n\n if (positions1.length === 0 || positions2.length === 0) {\n return null;\n }\n\n // Find minimum distance\n let minDist = Infinity;\n for (const p1 of positions1) {\n for (const p2 of positions2) {\n minDist = Math.min(minDist, Math.abs(p1 - p2));\n }\n }\n\n if (minDist > maxDistance) {\n return null;\n }\n\n // Score: 1.0 for adjacent, decreasing as distance increases\n return 1 / (1 + minDist);\n }\n}\n","/**\r\n * Search Module Barrel Export\r\n *\r\n * Sprint 2: Added SearchFilterChain for centralized filter logic\r\n * Phase 4 Sprint 10: Added EmbeddingService for semantic search\r\n */\r\n\r\nexport { BasicSearch } from './BasicSearch.js';\r\nexport { RankedSearch } from './RankedSearch.js';\r\nexport { BooleanSearch } from './BooleanSearch.js';\r\nexport { FuzzySearch, type FuzzySearchOptions } from './FuzzySearch.js';\r\nexport { SearchSuggestions } from './SearchSuggestions.js';\r\nexport { SavedSearchManager } from './SavedSearchManager.js';\r\nexport { SearchManager } from './SearchManager.js';\r\n\r\n// Sprint 2: Search Filter Chain utilities\r\nexport { SearchFilterChain, type SearchFilters, type ValidatedPagination } from './SearchFilterChain.js';\r\n\r\n// Phase 4 Sprint 10: Embedding Service for semantic search\r\n// Phase 12 Sprint 5: Added l2Normalize, prefixes, and progress callback\r\nexport {\r\n OpenAIEmbeddingService,\r\n LocalEmbeddingService,\r\n MockEmbeddingService,\r\n createEmbeddingService,\r\n l2Normalize,\r\n QUERY_PREFIX,\r\n DOCUMENT_PREFIX,\r\n type EmbeddingProgressCallback,\r\n} from './EmbeddingService.js';\r\n\r\n// Phase 12 Sprint 5: Embedding Cache with LRU eviction\r\nexport {\r\n EmbeddingCache,\r\n DEFAULT_EMBEDDING_CACHE_OPTIONS,\r\n type EmbeddingCacheStats,\r\n type EmbeddingCacheOptions,\r\n} from './EmbeddingCache.js';\r\n\r\n// Phase 12 Sprint 5: Incremental Indexer for batch updates\r\nexport {\r\n IncrementalIndexer,\r\n DEFAULT_INDEXER_OPTIONS,\r\n type IndexOperationType,\r\n type IndexOperation,\r\n type IncrementalIndexerOptions,\r\n type FlushResult,\r\n} from './IncrementalIndexer.js';\r\n\r\n// Phase 4 Sprint 11: Vector Store for semantic search\r\nexport {\r\n InMemoryVectorStore,\r\n SQLiteVectorStore,\r\n createVectorStore,\r\n cosineSimilarity,\r\n type SQLiteStorageWithEmbeddings,\r\n} from './VectorStore.js';\r\n\r\n// Phase 4 Sprint 12: Semantic Search Manager\r\nexport {\r\n SemanticSearch,\r\n entityToText,\r\n} from './SemanticSearch.js';\r\n\r\n// Phase 10 Sprint 3: TF-IDF Index Manager and Event Sync\r\nexport { TFIDFIndexManager } from './TFIDFIndexManager.js';\r\nexport { TFIDFEventSync } from './TFIDFEventSync.js';\r\n\r\n// Phase 10 Sprint 4: Query Cost Estimation\r\n// Phase 12 Sprint 4: Enhanced with adaptive depth, token estimation, layer recommendations\r\nexport {\r\n QueryCostEstimator,\r\n type SearchLayer,\r\n type ExtendedQueryCostEstimate,\r\n type LayerRecommendationOptions,\r\n type TokenEstimationOptions,\r\n type AdaptiveDepthConfig,\r\n} from './QueryCostEstimator.js';\r\n\r\n// Phase 11 Sprint 1: Hybrid Search\r\nexport { SymbolicSearch, type SymbolicResult } from './SymbolicSearch.js';\r\nexport { HybridSearchManager, DEFAULT_HYBRID_WEIGHTS } from './HybridSearchManager.js';\r\n\r\n// Phase 11 Sprint 3: Query Analysis\r\nexport { QueryAnalyzer } from './QueryAnalyzer.js';\r\nexport { QueryPlanner } from './QueryPlanner.js';\r\n\r\n// Phase 11 Sprint 4: Reflection-based Retrieval\r\n// Phase 12 Sprint 4: Enhanced with progressive limits, focused refinement, history tracking\r\nexport {\r\n ReflectionManager,\r\n type ReflectionOptions,\r\n type ReflectionResult,\r\n type RefinementHistoryEntry,\r\n} from './ReflectionManager.js';\r\n\r\n// Phase 12 Sprint 3: Search Algorithm Optimization\r\nexport {\r\n BM25Search,\r\n STOPWORDS,\r\n DEFAULT_BM25_CONFIG,\r\n type BM25DocumentEntry,\r\n type BM25Index,\r\n type BM25Config,\r\n} from './BM25Search.js';\r\n\r\nexport {\r\n OptimizedInvertedIndex,\r\n type IndexMemoryUsage,\r\n type PostingListResult,\r\n} from './OptimizedInvertedIndex.js';\r\n\r\nexport {\r\n HybridScorer,\r\n DEFAULT_SCORER_WEIGHTS,\r\n type SemanticLayerResult,\r\n type LexicalSearchResult,\r\n type SymbolicSearchResult,\r\n type ScoredResult,\r\n type HybridWeights,\r\n type HybridScorerOptions,\r\n} from './HybridScorer.js';\r\n\r\n// Phase 12 Sprint 2: Parallel Search Execution\r\nexport {\r\n ParallelSearchExecutor,\r\n type LayerTiming,\r\n type ParallelSearchResult,\r\n type ParallelSearchOptions,\r\n} from './ParallelSearchExecutor.js';\r\n\r\n// Phase 12 Sprint 4: Query Execution Optimization\r\nexport {\r\n EarlyTerminationManager,\r\n type AdequacyCheck,\r\n type EarlyTerminationOptions,\r\n type EarlyTerminationResult,\r\n} from './EarlyTerminationManager.js';\r\n\r\nexport {\r\n QueryPlanCache,\r\n type CachedQueryEntry,\r\n type QueryPlanCacheStats,\r\n type QueryPlanCacheOptions,\r\n} from './QueryPlanCache.js';\r\n\r\n// Phase 12 Sprint 6: Quantized Vector Store\r\nexport {\r\n QuantizedVectorStore,\r\n type QuantizationParams,\r\n type QuantizedVectorStoreStats,\r\n type QuantizedSearchResult,\r\n type QuantizedVectorStoreOptions,\r\n} from './QuantizedVectorStore.js';\r\n\r\n// Phase 1 Sprint 6: Query Logging and Tracing\r\nexport { QueryLogger, type QueryLoggerConfig } from './QueryLogger.js';\r\n\r\n// Phase 1 Sprint 8: Full-Text Search Operators\r\nexport { QueryParser, matchesPhrase, isPrefixPattern, matchesPrefix } from './QueryParser.js';\r\nexport {\r\n ProximitySearch,\r\n type ProximityMatch,\r\n type ProximityMatchLocation,\r\n} from './ProximitySearch.js';\r\n","/**\r\n * Tag Manager\r\n *\r\n * Manages tag aliases and canonical tag resolution.\r\n *\r\n * @module features/TagManager\r\n */\r\n\r\nimport * as fs from 'fs/promises';\r\nimport type { TagAlias } from '../types/index.js';\r\n\r\n/**\r\n * Manages tag alias system for synonym mapping.\r\n */\r\nexport class TagManager {\r\n constructor(private tagAliasesFilePath: string) {}\r\n\r\n /**\r\n * Load all tag aliases from JSONL file.\r\n *\r\n * @returns Array of tag aliases\r\n */\r\n private async loadTagAliases(): Promise<TagAlias[]> {\r\n try {\r\n const data = await fs.readFile(this.tagAliasesFilePath, 'utf-8');\r\n const lines = data.split('\\n').filter((line: string) => line.trim() !== '');\r\n return lines.map((line: string) => JSON.parse(line) as TagAlias);\r\n } catch (error) {\r\n if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {\r\n return [];\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Save tag aliases to JSONL file.\r\n *\r\n * @param aliases - Array of tag aliases\r\n */\r\n private async saveTagAliases(aliases: TagAlias[]): Promise<void> {\r\n const lines = aliases.map(a => JSON.stringify(a));\r\n await fs.writeFile(this.tagAliasesFilePath, lines.join('\\n'));\r\n }\r\n\r\n /**\r\n * Resolve a tag through aliases to get its canonical form.\r\n *\r\n * This method follows the alias chain to find the canonical (main) tag name.\r\n * All tags are normalized to lowercase for consistency.\r\n * If the tag has no alias, it returns the tag itself as canonical.\r\n *\r\n * @param tag - Tag to resolve (can be alias or canonical)\r\n * @returns Canonical tag name (lowercase)\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new TagManager(tagAliasesPath);\r\n *\r\n * // Set up: assume \"js\" is aliased to \"javascript\"\r\n * await manager.addTagAlias('js', 'javascript');\r\n *\r\n * // Resolve alias to canonical\r\n * const canonical = await manager.resolveTag('js');\r\n * console.log(canonical); // \"javascript\"\r\n *\r\n * // Resolve canonical tag (returns as-is)\r\n * const unchanged = await manager.resolveTag('javascript');\r\n * console.log(unchanged); // \"javascript\"\r\n *\r\n * // Resolve unknown tag (returns normalized)\r\n * const unknown = await manager.resolveTag('PYTHON');\r\n * console.log(unknown); // \"python\"\r\n * ```\r\n */\r\n async resolveTag(tag: string): Promise<string> {\r\n const aliases = await this.loadTagAliases();\r\n const normalized = tag.toLowerCase();\r\n\r\n // Check if this tag is an alias\r\n const alias = aliases.find(a => a.alias === normalized);\r\n if (alias) {\r\n return alias.canonical;\r\n }\r\n\r\n // Return as-is (might be canonical or unaliased tag)\r\n return normalized;\r\n }\r\n\r\n /**\r\n * Add a tag alias (synonym mapping).\r\n *\r\n * Creates a mapping from an alias (synonym) to a canonical (main) tag.\r\n * This enables flexible tagging where users can use different terms\r\n * that all resolve to the same canonical tag.\r\n *\r\n * Validation rules:\r\n * - Prevents duplicate aliases (same alias can't map to different canonicals)\r\n * - Prevents chained aliases (alias must point to canonical, not another alias)\r\n * - All tags are normalized to lowercase\r\n *\r\n * @param alias - The alias/synonym (will be normalized to lowercase)\r\n * @param canonical - The canonical (main) tag name (will be normalized to lowercase)\r\n * @param description - Optional description explaining the alias relationship\r\n * @returns Newly created TagAlias object with metadata\r\n * @throws {Error} If alias already exists or would create chained aliases\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new TagManager(tagAliasesPath);\r\n *\r\n * // Create simple alias\r\n * await manager.addTagAlias('js', 'javascript', 'Common abbreviation');\r\n *\r\n * // Create multiple aliases for same canonical\r\n * await manager.addTagAlias('py', 'python');\r\n * await manager.addTagAlias('py3', 'python', 'Python 3.x');\r\n *\r\n * // Error: duplicate alias\r\n * try {\r\n * await manager.addTagAlias('js', 'ecmascript'); // Fails - 'js' already aliased\r\n * } catch (error) {\r\n * console.error('Alias already exists');\r\n * }\r\n *\r\n * // Error: chained alias\r\n * await manager.addTagAlias('js', 'javascript');\r\n * try {\r\n * await manager.addTagAlias('javascript', 'ecmascript'); // Fails - can't alias canonical\r\n * } catch (error) {\r\n * console.error('Cannot create chained aliases');\r\n * }\r\n * ```\r\n */\r\n async addTagAlias(alias: string, canonical: string, description?: string): Promise<TagAlias> {\r\n const aliases = await this.loadTagAliases();\r\n const normalizedAlias = alias.toLowerCase();\r\n const normalizedCanonical = canonical.toLowerCase();\r\n\r\n // Check if alias already exists\r\n if (aliases.some(a => a.alias === normalizedAlias)) {\r\n throw new Error(`Tag alias \"${alias}\" already exists`);\r\n }\r\n\r\n // Prevent aliasing to another alias (aliases should point to canonical tags)\r\n if (aliases.some(a => a.canonical === normalizedAlias)) {\r\n throw new Error(\r\n `Cannot create alias to \"${alias}\" because it is a canonical tag with existing aliases`\r\n );\r\n }\r\n\r\n const newAlias: TagAlias = {\r\n alias: normalizedAlias,\r\n canonical: normalizedCanonical,\r\n description,\r\n createdAt: new Date().toISOString(),\r\n };\r\n\r\n aliases.push(newAlias);\r\n await this.saveTagAliases(aliases);\r\n\r\n return newAlias;\r\n }\r\n\r\n /**\r\n * List all tag aliases.\r\n *\r\n * @returns Array of all tag aliases\r\n */\r\n async listTagAliases(): Promise<TagAlias[]> {\r\n return await this.loadTagAliases();\r\n }\r\n\r\n /**\r\n * Remove a tag alias.\r\n *\r\n * @param alias - Alias to remove\r\n * @returns True if removed, false if not found\r\n */\r\n async removeTagAlias(alias: string): Promise<boolean> {\r\n const aliases = await this.loadTagAliases();\r\n const normalizedAlias = alias.toLowerCase();\r\n const initialLength = aliases.length;\r\n const filtered = aliases.filter(a => a.alias !== normalizedAlias);\r\n\r\n if (filtered.length === initialLength) {\r\n return false; // Alias not found\r\n }\r\n\r\n await this.saveTagAliases(filtered);\r\n return true;\r\n }\r\n\r\n /**\r\n * Get all aliases (synonyms) for a canonical tag.\r\n *\r\n * Returns all alias names that resolve to the specified canonical tag.\r\n * Useful for discovering alternative names users might use for a tag.\r\n * The canonical tag name is normalized to lowercase.\r\n *\r\n * @param canonicalTag - Canonical tag name (will be normalized to lowercase)\r\n * @returns Array of alias names (all lowercase)\r\n *\r\n * @example\r\n * ```typescript\r\n * const manager = new TagManager(tagAliasesPath);\r\n *\r\n * // Set up some aliases\r\n * await manager.addTagAlias('js', 'javascript');\r\n * await manager.addTagAlias('ecmascript', 'javascript');\r\n * await manager.addTagAlias('es6', 'javascript');\r\n *\r\n * // Get all aliases for canonical tag\r\n * const aliases = await manager.getAliasesForTag('javascript');\r\n * console.log(aliases); // ['js', 'ecmascript', 'es6']\r\n *\r\n * // Empty array if no aliases\r\n * const noAliases = await manager.getAliasesForTag('python');\r\n * console.log(noAliases); // []\r\n * ```\r\n */\r\n async getAliasesForTag(canonicalTag: string): Promise<string[]> {\r\n const aliases = await this.loadTagAliases();\r\n const normalized = canonicalTag.toLowerCase();\r\n return aliases.filter(a => a.canonical === normalized).map(a => a.alias);\r\n }\r\n}\r\n","/**\r\n * Analytics Manager\r\n *\r\n * Handles graph statistics and validation operations.\r\n * Extracted from SearchManager (Phase 4: Consolidate God Objects).\r\n *\r\n * @module features/AnalyticsManager\r\n */\r\n\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport type { GraphStats, ValidationReport, ValidationIssue, ValidationWarning } from '../types/index.js';\r\n\r\n/**\r\n * Manages analytics operations for the knowledge graph.\r\n */\r\nexport class AnalyticsManager {\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Validate the knowledge graph structure and data integrity.\r\n *\r\n * Checks for:\r\n * - Orphaned relations (pointing to non-existent entities)\r\n * - Duplicate entity names\r\n * - Invalid entity data (missing name/type, invalid observations)\r\n * - Isolated entities (no relations)\r\n * - Empty observations\r\n * - Missing metadata (createdAt, lastModified)\r\n *\r\n * @returns Validation report with errors, warnings, and summary\r\n */\r\n async validateGraph(): Promise<ValidationReport> {\r\n const graph = await this.storage.loadGraph();\r\n const issues: ValidationIssue[] = [];\r\n const warnings: ValidationWarning[] = [];\r\n\r\n // Create a set of all entity names for fast lookup\r\n const entityNames = new Set(graph.entities.map(e => e.name));\r\n\r\n // Check for orphaned relations (relations pointing to non-existent entities)\r\n for (const relation of graph.relations) {\r\n if (!entityNames.has(relation.from)) {\r\n issues.push({\r\n type: 'orphaned_relation',\r\n message: `Relation has non-existent source entity: \"${relation.from}\"`,\r\n details: { relation, missingEntity: relation.from },\r\n });\r\n }\r\n if (!entityNames.has(relation.to)) {\r\n issues.push({\r\n type: 'orphaned_relation',\r\n message: `Relation has non-existent target entity: \"${relation.to}\"`,\r\n details: { relation, missingEntity: relation.to },\r\n });\r\n }\r\n }\r\n\r\n // Check for duplicate entity names\r\n const entityNameCounts = new Map<string, number>();\r\n for (const entity of graph.entities) {\r\n const count = entityNameCounts.get(entity.name) || 0;\r\n entityNameCounts.set(entity.name, count + 1);\r\n }\r\n for (const [name, count] of entityNameCounts.entries()) {\r\n if (count > 1) {\r\n issues.push({\r\n type: 'duplicate_entity',\r\n message: `Duplicate entity name found: \"${name}\" (${count} instances)`,\r\n details: { entityName: name, count },\r\n });\r\n }\r\n }\r\n\r\n // Check for entities with invalid data\r\n for (const entity of graph.entities) {\r\n if (!entity.name || entity.name.trim() === '') {\r\n issues.push({\r\n type: 'invalid_data',\r\n message: 'Entity has empty or missing name',\r\n details: { entity },\r\n });\r\n }\r\n if (!entity.entityType || entity.entityType.trim() === '') {\r\n issues.push({\r\n type: 'invalid_data',\r\n message: `Entity \"${entity.name}\" has empty or missing entityType`,\r\n details: { entity },\r\n });\r\n }\r\n if (!Array.isArray(entity.observations)) {\r\n issues.push({\r\n type: 'invalid_data',\r\n message: `Entity \"${entity.name}\" has invalid observations (not an array)`,\r\n details: { entity },\r\n });\r\n }\r\n }\r\n\r\n // Warnings: Check for isolated entities (no relations)\r\n const entitiesInRelations = new Set<string>();\r\n for (const relation of graph.relations) {\r\n entitiesInRelations.add(relation.from);\r\n entitiesInRelations.add(relation.to);\r\n }\r\n for (const entity of graph.entities) {\r\n if (!entitiesInRelations.has(entity.name) && graph.relations.length > 0) {\r\n warnings.push({\r\n type: 'isolated_entity',\r\n message: `Entity \"${entity.name}\" has no relations to other entities`,\r\n details: { entityName: entity.name },\r\n });\r\n }\r\n }\r\n\r\n // Warnings: Check for entities with empty observations\r\n for (const entity of graph.entities) {\r\n if (entity.observations.length === 0) {\r\n warnings.push({\r\n type: 'empty_observations',\r\n message: `Entity \"${entity.name}\" has no observations`,\r\n details: { entityName: entity.name },\r\n });\r\n }\r\n }\r\n\r\n // Warnings: Check for missing metadata (createdAt, lastModified)\r\n for (const entity of graph.entities) {\r\n if (!entity.createdAt) {\r\n warnings.push({\r\n type: 'missing_metadata',\r\n message: `Entity \"${entity.name}\" is missing createdAt timestamp`,\r\n details: { entityName: entity.name, field: 'createdAt' },\r\n });\r\n }\r\n if (!entity.lastModified) {\r\n warnings.push({\r\n type: 'missing_metadata',\r\n message: `Entity \"${entity.name}\" is missing lastModified timestamp`,\r\n details: { entityName: entity.name, field: 'lastModified' },\r\n });\r\n }\r\n }\r\n\r\n // Count specific issues\r\n const orphanedRelationsCount = issues.filter(e => e.type === 'orphaned_relation').length;\r\n const entitiesWithoutRelationsCount = warnings.filter(\r\n w => w.type === 'isolated_entity'\r\n ).length;\r\n\r\n return {\r\n isValid: issues.length === 0,\r\n issues,\r\n warnings,\r\n summary: {\r\n totalErrors: issues.length,\r\n totalWarnings: warnings.length,\r\n orphanedRelationsCount,\r\n entitiesWithoutRelationsCount,\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Get comprehensive statistics about the knowledge graph.\r\n *\r\n * Provides metrics including:\r\n * - Total counts of entities and relations\r\n * - Entity and relation type distributions\r\n * - Oldest and newest entities/relations\r\n * - Date ranges for entities and relations\r\n *\r\n * @returns Graph statistics object\r\n */\r\n async getGraphStats(): Promise<GraphStats> {\r\n const graph = await this.storage.loadGraph();\r\n\r\n // Calculate entity type counts\r\n const entityTypesCounts: Record<string, number> = {};\r\n graph.entities.forEach(e => {\r\n entityTypesCounts[e.entityType] = (entityTypesCounts[e.entityType] || 0) + 1;\r\n });\r\n\r\n // Calculate relation type counts\r\n const relationTypesCounts: Record<string, number> = {};\r\n graph.relations.forEach(r => {\r\n relationTypesCounts[r.relationType] = (relationTypesCounts[r.relationType] || 0) + 1;\r\n });\r\n\r\n // Find oldest and newest entities\r\n let oldestEntity: { name: string; date: string } | undefined;\r\n let newestEntity: { name: string; date: string } | undefined;\r\n let earliestEntityDate: Date | null = null;\r\n let latestEntityDate: Date | null = null;\r\n\r\n graph.entities.forEach(e => {\r\n const date = new Date(e.createdAt || '');\r\n if (!earliestEntityDate || date < earliestEntityDate) {\r\n earliestEntityDate = date;\r\n oldestEntity = { name: e.name, date: e.createdAt || '' };\r\n }\r\n if (!latestEntityDate || date > latestEntityDate) {\r\n latestEntityDate = date;\r\n newestEntity = { name: e.name, date: e.createdAt || '' };\r\n }\r\n });\r\n\r\n // Find oldest and newest relations\r\n let oldestRelation: { from: string; to: string; relationType: string; date: string } | undefined;\r\n let newestRelation: { from: string; to: string; relationType: string; date: string } | undefined;\r\n let earliestRelationDate: Date | null = null;\r\n let latestRelationDate: Date | null = null;\r\n\r\n graph.relations.forEach(r => {\r\n const date = new Date(r.createdAt || '');\r\n if (!earliestRelationDate || date < earliestRelationDate) {\r\n earliestRelationDate = date;\r\n oldestRelation = { from: r.from, to: r.to, relationType: r.relationType, date: r.createdAt || '' };\r\n }\r\n if (!latestRelationDate || date > latestRelationDate) {\r\n latestRelationDate = date;\r\n newestRelation = { from: r.from, to: r.to, relationType: r.relationType, date: r.createdAt || '' };\r\n }\r\n });\r\n\r\n return {\r\n totalEntities: graph.entities.length,\r\n totalRelations: graph.relations.length,\r\n entityTypesCounts,\r\n relationTypesCounts,\r\n oldestEntity,\r\n newestEntity,\r\n oldestRelation,\r\n newestRelation,\r\n entityDateRange: earliestEntityDate && latestEntityDate ? {\r\n earliest: (earliestEntityDate as Date).toISOString(),\r\n latest: (latestEntityDate as Date).toISOString()\r\n } : undefined,\r\n relationDateRange: earliestRelationDate && latestRelationDate ? {\r\n earliest: (earliestRelationDate as Date).toISOString(),\r\n latest: (latestRelationDate as Date).toISOString()\r\n } : undefined,\r\n };\r\n }\r\n}\r\n","/**\r\n * Compression Manager\r\n *\r\n * Handles duplicate detection, entity merging, and graph compression.\r\n * Extracted from SearchManager (Phase 4: Consolidate God Objects).\r\n *\r\n * @module features/CompressionManager\r\n */\r\n\r\nimport type { Entity, Relation, GraphCompressionResult, KnowledgeGraph, LongRunningOperationOptions, PreparedEntity } from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport {\r\n levenshteinDistance,\r\n checkCancellation,\r\n createProgressReporter,\r\n createProgress,\r\n fnv1aHash,\r\n} from '../utils/index.js';\r\nimport { EntityNotFoundError, InsufficientEntitiesError } from '../utils/errors.js';\r\nimport { SIMILARITY_WEIGHTS, DEFAULT_DUPLICATE_THRESHOLD } from '../utils/constants.js';\r\n\r\n/**\r\n * Manages compression operations for the knowledge graph.\r\n */\r\nexport class CompressionManager {\r\n constructor(private storage: GraphStorage) {}\r\n\r\n /**\r\n * Prepare an entity for efficient similarity comparisons.\r\n * Pre-computes all normalized data to avoid repeated computation.\r\n *\r\n * Phase 12 Sprint 1: Added nameHash for fast bucketing.\r\n *\r\n * @param entity - The entity to prepare\r\n * @returns PreparedEntity with pre-computed data including hash\r\n */\r\n private prepareEntity(entity: Entity): PreparedEntity {\r\n const nameLower = entity.name.toLowerCase();\r\n return {\r\n entity,\r\n nameLower,\r\n typeLower: entity.entityType.toLowerCase(),\r\n observationSet: new Set(entity.observations.map(o => o.toLowerCase())),\r\n tagSet: new Set((entity.tags ?? []).map(t => t.toLowerCase())),\r\n nameHash: fnv1aHash(nameLower),\r\n };\r\n }\r\n\r\n /**\r\n * Prepare multiple entities for efficient similarity comparisons.\r\n * Use this before batch comparison operations.\r\n *\r\n * @param entities - Entities to prepare\r\n * @returns Map of entity name to PreparedEntity\r\n */\r\n private prepareEntities(entities: readonly Entity[]): Map<string, PreparedEntity> {\r\n const prepared = new Map<string, PreparedEntity>();\r\n for (const entity of entities) {\r\n prepared.set(entity.name, this.prepareEntity(entity));\r\n }\r\n return prepared;\r\n }\r\n\r\n /**\r\n * Calculate similarity between two entities using multiple heuristics.\r\n *\r\n * Uses configurable weights defined in SIMILARITY_WEIGHTS constant.\r\n * See SIMILARITY_WEIGHTS for the breakdown of scoring factors.\r\n *\r\n * NOTE: For batch comparisons, use prepareEntities() + calculatePreparedSimilarity() for better performance.\r\n *\r\n * @param e1 - First entity\r\n * @param e2 - Second entity\r\n * @returns Similarity score from 0 (completely different) to 1 (identical)\r\n */\r\n calculateEntitySimilarity(e1: Entity, e2: Entity): number {\r\n let score = 0;\r\n let factors = 0;\r\n\r\n // Name similarity (Levenshtein-based)\r\n const nameDistance = levenshteinDistance(e1.name.toLowerCase(), e2.name.toLowerCase());\r\n const maxNameLength = Math.max(e1.name.length, e2.name.length);\r\n const nameSimilarity = 1 - nameDistance / maxNameLength;\r\n score += nameSimilarity * SIMILARITY_WEIGHTS.NAME;\r\n factors += SIMILARITY_WEIGHTS.NAME;\r\n\r\n // Type similarity (exact match)\r\n if (e1.entityType.toLowerCase() === e2.entityType.toLowerCase()) {\r\n score += SIMILARITY_WEIGHTS.TYPE;\r\n }\r\n factors += SIMILARITY_WEIGHTS.TYPE;\r\n\r\n // Observation overlap (Jaccard similarity)\r\n const obs1Set = new Set(e1.observations.map(o => o.toLowerCase()));\r\n const obs2Set = new Set(e2.observations.map(o => o.toLowerCase()));\r\n const intersection = new Set([...obs1Set].filter(x => obs2Set.has(x)));\r\n const union = new Set([...obs1Set, ...obs2Set]);\r\n const observationSimilarity = union.size > 0 ? intersection.size / union.size : 0;\r\n score += observationSimilarity * SIMILARITY_WEIGHTS.OBSERVATIONS;\r\n factors += SIMILARITY_WEIGHTS.OBSERVATIONS;\r\n\r\n // Tag overlap (Jaccard similarity)\r\n if (e1.tags && e2.tags && (e1.tags.length > 0 || e2.tags.length > 0)) {\r\n const tags1Set = new Set(e1.tags.map(t => t.toLowerCase()));\r\n const tags2Set = new Set(e2.tags.map(t => t.toLowerCase()));\r\n const tagIntersection = new Set([...tags1Set].filter(x => tags2Set.has(x)));\r\n const tagUnion = new Set([...tags1Set, ...tags2Set]);\r\n const tagSimilarity = tagUnion.size > 0 ? tagIntersection.size / tagUnion.size : 0;\r\n score += tagSimilarity * SIMILARITY_WEIGHTS.TAGS;\r\n factors += SIMILARITY_WEIGHTS.TAGS;\r\n }\r\n\r\n return factors > 0 ? score / factors : 0;\r\n }\r\n\r\n /**\r\n * Efficiently calculate intersection size of two Sets without creating a new Set.\r\n * Iterates over the smaller set for O(min(m,n)) complexity.\r\n */\r\n private setIntersectionSize(a: Set<string>, b: Set<string>): number {\r\n // Always iterate over smaller set\r\n const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];\r\n let count = 0;\r\n for (const item of smaller) {\r\n if (larger.has(item)) count++;\r\n }\r\n return count;\r\n }\r\n\r\n /**\r\n * Calculate similarity between two prepared entities.\r\n * OPTIMIZED: Uses pre-computed Sets to avoid O(n) set creation per comparison.\r\n *\r\n * @param p1 - First prepared entity\r\n * @param p2 - Second prepared entity\r\n * @returns Similarity score from 0 (completely different) to 1 (identical)\r\n */\r\n private calculatePreparedSimilarity(p1: PreparedEntity, p2: PreparedEntity): number {\r\n let score = 0;\r\n let factors = 0;\r\n\r\n // Name similarity (Levenshtein-based) - use pre-computed lowercase\r\n const nameDistance = levenshteinDistance(p1.nameLower, p2.nameLower);\r\n const maxNameLength = Math.max(p1.nameLower.length, p2.nameLower.length);\r\n const nameSimilarity = 1 - nameDistance / maxNameLength;\r\n score += nameSimilarity * SIMILARITY_WEIGHTS.NAME;\r\n factors += SIMILARITY_WEIGHTS.NAME;\r\n\r\n // Type similarity (exact match) - use pre-computed lowercase\r\n if (p1.typeLower === p2.typeLower) {\r\n score += SIMILARITY_WEIGHTS.TYPE;\r\n }\r\n factors += SIMILARITY_WEIGHTS.TYPE;\r\n\r\n // Observation overlap (Jaccard similarity) - use pre-computed Sets\r\n const obsIntersectionSize = this.setIntersectionSize(p1.observationSet, p2.observationSet);\r\n const obsUnionSize = p1.observationSet.size + p2.observationSet.size - obsIntersectionSize;\r\n const observationSimilarity = obsUnionSize > 0 ? obsIntersectionSize / obsUnionSize : 0;\r\n score += observationSimilarity * SIMILARITY_WEIGHTS.OBSERVATIONS;\r\n factors += SIMILARITY_WEIGHTS.OBSERVATIONS;\r\n\r\n // Tag overlap (Jaccard similarity) - use pre-computed Sets\r\n if (p1.tagSet.size > 0 || p2.tagSet.size > 0) {\r\n const tagIntersectionSize = this.setIntersectionSize(p1.tagSet, p2.tagSet);\r\n const tagUnionSize = p1.tagSet.size + p2.tagSet.size - tagIntersectionSize;\r\n const tagSimilarity = tagUnionSize > 0 ? tagIntersectionSize / tagUnionSize : 0;\r\n score += tagSimilarity * SIMILARITY_WEIGHTS.TAGS;\r\n factors += SIMILARITY_WEIGHTS.TAGS;\r\n }\r\n\r\n return factors > 0 ? score / factors : 0;\r\n }\r\n\r\n /**\r\n * Find duplicate entities in the graph based on similarity threshold.\r\n *\r\n * OPTIMIZED: Uses bucketing strategies to reduce O(n²) comparisons:\r\n * 1. Buckets entities by entityType (only compare same types)\r\n * 2. Within each type, buckets by name prefix (first 2 chars normalized)\r\n * 3. Only compares entities within same or adjacent buckets\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.\r\n *\r\n * Complexity: O(n·k) where k is average bucket size (typically << n)\r\n *\r\n * @param threshold - Similarity threshold (0.0 to 1.0), default DEFAULT_DUPLICATE_THRESHOLD\r\n * @param options - Optional progress/cancellation options (Phase 9B)\r\n * @returns Array of duplicate groups (each group has similar entities)\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n */\r\n async findDuplicates(\r\n threshold: number = DEFAULT_DUPLICATE_THRESHOLD,\r\n options?: LongRunningOperationOptions\r\n ): Promise<string[][]> {\r\n // Check for early cancellation\r\n checkCancellation(options?.signal, 'findDuplicates');\r\n\r\n const graph = await this.storage.loadGraph();\r\n const duplicateGroups: string[][] = [];\r\n const processed = new Set<string>();\r\n\r\n // Setup progress reporter\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n const totalEntities = graph.entities.length;\r\n let processedCount = 0;\r\n reportProgress?.(createProgress(0, totalEntities, 'findDuplicates'));\r\n\r\n // OPTIMIZATION: Pre-prepare all entities once before comparisons\r\n const preparedEntities = this.prepareEntities(graph.entities);\r\n\r\n // Step 1: Bucket entities by type (reduces comparisons drastically)\r\n const typeMap = new Map<string, Entity[]>();\r\n for (const entity of graph.entities) {\r\n const normalizedType = entity.entityType.toLowerCase();\r\n if (!typeMap.has(normalizedType)) {\r\n typeMap.set(normalizedType, []);\r\n }\r\n typeMap.get(normalizedType)!.push(entity);\r\n }\r\n\r\n // Step 2: For each type bucket, sub-bucket by name prefix\r\n for (const entities of typeMap.values()) {\r\n // Check for cancellation between type buckets\r\n checkCancellation(options?.signal, 'findDuplicates');\r\n\r\n // Skip single-entity types (no duplicates possible)\r\n if (entities.length < 2) {\r\n processedCount += entities.length;\r\n reportProgress?.(createProgress(processedCount, totalEntities, 'findDuplicates'));\r\n continue;\r\n }\r\n\r\n // Create name prefix buckets (first 2 chars, normalized)\r\n const prefixMap = new Map<string, Entity[]>();\r\n for (const entity of entities) {\r\n const prefix = entity.name.toLowerCase().slice(0, 2);\r\n if (!prefixMap.has(prefix)) {\r\n prefixMap.set(prefix, []);\r\n }\r\n prefixMap.get(prefix)!.push(entity);\r\n }\r\n\r\n // Step 3: Compare only within buckets (or adjacent buckets for fuzzy matching)\r\n const prefixKeys = Array.from(prefixMap.keys()).sort();\r\n\r\n for (let bucketIdx = 0; bucketIdx < prefixKeys.length; bucketIdx++) {\r\n // Check for cancellation between prefix buckets\r\n checkCancellation(options?.signal, 'findDuplicates');\r\n\r\n const currentPrefix = prefixKeys[bucketIdx];\r\n const currentBucket = prefixMap.get(currentPrefix)!;\r\n\r\n // Collect entities to compare: current bucket + adjacent buckets\r\n const candidateEntities: Entity[] = [...currentBucket];\r\n\r\n // Add next bucket if exists (handles fuzzy prefix matching)\r\n if (bucketIdx + 1 < prefixKeys.length) {\r\n candidateEntities.push(...prefixMap.get(prefixKeys[bucketIdx + 1])!);\r\n }\r\n\r\n // Compare entities within candidate pool\r\n for (let i = 0; i < currentBucket.length; i++) {\r\n const entity1 = currentBucket[i];\r\n if (processed.has(entity1.name)) continue;\r\n\r\n // OPTIMIZATION: Use prepared entity for comparison\r\n const prepared1 = preparedEntities.get(entity1.name)!;\r\n const group: string[] = [entity1.name];\r\n\r\n for (let j = 0; j < candidateEntities.length; j++) {\r\n const entity2 = candidateEntities[j];\r\n if (entity1.name === entity2.name || processed.has(entity2.name)) continue;\r\n\r\n // OPTIMIZATION: Use prepared entity and optimized similarity\r\n const prepared2 = preparedEntities.get(entity2.name)!;\r\n const similarity = this.calculatePreparedSimilarity(prepared1, prepared2);\r\n if (similarity >= threshold) {\r\n group.push(entity2.name);\r\n processed.add(entity2.name);\r\n }\r\n }\r\n\r\n if (group.length > 1) {\r\n duplicateGroups.push(group);\r\n processed.add(entity1.name);\r\n }\r\n\r\n processedCount++;\r\n reportProgress?.(createProgress(processedCount, totalEntities, 'findDuplicates'));\r\n }\r\n }\r\n }\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(totalEntities, totalEntities, 'findDuplicates'));\r\n\r\n return duplicateGroups;\r\n }\r\n\r\n /**\r\n * Merge a group of entities into a single entity.\r\n *\r\n * Merging strategy:\r\n * - First entity is kept (or renamed to targetName)\r\n * - Observations: Union of all observations\r\n * - Tags: Union of all tags\r\n * - Importance: Maximum importance value\r\n * - createdAt: Earliest date\r\n * - lastModified: Current timestamp\r\n * - Relations: Redirected to kept entity, duplicates removed\r\n *\r\n * @param entityNames - Names of entities to merge (first one is kept)\r\n * @param targetName - Optional new name for merged entity (default: first entity name)\r\n * @param options - Optional configuration\r\n * @param options.graph - Pre-loaded graph to use (avoids reload)\r\n * @param options.skipSave - If true, don't save (caller will save)\r\n * @returns The merged entity\r\n * @throws {InsufficientEntitiesError} If less than 2 entities provided\r\n * @throws {EntityNotFoundError} If any entity not found\r\n */\r\n async mergeEntities(\r\n entityNames: string[],\r\n targetName?: string,\r\n options: {\r\n graph?: KnowledgeGraph;\r\n skipSave?: boolean;\r\n } = {}\r\n ): Promise<Entity> {\r\n if (entityNames.length < 2) {\r\n throw new InsufficientEntitiesError('merging', 2, entityNames.length);\r\n }\r\n\r\n // Use provided graph or load fresh\r\n const graph = options.graph ?? await this.storage.getGraphForMutation();\r\n const entitiesToMerge = entityNames.map(name => {\r\n const entity = graph.entities.find(e => e.name === name);\r\n if (!entity) {\r\n throw new EntityNotFoundError(name);\r\n }\r\n return entity;\r\n });\r\n\r\n const keepEntity = entitiesToMerge[0];\r\n const mergeEntities = entitiesToMerge.slice(1);\r\n\r\n // Merge observations (unique)\r\n const allObservations = new Set<string>();\r\n for (const entity of entitiesToMerge) {\r\n entity.observations.forEach(obs => allObservations.add(obs));\r\n }\r\n keepEntity.observations = Array.from(allObservations);\r\n\r\n // Merge tags (unique)\r\n const allTags = new Set<string>();\r\n for (const entity of entitiesToMerge) {\r\n if (entity.tags) {\r\n entity.tags.forEach(tag => allTags.add(tag));\r\n }\r\n }\r\n if (allTags.size > 0) {\r\n keepEntity.tags = Array.from(allTags);\r\n }\r\n\r\n // Use highest importance\r\n const importances = entitiesToMerge\r\n .map(e => e.importance)\r\n .filter(imp => imp !== undefined) as number[];\r\n if (importances.length > 0) {\r\n keepEntity.importance = Math.max(...importances);\r\n }\r\n\r\n // Use earliest createdAt\r\n const createdDates = entitiesToMerge\r\n .map(e => e.createdAt)\r\n .filter(date => date !== undefined) as string[];\r\n if (createdDates.length > 0) {\r\n keepEntity.createdAt = createdDates.sort()[0];\r\n }\r\n\r\n // Update lastModified\r\n keepEntity.lastModified = new Date().toISOString();\r\n\r\n // Rename if requested\r\n if (targetName && targetName !== keepEntity.name) {\r\n // Update all relations pointing to old name\r\n graph.relations.forEach(rel => {\r\n if (rel.from === keepEntity.name) rel.from = targetName;\r\n if (rel.to === keepEntity.name) rel.to = targetName;\r\n });\r\n keepEntity.name = targetName;\r\n }\r\n\r\n // Update relations from merged entities to point to kept entity\r\n for (const mergeEntity of mergeEntities) {\r\n graph.relations.forEach(rel => {\r\n if (rel.from === mergeEntity.name) rel.from = keepEntity.name;\r\n if (rel.to === mergeEntity.name) rel.to = keepEntity.name;\r\n });\r\n }\r\n\r\n // Remove duplicate relations\r\n const uniqueRelations = new Map<string, Relation>();\r\n for (const relation of graph.relations) {\r\n const key = `${relation.from}|${relation.to}|${relation.relationType}`;\r\n if (!uniqueRelations.has(key)) {\r\n uniqueRelations.set(key, relation);\r\n }\r\n }\r\n graph.relations = Array.from(uniqueRelations.values());\r\n\r\n // Remove merged entities\r\n const mergeNames = new Set(mergeEntities.map(e => e.name));\r\n graph.entities = graph.entities.filter(e => !mergeNames.has(e.name));\r\n\r\n // Save unless caller said to skip\r\n if (!options.skipSave) {\r\n await this.storage.saveGraph(graph);\r\n }\r\n return keepEntity;\r\n }\r\n\r\n /**\r\n * Compress the knowledge graph by finding and merging duplicates.\r\n * OPTIMIZED: Loads graph once, performs all merges, saves once.\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via LongRunningOperationOptions.\r\n *\r\n * @param threshold - Similarity threshold for duplicate detection (0.0 to 1.0), default DEFAULT_DUPLICATE_THRESHOLD\r\n * @param dryRun - If true, only report what would be compressed without applying changes\r\n * @param options - Optional progress/cancellation options (Phase 9B)\r\n * @returns Compression result with statistics\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n */\r\n async compressGraph(\r\n threshold: number = DEFAULT_DUPLICATE_THRESHOLD,\r\n dryRun: boolean = false,\r\n options?: LongRunningOperationOptions\r\n ): Promise<GraphCompressionResult> {\r\n // Check for early cancellation\r\n checkCancellation(options?.signal, 'compressGraph');\r\n\r\n // Setup progress reporter (we'll use phases: 50% finding duplicates, 50% merging)\r\n const reportProgress = createProgressReporter(options?.onProgress);\r\n reportProgress?.(createProgress(0, 100, 'compressGraph'));\r\n\r\n // Phase 1: Find duplicates (0-50% progress)\r\n const duplicateGroups = await this.findDuplicates(threshold, {\r\n signal: options?.signal,\r\n onProgress: (p) => {\r\n // Map findDuplicates progress (0-100%) to compressGraph progress (0-50%)\r\n const compressProgress = Math.round(p.percentage * 0.5);\r\n reportProgress?.(createProgress(compressProgress, 100, 'finding duplicates'));\r\n },\r\n });\r\n\r\n // Check for cancellation after finding duplicates\r\n checkCancellation(options?.signal, 'compressGraph');\r\n reportProgress?.(createProgress(50, 100, 'compressGraph'));\r\n\r\n // OPTIMIZATION: Load graph once for all operations\r\n const graph = await this.storage.getGraphForMutation();\r\n const initialSize = JSON.stringify(graph).length;\r\n const result: GraphCompressionResult = {\r\n duplicatesFound: duplicateGroups.reduce((sum, group) => sum + group.length, 0),\r\n entitiesMerged: 0,\r\n observationsCompressed: 0,\r\n relationsConsolidated: 0,\r\n spaceFreed: 0,\r\n mergedEntities: [],\r\n };\r\n\r\n if (dryRun) {\r\n // Just report what would happen\r\n for (const group of duplicateGroups) {\r\n result.mergedEntities.push({\r\n kept: group[0],\r\n merged: group.slice(1),\r\n });\r\n result.entitiesMerged += group.length - 1;\r\n }\r\n reportProgress?.(createProgress(100, 100, 'compressGraph'));\r\n return result;\r\n }\r\n\r\n // Phase 2: Merge duplicates (50-100% progress)\r\n const totalGroups = duplicateGroups.length;\r\n let mergedGroups = 0;\r\n\r\n // OPTIMIZATION: Build entity lookup map for O(1) access during merges\r\n const entityMap = new Map<string, Entity>();\r\n for (const entity of graph.entities) {\r\n entityMap.set(entity.name, entity);\r\n }\r\n\r\n // Merge all duplicates using the same graph instance\r\n for (const group of duplicateGroups) {\r\n // Check for cancellation between merges\r\n checkCancellation(options?.signal, 'compressGraph');\r\n\r\n try {\r\n // Count observations before merge using O(1) lookup\r\n let totalObservationsBefore = 0;\r\n for (const name of group) {\r\n const entity = entityMap.get(name);\r\n if (entity) {\r\n totalObservationsBefore += entity.observations.length;\r\n }\r\n }\r\n\r\n // OPTIMIZATION: Pass graph and skip individual saves\r\n const mergedEntity = await this.mergeEntities(group, undefined, {\r\n graph,\r\n skipSave: true,\r\n });\r\n\r\n const observationsAfter = mergedEntity.observations.length;\r\n result.observationsCompressed += totalObservationsBefore - observationsAfter;\r\n\r\n result.mergedEntities.push({\r\n kept: group[0],\r\n merged: group.slice(1),\r\n });\r\n result.entitiesMerged += group.length - 1;\r\n } catch (error) {\r\n // Skip groups that fail to merge\r\n console.error(`Failed to merge group ${group}:`, error);\r\n }\r\n\r\n mergedGroups++;\r\n // Map merge progress (0-100%) to compressGraph progress (50-100%)\r\n const mergeProgress = totalGroups > 0 ? Math.round(50 + (mergedGroups / totalGroups) * 50) : 100;\r\n reportProgress?.(createProgress(mergeProgress, 100, 'merging entities'));\r\n }\r\n\r\n // Check for cancellation before final save\r\n checkCancellation(options?.signal, 'compressGraph');\r\n\r\n // OPTIMIZATION: Save once after all merges complete\r\n await this.storage.saveGraph(graph);\r\n\r\n const finalSize = JSON.stringify(graph).length;\r\n result.spaceFreed = initialSize - finalSize;\r\n result.relationsConsolidated = result.entitiesMerged;\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(100, 100, 'compressGraph'));\r\n\r\n return result;\r\n }\r\n}\r\n","/**\r\n * Archive Manager\r\n *\r\n * Handles archiving (removal) of entities based on criteria.\r\n * Archives are stored as compressed files for space-efficient long-term storage.\r\n * Extracted from EntityManager (Phase 4: Consolidate God Objects).\r\n * Enhanced with brotli compression in Phase 3 Sprint 5.\r\n *\r\n * @module features/ArchiveManager\r\n */\r\n\r\nimport { promises as fs } from 'fs';\r\nimport { dirname, join } from 'path';\r\nimport type { Entity, LongRunningOperationOptions } from '../types/index.js';\r\nimport type { GraphStorage } from '../core/GraphStorage.js';\r\nimport {\r\n compress,\r\n COMPRESSION_CONFIG,\r\n checkCancellation,\r\n createProgressReporter,\r\n createProgress,\r\n} from '../utils/index.js';\r\n\r\n/**\r\n * Criteria for archiving entities.\r\n */\r\nexport interface ArchiveCriteria {\r\n /** Entities older than this date (ISO 8601) */\r\n olderThan?: string;\r\n /** Entities with importance less than this value */\r\n importanceLessThan?: number;\r\n /** Entities with any of these tags */\r\n tags?: string[];\r\n}\r\n\r\n/**\r\n * Options for archive operations.\r\n * Phase 9B: Extended with LongRunningOperationOptions.\r\n */\r\nexport interface ArchiveOptions extends LongRunningOperationOptions {\r\n /** Dry run mode - preview without making changes */\r\n dryRun?: boolean;\r\n /** Whether to save archived entities to a compressed file (default: true) */\r\n saveToFile?: boolean;\r\n}\r\n\r\n/**\r\n * Result of archive operation.\r\n * Extends ArchiveResultExtended with compression statistics.\r\n */\r\nexport interface ArchiveResult {\r\n /** Number of entities archived */\r\n archived: number;\r\n /** Names of archived entities */\r\n entityNames: string[];\r\n /** Path to the archive file (if created) */\r\n archivePath?: string;\r\n /** Original size of archive data in bytes */\r\n originalSize?: number;\r\n /** Compressed size in bytes */\r\n compressedSize?: number;\r\n /** Compression ratio (compressedSize / originalSize). Lower is better. */\r\n compressionRatio?: number;\r\n}\r\n\r\n/**\r\n * Manages archive operations for the knowledge graph.\r\n *\r\n * Archives are stored as brotli-compressed files in the `.archives` directory.\r\n * Maximum compression quality is used for optimal long-term storage.\r\n */\r\nexport class ArchiveManager {\r\n private readonly archiveDir: string;\r\n\r\n constructor(private storage: GraphStorage) {\r\n const filePath = this.storage.getFilePath();\r\n const dir = dirname(filePath);\r\n this.archiveDir = join(dir, '.archives');\r\n }\r\n\r\n /**\r\n * Archive old or low-importance entities.\r\n *\r\n * Entities matching ANY of the criteria are archived:\r\n * - lastModified older than olderThan date\r\n * - importance less than importanceLessThan\r\n * - has at least one tag from tags array\r\n *\r\n * By default, archived entities are saved to a compressed file before\r\n * being removed from the active graph. Use `saveToFile: false` to\r\n * skip creating the archive file.\r\n *\r\n * Phase 9B: Supports progress tracking and cancellation via options.\r\n *\r\n * @param criteria - Archiving criteria\r\n * @param options - Archive options (dryRun, saveToFile, onProgress, signal)\r\n * @returns Archive result with count, entity names, and compression stats\r\n * @throws {OperationCancelledError} If operation is cancelled via signal (Phase 9B)\r\n *\r\n * @example\r\n * ```typescript\r\n * // Archive old entities with compression\r\n * const result = await manager.archiveEntities({\r\n * olderThan: '2023-01-01T00:00:00Z',\r\n * importanceLessThan: 3\r\n * });\r\n * console.log(`Archived ${result.archived} entities`);\r\n * console.log(`Compressed from ${result.originalSize} to ${result.compressedSize} bytes`);\r\n *\r\n * // Preview without making changes\r\n * const preview = await manager.archiveEntities(criteria, { dryRun: true });\r\n *\r\n * // With progress tracking and cancellation (Phase 9B)\r\n * const controller = new AbortController();\r\n * const result = await manager.archiveEntities(criteria, {\r\n * signal: controller.signal,\r\n * onProgress: (p) => console.log(`${p.percentage}% complete`),\r\n * });\r\n * ```\r\n */\r\n async archiveEntities(\r\n criteria: ArchiveCriteria,\r\n options: ArchiveOptions | boolean = {}\r\n ): Promise<ArchiveResult> {\r\n // Handle legacy boolean argument (backward compatibility)\r\n const opts: ArchiveOptions = typeof options === 'boolean'\r\n ? { dryRun: options, saveToFile: true }\r\n : { saveToFile: true, ...options };\r\n\r\n // Check for early cancellation\r\n checkCancellation(opts.signal, 'archiveEntities');\r\n\r\n // Setup progress reporter\r\n const reportProgress = createProgressReporter(opts.onProgress);\r\n reportProgress?.(createProgress(0, 100, 'archiveEntities'));\r\n\r\n // Use read-only graph for analysis\r\n const readGraph = await this.storage.loadGraph();\r\n const toArchive: Entity[] = [];\r\n const totalEntities = readGraph.entities.length;\r\n let processedEntities = 0;\r\n\r\n // Phase 1: Identify entities to archive (0-40% progress)\r\n reportProgress?.(createProgress(5, 100, 'analyzing entities'));\r\n\r\n for (const entity of readGraph.entities) {\r\n // Check for cancellation periodically\r\n checkCancellation(opts.signal, 'archiveEntities');\r\n\r\n let shouldArchive = false;\r\n\r\n // Check age criteria\r\n if (criteria.olderThan && entity.lastModified) {\r\n const entityDate = new Date(entity.lastModified);\r\n const cutoffDate = new Date(criteria.olderThan);\r\n if (entityDate < cutoffDate) {\r\n shouldArchive = true;\r\n }\r\n }\r\n\r\n // Check importance criteria\r\n if (criteria.importanceLessThan !== undefined) {\r\n if (entity.importance === undefined || entity.importance < criteria.importanceLessThan) {\r\n shouldArchive = true;\r\n }\r\n }\r\n\r\n // Check tag criteria (must have at least one matching tag)\r\n if (criteria.tags && criteria.tags.length > 0) {\r\n const normalizedCriteriaTags = criteria.tags.map(t => t.toLowerCase());\r\n const entityTags = (entity.tags || []).map(t => t.toLowerCase());\r\n const hasMatchingTag = normalizedCriteriaTags.some(tag => entityTags.includes(tag));\r\n if (hasMatchingTag) {\r\n shouldArchive = true;\r\n }\r\n }\r\n\r\n if (shouldArchive) {\r\n toArchive.push(entity);\r\n }\r\n\r\n processedEntities++;\r\n // Map analysis progress (0-100%) to overall progress (0-40%)\r\n const analysisProgress = totalEntities > 0 ? Math.round((processedEntities / totalEntities) * 40) : 40;\r\n reportProgress?.(createProgress(analysisProgress, 100, 'analyzing entities'));\r\n }\r\n\r\n reportProgress?.(createProgress(40, 100, 'analysis complete'));\r\n\r\n // Dry run - return preview without changes\r\n if (opts.dryRun) {\r\n reportProgress?.(createProgress(100, 100, 'archiveEntities'));\r\n return {\r\n archived: toArchive.length,\r\n entityNames: toArchive.map(e => e.name),\r\n };\r\n }\r\n\r\n // No entities to archive\r\n if (toArchive.length === 0) {\r\n reportProgress?.(createProgress(100, 100, 'archiveEntities'));\r\n return {\r\n archived: 0,\r\n entityNames: [],\r\n };\r\n }\r\n\r\n // Check for cancellation before archiving\r\n checkCancellation(opts.signal, 'archiveEntities');\r\n\r\n // Phase 2: Save to compressed archive file (40-80% progress)\r\n let archivePath: string | undefined;\r\n let originalSize: number | undefined;\r\n let compressedSize: number | undefined;\r\n let compressionRatio: number | undefined;\r\n\r\n if (opts.saveToFile) {\r\n reportProgress?.(createProgress(50, 100, 'compressing archive'));\r\n const archiveResult = await this.saveToArchive(toArchive);\r\n archivePath = archiveResult.archivePath;\r\n originalSize = archiveResult.originalSize;\r\n compressedSize = archiveResult.compressedSize;\r\n compressionRatio = archiveResult.compressionRatio;\r\n reportProgress?.(createProgress(80, 100, 'archive saved'));\r\n } else {\r\n reportProgress?.(createProgress(80, 100, 'skipped archive file'));\r\n }\r\n\r\n // Check for cancellation before graph modification\r\n checkCancellation(opts.signal, 'archiveEntities');\r\n\r\n // Phase 3: Remove from main graph (80-100% progress)\r\n reportProgress?.(createProgress(85, 100, 'updating graph'));\r\n\r\n // Get mutable copy for write operation\r\n const graph = await this.storage.getGraphForMutation();\r\n\r\n // Remove archived entities from main graph\r\n const archiveNames = new Set(toArchive.map(e => e.name));\r\n graph.entities = graph.entities.filter(e => !archiveNames.has(e.name));\r\n graph.relations = graph.relations.filter(\r\n r => !archiveNames.has(r.from) && !archiveNames.has(r.to)\r\n );\r\n await this.storage.saveGraph(graph);\r\n\r\n // Report completion\r\n reportProgress?.(createProgress(100, 100, 'archiveEntities'));\r\n\r\n return {\r\n archived: toArchive.length,\r\n entityNames: toArchive.map(e => e.name),\r\n archivePath,\r\n originalSize,\r\n compressedSize,\r\n compressionRatio,\r\n };\r\n }\r\n\r\n /**\r\n * Save entities to a compressed archive file.\r\n *\r\n * Creates a brotli-compressed file in the `.archives` directory\r\n * with maximum compression quality for space efficiency.\r\n *\r\n * @param entities - Entities to archive\r\n * @returns Archive file path and compression statistics\r\n */\r\n private async saveToArchive(entities: Entity[]): Promise<{\r\n archivePath: string;\r\n originalSize: number;\r\n compressedSize: number;\r\n compressionRatio: number;\r\n }> {\r\n // Ensure archive directory exists\r\n await fs.mkdir(this.archiveDir, { recursive: true });\r\n\r\n // Generate timestamp-based filename\r\n const timestamp = new Date().toISOString()\r\n .replace(/:/g, '-')\r\n .replace(/\\./g, '-')\r\n .replace('T', '_')\r\n .replace('Z', '');\r\n const archivePath = join(this.archiveDir, `archive_${timestamp}.jsonl.br`);\r\n\r\n // Serialize entities to JSONL format\r\n const content = entities.map(e => JSON.stringify(e)).join('\\n');\r\n\r\n // Compress with maximum quality for archives\r\n const compressionResult = await compress(content, {\r\n quality: COMPRESSION_CONFIG.BROTLI_QUALITY_ARCHIVE,\r\n mode: 'text',\r\n });\r\n\r\n // Write compressed archive\r\n await fs.writeFile(archivePath, compressionResult.compressed);\r\n\r\n // Write metadata file\r\n const metadataPath = `${archivePath}.meta.json`;\r\n const metadata = {\r\n timestamp: new Date().toISOString(),\r\n entityCount: entities.length,\r\n entityNames: entities.map(e => e.name),\r\n compressed: true,\r\n compressionFormat: 'brotli',\r\n originalSize: compressionResult.originalSize,\r\n compressedSize: compressionResult.compressedSize,\r\n compressionRatio: compressionResult.ratio,\r\n };\r\n await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));\r\n\r\n return {\r\n archivePath,\r\n originalSize: compressionResult.originalSize,\r\n compressedSize: compressionResult.compressedSize,\r\n compressionRatio: compressionResult.ratio,\r\n };\r\n }\r\n\r\n /**\r\n * List all available archives.\r\n *\r\n * @returns Array of archive information with compression details\r\n */\r\n async listArchives(): Promise<Array<{\r\n fileName: string;\r\n filePath: string;\r\n timestamp: string;\r\n entityCount: number;\r\n compressed: boolean;\r\n originalSize?: number;\r\n compressedSize?: number;\r\n compressionRatio?: number;\r\n }>> {\r\n try {\r\n try {\r\n await fs.access(this.archiveDir);\r\n } catch {\r\n return [];\r\n }\r\n\r\n const files = await fs.readdir(this.archiveDir);\r\n const archiveFiles = files.filter(f =>\r\n f.startsWith('archive_') &&\r\n (f.endsWith('.jsonl') || f.endsWith('.jsonl.br')) &&\r\n !f.endsWith('.meta.json')\r\n );\r\n\r\n const archives: Array<{\r\n fileName: string;\r\n filePath: string;\r\n timestamp: string;\r\n entityCount: number;\r\n compressed: boolean;\r\n originalSize?: number;\r\n compressedSize?: number;\r\n compressionRatio?: number;\r\n }> = [];\r\n\r\n for (const fileName of archiveFiles) {\r\n const filePath = join(this.archiveDir, fileName);\r\n const metadataPath = `${filePath}.meta.json`;\r\n\r\n try {\r\n const metadataContent = await fs.readFile(metadataPath, 'utf-8');\r\n const metadata = JSON.parse(metadataContent);\r\n\r\n archives.push({\r\n fileName,\r\n filePath,\r\n timestamp: metadata.timestamp,\r\n entityCount: metadata.entityCount,\r\n compressed: metadata.compressed ?? fileName.endsWith('.br'),\r\n originalSize: metadata.originalSize,\r\n compressedSize: metadata.compressedSize,\r\n compressionRatio: metadata.compressionRatio,\r\n });\r\n } catch {\r\n // Skip archives without valid metadata\r\n continue;\r\n }\r\n }\r\n\r\n // Sort by timestamp (newest first)\r\n archives.sort((a, b) =>\r\n new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()\r\n );\r\n\r\n return archives;\r\n } catch {\r\n return [];\r\n }\r\n }\r\n\r\n /**\r\n * Get the path to the archives directory.\r\n */\r\n getArchiveDir(): string {\r\n return this.archiveDir;\r\n }\r\n}\r\n","/**\n * Access Tracker\n *\n * Tracks memory access patterns to inform decay calculations\n * and retrieval ranking. Records access frequency, recency,\n * and context for each memory entity.\n *\n * @module agent/AccessTracker\n */\n\nimport type { IGraphStorage } from '../types/types.js';\nimport type { AgentEntity, AccessContext, AccessPattern } from '../types/agent-memory.js';\n\n// Re-export AccessContext for convenience\nexport type { AccessContext } from '../types/agent-memory.js';\n\n// ==================== Interfaces ====================\n\n/**\n * Internal record of access history for an entity.\n */\ninterface AccessRecord {\n /** Entity name */\n entityName: string;\n /** Total access count */\n totalAccesses: number;\n /** ISO 8601 timestamp of last access */\n lastAccessedAt: string;\n /** Recent access timestamps (circular buffer) */\n recentAccesses: string[];\n /** Access counts per session */\n accessesBySession: Record<string, number>;\n}\n\n/**\n * Statistics about an entity's access patterns.\n */\nexport interface AccessStats {\n /** Total number of accesses */\n totalAccesses: number;\n /** ISO 8601 timestamp of last access */\n lastAccessedAt: string;\n /** Classified access pattern */\n accessPattern: AccessPattern;\n /** Average interval between accesses in milliseconds */\n averageAccessInterval: number;\n /** Access counts by session */\n accessesBySession: Record<string, number>;\n}\n\n/**\n * Configuration options for AccessTracker.\n */\nexport interface AccessTrackerConfig {\n /** Maximum recent accesses to track per entity (default: 100) */\n historyBufferSize?: number;\n /** Half-life in hours for recency scoring (default: 24) */\n recencyHalfLifeHours?: number;\n /** Threshold for 'frequent' pattern (accesses per day, default: 10) */\n frequentThreshold?: number;\n /** Threshold for 'occasional' pattern (accesses per day, default: 1) */\n occasionalThreshold?: number;\n}\n\n// ==================== AccessTracker Class ====================\n\n/**\n * Tracks memory access patterns for decay and retrieval ranking.\n *\n * The AccessTracker records every memory access and provides:\n * - Access statistics (frequency, recency, patterns)\n * - Recency scoring using exponential decay\n * - Retrieval of frequently/recently accessed entities\n *\n * @example\n * ```typescript\n * const tracker = new AccessTracker(storage);\n * await tracker.recordAccess('entity_name', { sessionId: 'session_1' });\n * const stats = await tracker.getAccessStats('entity_name');\n * console.log(stats.accessPattern); // 'frequent', 'occasional', or 'rare'\n * ```\n */\nexport class AccessTracker {\n private readonly storage: IGraphStorage;\n private readonly config: Required<AccessTrackerConfig>;\n private readonly accessRecords: Map<string, AccessRecord>;\n private dirty: boolean;\n\n constructor(storage: IGraphStorage, config: AccessTrackerConfig = {}) {\n this.storage = storage;\n this.config = {\n historyBufferSize: config.historyBufferSize ?? 100,\n recencyHalfLifeHours: config.recencyHalfLifeHours ?? 24,\n frequentThreshold: config.frequentThreshold ?? 10,\n occasionalThreshold: config.occasionalThreshold ?? 1,\n };\n this.accessRecords = new Map();\n this.dirty = false;\n }\n\n // ==================== Public Methods ====================\n\n /**\n * Record an access to an entity.\n *\n * Updates access counts, timestamps, and maintains the history buffer.\n * Optionally updates the entity's access fields in storage.\n *\n * @param entityName - Name of the accessed entity\n * @param context - Optional context about the access\n */\n async recordAccess(entityName: string, context?: AccessContext): Promise<void> {\n const now = new Date().toISOString();\n\n // Get or create access record\n let record = this.accessRecords.get(entityName);\n if (!record) {\n record = {\n entityName,\n totalAccesses: 0,\n lastAccessedAt: now,\n recentAccesses: [],\n accessesBySession: {},\n };\n this.accessRecords.set(entityName, record);\n }\n\n // Update access counts\n record.totalAccesses++;\n record.lastAccessedAt = now;\n\n // Add to recent accesses (circular buffer)\n record.recentAccesses.push(now);\n if (record.recentAccesses.length > this.config.historyBufferSize) {\n record.recentAccesses.shift();\n }\n\n // Track session-specific access\n if (context?.sessionId) {\n record.accessesBySession[context.sessionId] =\n (record.accessesBySession[context.sessionId] ?? 0) + 1;\n }\n\n // Mark as dirty for batch persistence\n this.dirty = true;\n\n // Update entity in storage if it exists\n await this.updateEntityAccessFields(entityName, record);\n }\n\n /**\n * Get access statistics for an entity.\n *\n * @param entityName - Entity to get stats for\n * @returns Access statistics including pattern classification\n */\n async getAccessStats(entityName: string): Promise<AccessStats> {\n const record = this.accessRecords.get(entityName);\n\n if (!record) {\n // Return default stats for untracked entity\n return {\n totalAccesses: 0,\n lastAccessedAt: '',\n accessPattern: 'rare',\n averageAccessInterval: Infinity,\n accessesBySession: {},\n };\n }\n\n return {\n totalAccesses: record.totalAccesses,\n lastAccessedAt: record.lastAccessedAt,\n accessPattern: this.classifyAccessPattern(record),\n averageAccessInterval: this.calculateAverageInterval(record),\n accessesBySession: { ...record.accessesBySession },\n };\n }\n\n /**\n * Calculate recency score based on time since last access.\n *\n * Uses exponential decay: e^(-ln(2) * hours_since_access / half_life)\n * - Score of 1.0 for just-accessed memories\n * - Score of 0.5 after one half-life\n * - Score approaches 0 for very old accesses\n *\n * @param entityName - Entity to calculate score for\n * @param halfLifeHours - Optional override for half-life (default: config value)\n * @returns Recency score between 0.0 and 1.0\n */\n calculateRecencyScore(entityName: string, halfLifeHours?: number): number {\n const record = this.accessRecords.get(entityName);\n\n if (!record || !record.lastAccessedAt) {\n return 0; // No access history = minimum recency\n }\n\n const halfLife = halfLifeHours ?? this.config.recencyHalfLifeHours;\n const lastAccess = new Date(record.lastAccessedAt).getTime();\n const now = Date.now();\n const hoursSinceAccess = (now - lastAccess) / (1000 * 60 * 60);\n\n // Exponential decay formula\n const decayConstant = Math.LN2 / halfLife;\n const score = Math.exp(-decayConstant * hoursSinceAccess);\n\n return Math.max(0, Math.min(1, score)); // Clamp to [0, 1]\n }\n\n /**\n * Calculate recency score for a given timestamp.\n * Static utility for use without AccessTracker instance.\n *\n * @param lastAccessedAt - ISO 8601 timestamp of last access\n * @param halfLifeHours - Half-life for decay calculation\n * @returns Recency score between 0.0 and 1.0\n */\n static calculateRecencyScoreFromTimestamp(\n lastAccessedAt: string,\n halfLifeHours: number = 24\n ): number {\n if (!lastAccessedAt) return 0;\n\n const lastAccess = new Date(lastAccessedAt).getTime();\n const now = Date.now();\n const hoursSinceAccess = (now - lastAccess) / (1000 * 60 * 60);\n\n const decayConstant = Math.LN2 / halfLifeHours;\n const score = Math.exp(-decayConstant * hoursSinceAccess);\n\n return Math.max(0, Math.min(1, score));\n }\n\n /**\n * Get most frequently accessed entities.\n *\n * @param limit - Maximum number of entities to return\n * @param timeWindowHours - Optional time window for frequency calculation\n * @returns Array of entities sorted by access frequency (descending)\n */\n async getFrequentlyAccessed(\n limit: number,\n timeWindowHours?: number\n ): Promise<AgentEntity[]> {\n const now = Date.now();\n const windowStart = timeWindowHours\n ? now - timeWindowHours * 60 * 60 * 1000\n : 0;\n\n // Calculate frequency scores\n const scored: Array<{ name: string; frequency: number }> = [];\n\n for (const [name, record] of this.accessRecords) {\n let frequency: number;\n\n if (timeWindowHours) {\n // Count accesses within time window\n frequency = record.recentAccesses.filter(\n (ts) => new Date(ts).getTime() >= windowStart\n ).length;\n } else {\n frequency = record.totalAccesses;\n }\n\n if (frequency > 0) {\n scored.push({ name, frequency });\n }\n }\n\n // Sort by frequency (descending) and take top N\n scored.sort((a, b) => b.frequency - a.frequency);\n const topNames = scored.slice(0, limit).map((s) => s.name);\n\n // Fetch entities from storage\n const entities: AgentEntity[] = [];\n for (const name of topNames) {\n const entity = this.storage.getEntityByName(name);\n if (entity) {\n entities.push(entity as AgentEntity);\n }\n }\n\n return entities;\n }\n\n /**\n * Get most recently accessed entities.\n *\n * @param limit - Maximum number of entities to return\n * @param withinHours - Optional filter for accesses within N hours\n * @returns Array of entities sorted by recency (most recent first)\n */\n async getRecentlyAccessed(\n limit: number,\n withinHours?: number\n ): Promise<AgentEntity[]> {\n const now = Date.now();\n const windowStart = withinHours ? now - withinHours * 60 * 60 * 1000 : 0;\n\n // Collect entities with their last access time\n const scored: Array<{ name: string; lastAccess: number }> = [];\n\n for (const [name, record] of this.accessRecords) {\n const lastAccess = new Date(record.lastAccessedAt).getTime();\n\n if (lastAccess >= windowStart) {\n scored.push({ name, lastAccess });\n }\n }\n\n // Sort by last access (most recent first) and take top N\n scored.sort((a, b) => b.lastAccess - a.lastAccess);\n const topNames = scored.slice(0, limit).map((s) => s.name);\n\n // Fetch entities from storage\n const entities: AgentEntity[] = [];\n for (const name of topNames) {\n const entity = this.storage.getEntityByName(name);\n if (entity) {\n entities.push(entity as AgentEntity);\n }\n }\n\n return entities;\n }\n\n /**\n * Persist all dirty access records to storage.\n * Call periodically or on shutdown.\n */\n async flush(): Promise<void> {\n if (!this.dirty) return;\n\n // Persistence logic depends on storage backend\n // For now, entity updates happen in recordAccess\n this.dirty = false;\n }\n\n /**\n * Check if there are unsaved changes.\n */\n isDirty(): boolean {\n return this.dirty;\n }\n\n /**\n * Get all tracked entity names.\n */\n getTrackedEntities(): string[] {\n return Array.from(this.accessRecords.keys());\n }\n\n /**\n * Clear access records for an entity.\n *\n * @param entityName - Entity to clear records for\n */\n clearAccessRecords(entityName: string): void {\n this.accessRecords.delete(entityName);\n this.dirty = true;\n }\n\n /**\n * Clear all access records.\n */\n clearAllAccessRecords(): void {\n this.accessRecords.clear();\n this.dirty = true;\n }\n\n // ==================== Private Helpers ====================\n\n /**\n * Update entity access fields in storage.\n */\n private async updateEntityAccessFields(\n entityName: string,\n _record: AccessRecord\n ): Promise<void> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity) return;\n\n // Update entity's access tracking fields\n // Note: This updates the entity's lastModified timestamp\n // AgentEntity fields (accessCount, lastAccessedAt, accessPattern)\n // will be managed by higher-level managers that handle AgentEntity\n // The _record parameter is available for future use when we need\n // to update AgentEntity-specific fields\n await this.storage.updateEntity(entityName, {\n lastModified: new Date().toISOString(),\n });\n }\n\n /**\n * Classify access pattern based on access frequency.\n */\n private classifyAccessPattern(record: AccessRecord): AccessPattern {\n if (record.recentAccesses.length < 2) {\n return 'rare';\n }\n\n // Calculate accesses per day over the history window\n const oldest = new Date(record.recentAccesses[0]).getTime();\n const newest = new Date(\n record.recentAccesses[record.recentAccesses.length - 1]\n ).getTime();\n const daysDiff = Math.max((newest - oldest) / (1000 * 60 * 60 * 24), 1);\n const accessesPerDay = record.recentAccesses.length / daysDiff;\n\n if (accessesPerDay >= this.config.frequentThreshold) {\n return 'frequent';\n } else if (accessesPerDay >= this.config.occasionalThreshold) {\n return 'occasional';\n } else {\n return 'rare';\n }\n }\n\n /**\n * Calculate average interval between accesses.\n */\n private calculateAverageInterval(record: AccessRecord): number {\n if (record.recentAccesses.length < 2) {\n return Infinity;\n }\n\n let totalInterval = 0;\n for (let i = 1; i < record.recentAccesses.length; i++) {\n const prev = new Date(record.recentAccesses[i - 1]).getTime();\n const curr = new Date(record.recentAccesses[i]).getTime();\n totalInterval += curr - prev;\n }\n\n return totalInterval / (record.recentAccesses.length - 1);\n }\n}\n","/**\n * Agent Memory Type Definitions\n *\n * Extended type definitions for AI agent memory systems.\n * Provides interfaces for working, episodic, and semantic memory\n * with lifecycle management and multi-agent support.\n *\n * @module types/agent-memory\n */\n\nimport type { Entity } from './types.js';\n\n// ==================== Memory Classification Types ====================\n\n/**\n * Classification of memory types based on cognitive patterns.\n * - working: Short-term, session-scoped, TTL-based\n * - episodic: Conversation history, events, experiences\n * - semantic: Long-term facts, concepts, knowledge\n * - procedural: Skills, patterns, procedures (future)\n */\nexport type MemoryType = 'working' | 'episodic' | 'semantic' | 'procedural';\n\n/**\n * Classification of memory access frequency.\n * Used for decay calculations and retrieval ranking.\n */\nexport type AccessPattern = 'frequent' | 'occasional' | 'rare';\n\n/**\n * Memory visibility for multi-agent scenarios.\n * - private: Only owning agent can access\n * - shared: Specified agents can access\n * - public: All agents can access\n */\nexport type MemoryVisibility = 'private' | 'shared' | 'public';\n\n/**\n * How a memory was acquired.\n * - observed: Directly perceived/received\n * - inferred: Derived through reasoning\n * - told: Communicated by another agent\n * - consolidated: Created by merging other memories\n */\nexport type MemoryAcquisitionMethod = 'observed' | 'inferred' | 'told' | 'consolidated';\n\n/**\n * Session lifecycle status.\n * - active: Session is currently in progress\n * - completed: Session ended normally\n * - abandoned: Session ended without proper closure\n */\nexport type SessionStatus = 'active' | 'completed' | 'abandoned';\n\n/**\n * Temporal focus for salience calculation.\n */\nexport type TemporalFocus = 'recent' | 'historical' | 'balanced';\n\n/**\n * Strategy for resolving conflicts between memories from different agents.\n * - most_recent: Use the memory with most recent lastModified\n * - highest_confidence: Use the memory with highest confidence score\n * - most_confirmations: Use the memory with most confirmations\n * - trusted_agent: Use the memory from agent with highest trustLevel\n * - merge_all: Combine observations from all conflicting memories\n */\nexport type ConflictStrategy =\n | 'most_recent'\n | 'highest_confidence'\n | 'most_confirmations'\n | 'trusted_agent'\n | 'merge_all';\n\n/**\n * Information about a detected memory conflict.\n */\nexport interface ConflictInfo {\n /** Primary memory in the conflict */\n primaryMemory: string;\n /** Conflicting memory names */\n conflictingMemories: string[];\n /** How conflict was detected */\n detectionMethod: 'similarity' | 'negation' | 'manual';\n /** Similarity score between memories (0-1) */\n similarityScore?: number;\n /** Suggested resolution strategy */\n suggestedStrategy: ConflictStrategy;\n /** Timestamp when conflict was detected */\n detectedAt: string;\n}\n\n// ==================== Memory Source Types ====================\n\n/**\n * Source tracking for observation provenance.\n *\n * Tracks how an observation was acquired:\n * - user_input: Directly from user conversation\n * - agent_inference: Derived by agent reasoning\n * - external_api: Retrieved from external service\n * - consolidation: Created by summarizing other observations\n */\nexport interface ObservationSource {\n /** How the observation was acquired */\n type: 'user_input' | 'agent_inference' | 'external_api' | 'consolidation';\n /** Agent that created this observation */\n agentId?: string;\n /** Session during which observation was created */\n sessionId?: string;\n /** Original user input if transformed */\n rawInput?: string;\n}\n\n/**\n * Provenance tracking for memory origin.\n *\n * Tracks the complete lineage of a memory:\n * - Which agent created it\n * - When it was created\n * - How it was acquired\n * - How reliable it is\n * - What it was derived from (if consolidated)\n *\n * @example\n * ```typescript\n * const source: MemorySource = {\n * agentId: 'travel_assistant',\n * timestamp: '2024-01-15T10:30:00Z',\n * method: 'observed',\n * reliability: 0.95\n * };\n * ```\n */\nexport interface MemorySource {\n /** ID of the agent that created this memory */\n agentId: string;\n /** ISO 8601 timestamp when memory was created */\n timestamp: string;\n /** How the memory was acquired */\n method: MemoryAcquisitionMethod;\n /** Trust/reliability score (0.0-1.0) */\n reliability: number;\n /** Original entity ID if consolidated from another memory */\n originalEntityId?: string;\n}\n\n// ==================== Agent Entity Types ====================\n\n/**\n * Extended entity interface for agent memory systems.\n *\n * Extends the base Entity with fields for:\n * - Memory classification (working/episodic/semantic/procedural)\n * - Session and task context\n * - Lifecycle management (TTL, promotion tracking)\n * - Access tracking (frequency, recency, patterns)\n * - Memory strength (confidence, confirmations, decay)\n * - Multi-agent support (ownership, visibility, provenance)\n *\n * @example\n * ```typescript\n * const memory: AgentEntity = {\n * name: 'user_preference_budget_travel',\n * entityType: 'preference',\n * observations: ['User prefers budget travel options'],\n * memoryType: 'working',\n * sessionId: 'session_123',\n * expiresAt: '2024-01-02T00:00:00Z',\n * accessCount: 5,\n * confidence: 0.9,\n * confirmationCount: 2,\n * visibility: 'private'\n * };\n * ```\n */\nexport interface AgentEntity extends Entity {\n // === Memory Classification ===\n /** Type of memory: working, episodic, semantic, or procedural */\n memoryType: MemoryType;\n\n // === Session & Context ===\n /** Session ID grouping related memories */\n sessionId?: string;\n /** Specific conversation identifier */\n conversationId?: string;\n /** Associated task or goal identifier */\n taskId?: string;\n\n // === Lifecycle Management ===\n /** ISO 8601 timestamp for auto-cleanup (working memory) */\n expiresAt?: string;\n /** Flag indicating temporary working memory */\n isWorkingMemory?: boolean;\n /** ISO 8601 timestamp when promoted to long-term */\n promotedAt?: string;\n /** Source session or entity ID if promoted */\n promotedFrom?: string;\n /** Flag indicating memory is marked for promotion consideration */\n markedForPromotion?: boolean;\n\n // === Access Tracking ===\n /** Total number of times this memory was retrieved */\n accessCount: number;\n /** ISO 8601 timestamp of most recent access */\n lastAccessedAt?: string;\n /** Classified access frequency pattern */\n accessPattern?: AccessPattern;\n\n // === Memory Strength ===\n /** Belief strength / certainty (0.0-1.0) */\n confidence: number;\n /** Number of times this memory was verified/reinforced */\n confirmationCount: number;\n /** Custom decay rate multiplier (default 1.0) */\n decayRate?: number;\n\n // === Multi-Agent ===\n /** Owning agent identifier */\n agentId?: string;\n /** Visibility level for multi-agent access */\n visibility: MemoryVisibility;\n /** Provenance tracking for memory origin */\n source?: MemorySource;\n}\n\n// ==================== Agent Observation Types ====================\n\n/**\n * Extended observation with confidence, temporal validity, and provenance.\n *\n * Agent observations go beyond simple strings to include:\n * - Confidence scoring for belief strength\n * - Confirmation counting for verification tracking\n * - Contradiction references for conflicting information\n * - Temporal scoping for time-bounded facts\n * - Source provenance for audit trails\n * - Consolidation metadata for summarization tracking\n *\n * @example\n * ```typescript\n * const observation: AgentObservation = {\n * content: 'User prefers budget travel under $100/night',\n * confidence: 0.95,\n * confirmationCount: 3,\n * observedAt: '2024-01-15T10:30:00Z',\n * source: { type: 'user_input', sessionId: 'session_123' },\n * abstractionLevel: 0\n * };\n * ```\n */\nexport interface AgentObservation {\n /** The observation content text */\n content: string;\n\n // === Confidence & Verification ===\n /** Certainty level (0.0-1.0) */\n confidence: number;\n /** Number of times this observation was confirmed */\n confirmationCount: number;\n /** IDs of observations that contradict this one */\n contradictedBy?: string[];\n\n // === Temporal Context ===\n /** ISO 8601 timestamp when this was learned */\n observedAt: string;\n /** ISO 8601 timestamp when this fact becomes valid */\n validFrom?: string;\n /** ISO 8601 timestamp when this fact expires */\n validUntil?: string;\n\n // === Source Tracking ===\n /** Provenance information */\n source: ObservationSource;\n\n // === Consolidation ===\n /** IDs of observations this was summarized from */\n consolidatedFrom?: string[];\n /** Abstraction depth: 0=raw, 1=summarized, 2=generalized */\n abstractionLevel: number;\n}\n\n// ==================== Session Entity Types ====================\n\n/**\n * Entity representing a conversation or task session.\n *\n * Sessions group working memories and provide context for:\n * - Memory scoping (all memories in a session)\n * - Consolidation triggers (end of session)\n * - Context continuity (linked sessions)\n * - Goal tracking (task metadata)\n *\n * @example\n * ```typescript\n * const session: SessionEntity = {\n * name: 'session_trip_planning_2024_01_15',\n * entityType: 'session',\n * observations: ['Planning trip to Japan'],\n * memoryType: 'episodic',\n * status: 'active',\n * startedAt: '2024-01-15T10:00:00Z',\n * goalDescription: 'Help user plan a 2-week trip to Japan',\n * taskType: 'travel_planning',\n * memoryCount: 15,\n * consolidatedCount: 0,\n * accessCount: 0,\n * confidence: 1.0,\n * confirmationCount: 0,\n * visibility: 'private'\n * };\n * ```\n */\nexport interface SessionEntity extends AgentEntity {\n /** Fixed entity type for sessions */\n entityType: 'session';\n /** Sessions are episodic memory */\n memoryType: 'episodic';\n\n // === Session Metadata ===\n /** ISO 8601 timestamp when session started */\n startedAt: string;\n /** ISO 8601 timestamp when session ended */\n endedAt?: string;\n /** Current session status */\n status: SessionStatus;\n\n // === Context ===\n /** Description of the session's goal or purpose */\n goalDescription?: string;\n /** Type of task being performed */\n taskType?: string;\n /** Detected user intent for the session */\n userIntent?: string;\n\n // === Statistics ===\n /** Count of memories created during this session */\n memoryCount: number;\n /** Count of memories promoted to long-term storage */\n consolidatedCount: number;\n\n // === Relationships ===\n /** ID of previous session if this is a continuation */\n previousSessionId?: string;\n /** IDs of related sessions */\n relatedSessionIds?: string[];\n}\n\n// ==================== Context Types ====================\n\n/**\n * Context provided when recording a memory access.\n * Used to track how and why a memory was retrieved.\n */\nexport interface AccessContext {\n /** Session ID during which access occurred */\n sessionId?: string;\n /** Task ID associated with access */\n taskId?: string;\n /** Search query that triggered this access */\n queryContext?: string;\n /** How the memory was retrieved */\n retrievalMethod?: 'search' | 'direct' | 'traversal';\n}\n\n/**\n * Builder for constructing AccessContext objects.\n *\n * @example\n * ```typescript\n * const ctx = new AccessContextBuilder()\n * .forSession('session_123')\n * .withQuery('budget hotels')\n * .viaSearch()\n * .build();\n * ```\n */\nexport class AccessContextBuilder {\n private context: AccessContext = {};\n\n forSession(sessionId: string): this {\n this.context.sessionId = sessionId;\n return this;\n }\n\n forTask(taskId: string): this {\n this.context.taskId = taskId;\n return this;\n }\n\n withQuery(queryContext: string): this {\n this.context.queryContext = queryContext;\n return this;\n }\n\n viaSearch(): this {\n this.context.retrievalMethod = 'search';\n return this;\n }\n\n viaDirect(): this {\n this.context.retrievalMethod = 'direct';\n return this;\n }\n\n viaTraversal(): this {\n this.context.retrievalMethod = 'traversal';\n return this;\n }\n\n build(): AccessContext {\n return { ...this.context };\n }\n}\n\n/**\n * Context for salience calculation.\n *\n * @example\n * ```typescript\n * const context: SalienceContext = {\n * currentTask: 'travel_booking',\n * currentSession: 'session_123',\n * queryText: 'hotel preferences',\n * temporalFocus: 'recent',\n * };\n * ```\n */\nexport interface SalienceContext {\n /** Current task identifier */\n currentTask?: string;\n /** Current session identifier */\n currentSession?: string;\n /** Recently accessed entity names */\n recentEntities?: string[];\n /** Query text for relevance matching */\n queryText?: string;\n /** Inferred user intent */\n userIntent?: string;\n /** Temporal focus for scoring */\n temporalFocus?: TemporalFocus;\n /** Custom context fields */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Weights for salience calculation components.\n */\nexport interface SalienceWeights {\n importance: number;\n recency: number;\n frequency: number;\n context: number;\n novelty: number;\n}\n\n/**\n * Component breakdown of salience score.\n */\nexport interface SalienceComponents {\n /** Base importance after decay */\n baseImportance: number;\n /** Recency contribution */\n recencyBoost: number;\n /** Frequency contribution */\n frequencyBoost: number;\n /** Context relevance contribution */\n contextRelevance: number;\n /** Novelty contribution */\n noveltyBoost: number;\n}\n\n/**\n * Entity with calculated salience score.\n *\n * @example\n * ```typescript\n * const scored = await engine.calculateSalience(entity, context);\n * console.log(`Score: ${scored.salienceScore}`);\n * console.log(`Recency: ${scored.components.recencyBoost}`);\n * ```\n */\nexport interface ScoredEntity {\n /** The entity being scored */\n entity: AgentEntity;\n /** Overall salience score (0-1) */\n salienceScore: number;\n /** Component breakdown */\n components: SalienceComponents;\n}\n\n// ==================== Working Memory Types ====================\n\n/**\n * Options for creating working memory.\n *\n * @example\n * ```typescript\n * const options: WorkingMemoryOptions = {\n * ttlHours: 48,\n * taskId: 'trip_planning',\n * importance: 7,\n * confidence: 0.9,\n * };\n * ```\n */\nexport interface WorkingMemoryOptions {\n /** TTL in hours (default: 24) */\n ttlHours?: number;\n /** Enable auto-promotion when thresholds met */\n autoPromote?: boolean;\n /** Associated task ID */\n taskId?: string;\n /** Initial importance (0-10) */\n importance?: number;\n /** Initial confidence (0-1) */\n confidence?: number;\n /** Entity type (default: 'working_memory') */\n entityType?: string;\n /** Visibility for multi-agent scenarios */\n visibility?: MemoryVisibility;\n /** Agent ID for multi-agent scenarios */\n agentId?: string;\n}\n\n// ==================== Decay Types ====================\n\n/**\n * Options for decay operations.\n *\n * @remarks\n * - halfLifeHours must be positive\n * - minImportance should be in range [0, 10]\n */\nexport interface DecayOptions {\n /** Override half-life in hours (must be > 0) */\n halfLifeHours?: number;\n /** Enable importance-based half-life modulation */\n importanceModulation?: boolean;\n /** Enable access frequency-based modulation */\n accessModulation?: boolean;\n /** Minimum importance floor (0-10) */\n minImportance?: number;\n /** Dry run - calculate but don't persist */\n dryRun?: boolean;\n}\n\n/**\n * Options for forgetting weak memories.\n *\n * @remarks\n * - effectiveImportanceThreshold is required and must be in range [0, 10]\n * - olderThanHours must be positive if specified\n */\nexport interface ForgetOptions {\n /** Threshold - forget memories with effective importance below this (required) */\n effectiveImportanceThreshold: number;\n /** Only forget memories older than this many hours */\n olderThanHours?: number;\n /** Tags that protect memories from forgetting */\n excludeTags?: string[];\n /** Preview mode - calculate but don't delete */\n dryRun?: boolean;\n /** Archive instead of hard delete */\n archive?: boolean;\n}\n\n/**\n * Result of batch decay operation.\n */\nexport interface DecayResult {\n /** Number of entities processed */\n entitiesProcessed: number;\n /** Average decay factor (0-1, higher = more decay) */\n averageDecay: number;\n /** Memories below warning threshold but not forgotten */\n memoriesAtRisk: number;\n /** Processing time in milliseconds */\n processingTimeMs: number;\n}\n\n/**\n * Result of forget operation.\n */\nexport interface ForgetResult {\n /** Number of memories forgotten */\n memoriesForgotten: number;\n /** Names of forgotten entities */\n forgottenNames: string[];\n /** Memories protected by tags */\n memoriesProtected: number;\n /** Memories too young to forget */\n memoriesTooYoung: number;\n /** Was this a dry run? */\n dryRun: boolean;\n}\n\n// ==================== Type Guards ====================\n\n/**\n * Type guard to check if an entity is an AgentEntity.\n *\n * @param entity - Entity to check\n * @returns True if entity has AgentEntity required fields\n *\n * @example\n * ```typescript\n * if (isAgentEntity(entity)) {\n * console.log(entity.memoryType); // TypeScript knows this exists\n * }\n * ```\n */\nexport function isAgentEntity(entity: unknown): entity is AgentEntity {\n if (!entity || typeof entity !== 'object') return false;\n const e = entity as Record<string, unknown>;\n return (\n typeof e.name === 'string' &&\n typeof e.entityType === 'string' &&\n typeof e.memoryType === 'string' &&\n ['working', 'episodic', 'semantic', 'procedural'].includes(e.memoryType as string) &&\n typeof e.accessCount === 'number' &&\n typeof e.confidence === 'number' &&\n typeof e.confirmationCount === 'number' &&\n typeof e.visibility === 'string' &&\n ['private', 'shared', 'public'].includes(e.visibility as string)\n );\n}\n\n/**\n * Type guard to check if an entity is a SessionEntity.\n *\n * @param entity - Entity to check\n * @returns True if entity is a session\n *\n * @example\n * ```typescript\n * if (isSessionEntity(entity)) {\n * console.log(entity.status); // TypeScript knows this is SessionEntity\n * }\n * ```\n */\nexport function isSessionEntity(entity: unknown): entity is SessionEntity {\n if (!isAgentEntity(entity)) return false;\n const e = entity as AgentEntity;\n return (\n e.entityType === 'session' &&\n e.memoryType === 'episodic' &&\n typeof (e as SessionEntity).startedAt === 'string' &&\n typeof (e as SessionEntity).status === 'string' &&\n ['active', 'completed', 'abandoned'].includes((e as SessionEntity).status) &&\n typeof (e as SessionEntity).memoryCount === 'number' &&\n typeof (e as SessionEntity).consolidatedCount === 'number'\n );\n}\n\n/**\n * Type guard to check if an entity is working memory.\n */\nexport function isWorkingMemory(entity: unknown): entity is AgentEntity & { memoryType: 'working' } {\n return isAgentEntity(entity) && entity.memoryType === 'working';\n}\n\n/**\n * Type guard to check if an entity is episodic memory.\n */\nexport function isEpisodicMemory(entity: unknown): entity is AgentEntity & { memoryType: 'episodic' } {\n return isAgentEntity(entity) && entity.memoryType === 'episodic';\n}\n\n/**\n * Type guard to check if an entity is semantic memory.\n */\nexport function isSemanticMemory(entity: unknown): entity is AgentEntity & { memoryType: 'semantic' } {\n return isAgentEntity(entity) && entity.memoryType === 'semantic';\n}\n\n/**\n * Type guard to check if an entity is procedural memory.\n */\nexport function isProceduralMemory(entity: unknown): entity is AgentEntity & { memoryType: 'procedural' } {\n return isAgentEntity(entity) && entity.memoryType === 'procedural';\n}\n\n// ==================== Utility Types ====================\n\n/**\n * Utility type for working memory entities.\n */\nexport type WorkingMemoryEntity = AgentEntity & { memoryType: 'working' };\n\n/**\n * Utility type for episodic memory entities.\n */\nexport type EpisodicMemoryEntity = AgentEntity & { memoryType: 'episodic' };\n\n/**\n * Utility type for semantic memory entities.\n */\nexport type SemanticMemoryEntity = AgentEntity & { memoryType: 'semantic' };\n\n/**\n * Utility type for procedural memory entities (future).\n */\nexport type ProceduralMemoryEntity = AgentEntity & { memoryType: 'procedural' };\n\n// ==================== Consolidation Types ====================\n\n/**\n * Options for memory consolidation operations.\n *\n * Controls how working memories are transformed and promoted\n * to long-term storage.\n *\n * @example\n * ```typescript\n * const options: ConsolidateOptions = {\n * summarize: true,\n * extractPatterns: true,\n * minConfidence: 0.8,\n * minConfirmations: 3,\n * };\n * ```\n */\nexport interface ConsolidateOptions {\n /** Enable observation summarization (default: true) */\n summarize?: boolean;\n /** Enable pattern extraction (default: true) */\n extractPatterns?: boolean;\n /** Minimum confidence for promotion (0-1, default: 0.7) */\n minConfidence?: number;\n /** Minimum confirmation count for promotion (default: 2) */\n minConfirmations?: number;\n /** Keep original working memories after promotion (default: false) */\n preserveOriginals?: boolean;\n /** Target memory type for promotion (default: 'episodic') */\n targetType?: MemoryType;\n}\n\n/**\n * Result of a consolidation operation.\n *\n * Provides detailed statistics about what was processed\n * and any errors encountered.\n *\n * @example\n * ```typescript\n * const result = await pipeline.consolidateSession('session_1');\n * console.log(`Promoted ${result.memoriesPromoted} of ${result.memoriesProcessed}`);\n * ```\n */\nexport interface ConsolidationResult {\n /** Total memories processed */\n memoriesProcessed: number;\n /** Memories successfully promoted to long-term */\n memoriesPromoted: number;\n /** Memories merged with existing entities */\n memoriesMerged: number;\n /** Patterns extracted from observations */\n patternsExtracted: number;\n /** Summary observations created */\n summariesCreated: number;\n /** Error messages encountered */\n errors: string[];\n}\n\n/**\n * Result of observation summarization.\n *\n * Provides detailed statistics about the summarization operation\n * including compression metrics and provenance tracking.\n *\n * @example\n * ```typescript\n * const result = await pipeline.summarizeObservations(entity);\n * console.log(`Compressed ${result.originalCount} to ${result.summaryCount}`);\n * console.log(`Compression ratio: ${result.compressionRatio.toFixed(2)}x`);\n * ```\n */\nexport interface SummarizationResult {\n /** Number of original observations */\n originalCount: number;\n /** Number of summary observations */\n summaryCount: number;\n /** Compression ratio (original / summary) */\n compressionRatio: number;\n /** The generated summaries */\n summaries: string[];\n /** Source observations for each summary (for provenance) */\n sourceObservations: string[][];\n}\n\n/**\n * Result of pattern detection in observations.\n *\n * Represents a detected template pattern with variable slots.\n * Used for identifying recurring patterns and creating semantic memories.\n *\n * @example\n * ```typescript\n * const patterns = await pipeline.extractPatterns('preference');\n * for (const p of patterns) {\n * console.log(`Pattern: ${p.pattern}`);\n * console.log(`Variables: ${p.variables.join(', ')}`);\n * console.log(`Confidence: ${p.confidence}`);\n * }\n * ```\n */\nexport interface PatternResult {\n /** Template pattern with {X} variable slots (e.g., \"User prefers {X}\") */\n pattern: string;\n /** Extracted variable values from matching observations */\n variables: string[];\n /** Number of times pattern appeared */\n occurrences: number;\n /** Confidence score based on frequency (0-1) */\n confidence: number;\n /** Names of source entities that contain this pattern */\n sourceEntities: string[];\n}\n\n/**\n * Strategy for merging duplicate memories.\n * - newest: Keep the most recently modified entity\n * - strongest: Keep entity with highest confidence * confirmations\n * - merge_observations: Combine all observations into first entity\n */\nexport type MemoryMergeStrategy = 'newest' | 'strongest' | 'merge_observations';\n\n/**\n * Result of a memory merge operation.\n *\n * @example\n * ```typescript\n * const result = await pipeline.mergeMemories(['ent1', 'ent2'], 'newest');\n * console.log(`Survivor: ${result.survivor.name}`);\n * console.log(`Merged ${result.mergedCount} entities`);\n * ```\n */\nexport interface MergeResult {\n /** The surviving merged entity */\n survivor: AgentEntity;\n /** Names of entities that were merged */\n mergedEntities: string[];\n /** Number of entities merged */\n mergedCount: number;\n /** Strategy used for merge */\n strategy: MemoryMergeStrategy;\n /** Combined observation count after dedup */\n observationCount: number;\n}\n\n/**\n * Result of duplicate detection.\n */\nexport interface DuplicatePair {\n /** First entity name */\n entity1: string;\n /** Second entity name */\n entity2: string;\n /** Similarity score between entities (0-1) */\n similarity: number;\n}\n\n// ==================== Auto-Consolidation Rule Types ====================\n\n/**\n * Trigger types for consolidation rules.\n */\nexport type ConsolidationTrigger =\n | 'session_end'\n | 'time_elapsed'\n | 'confirmation_threshold'\n | 'manual';\n\n/**\n * Actions for consolidation rules.\n */\nexport type ConsolidationAction =\n | 'promote_to_episodic'\n | 'promote_to_semantic'\n | 'merge_duplicates'\n | 'archive'\n | 'summarize';\n\n/**\n * Conditions for rule evaluation.\n *\n * @example\n * ```typescript\n * const conditions: RuleConditions = {\n * minConfidence: 0.8,\n * minConfirmations: 2,\n * memoryType: 'working',\n * useAnd: true,\n * };\n * ```\n */\nexport interface RuleConditions {\n /** Minimum confidence score (0-1) */\n minConfidence?: number;\n /** Minimum confirmation count */\n minConfirmations?: number;\n /** Minimum access count */\n minAccessCount?: number;\n /** Memory type filter */\n memoryType?: MemoryType;\n /** Entity type filter */\n entityType?: string;\n /** Minimum age in hours */\n minAgeHours?: number;\n /** Use AND logic (default: true) */\n useAnd?: boolean;\n}\n\n/**\n * Rule for automatic consolidation.\n *\n * @example\n * ```typescript\n * const rule: ConsolidationRule = {\n * name: 'Promote confirmed memories',\n * trigger: 'session_end',\n * conditions: {\n * minConfidence: 0.8,\n * minConfirmations: 2,\n * memoryType: 'working',\n * },\n * action: 'promote_to_episodic',\n * enabled: true,\n * priority: 10,\n * };\n * ```\n */\nexport interface ConsolidationRule {\n /** Rule name for identification */\n name: string;\n /** What triggers this rule */\n trigger: ConsolidationTrigger;\n /** Conditions that must be met */\n conditions: RuleConditions;\n /** Action to take */\n action: ConsolidationAction;\n /** Whether rule is active */\n enabled: boolean;\n /** Optional priority (higher = processed first) */\n priority?: number;\n}\n\n/**\n * Result of rule evaluation.\n */\nexport interface RuleEvaluationResult {\n /** Whether all conditions passed */\n passed: boolean;\n /** Per-condition results */\n details: Record<string, boolean>;\n}\n\n// ==================== Context Window Management Types ====================\n\n/**\n * Options for retrieving memories within a token budget.\n *\n * @example\n * ```typescript\n * const options: ContextRetrievalOptions = {\n * maxTokens: 4000,\n * context: { currentTask: 'booking', queryText: 'hotel' },\n * includeWorkingMemory: true,\n * includeEpisodicRecent: true,\n * mustInclude: ['user_preferences'],\n * };\n * ```\n */\nexport interface ContextRetrievalOptions {\n /** Maximum token budget for retrieved memories */\n maxTokens: number;\n /** Salience context for relevance scoring */\n context?: SalienceContext;\n /** Include working memory entities (default: true) */\n includeWorkingMemory?: boolean;\n /** Include recent episodic memories (default: true) */\n includeEpisodicRecent?: boolean;\n /** Include semantically relevant memories (default: true) */\n includeSemanticRelevant?: boolean;\n /** Entity names that must be included regardless of budget */\n mustInclude?: string[];\n /** Minimum salience score to consider (default: 0) */\n minSalience?: number;\n}\n\n/**\n * Token breakdown by memory type.\n */\nexport interface TokenBreakdown {\n /** Tokens used by working memory */\n working: number;\n /** Tokens used by episodic memory */\n episodic: number;\n /** Tokens used by semantic memory */\n semantic: number;\n /** Tokens used by procedural memory */\n procedural: number;\n /** Tokens used by must-include entities */\n mustInclude: number;\n}\n\n/**\n * Result of context window memory retrieval.\n *\n * @example\n * ```typescript\n * const result: ContextPackage = {\n * memories: [entity1, entity2],\n * totalTokens: 3500,\n * breakdown: { working: 1000, episodic: 2000, semantic: 500, procedural: 0, mustInclude: 0 },\n * excluded: [{ entity: entity3, reason: 'budget_exceeded', tokens: 800 }],\n * suggestions: ['Consider retrieving user_history if more space available'],\n * };\n * ```\n */\nexport interface ContextPackage {\n /** Retrieved memory entities */\n memories: AgentEntity[];\n /** Total tokens used */\n totalTokens: number;\n /** Token breakdown by memory type */\n breakdown: TokenBreakdown;\n /** Entities that didn't fit in budget */\n excluded: ExcludedEntity[];\n /** Suggestions for additional retrieval if budget increases */\n suggestions: string[];\n}\n\n/**\n * Entity excluded from context package with reason.\n */\nexport interface ExcludedEntity {\n /** The excluded entity */\n entity: AgentEntity;\n /** Why it was excluded */\n reason: 'budget_exceeded' | 'low_salience' | 'filtered';\n /** Estimated tokens for this entity */\n tokens: number;\n /** Salience score at time of exclusion */\n salience?: number;\n}\n\n// ==================== Multi-Agent Types ====================\n\n/**\n * Agent type classifications.\n */\nexport type AgentType = 'llm' | 'tool' | 'human' | 'system' | 'default';\n\n/**\n * Metadata for registered agents.\n *\n * @example\n * ```typescript\n * const metadata: AgentMetadata = {\n * name: 'Travel Assistant',\n * type: 'llm',\n * trustLevel: 0.9,\n * capabilities: ['read', 'write', 'share'],\n * createdAt: new Date().toISOString(),\n * lastActiveAt: new Date().toISOString(),\n * };\n * ```\n */\nexport interface AgentMetadata {\n /** Human-readable agent name */\n name: string;\n /** Agent type classification */\n type: AgentType;\n /** Trust level (0-1, higher = more trusted) */\n trustLevel: number;\n /** Agent capabilities */\n capabilities: string[];\n /** Registration timestamp */\n createdAt: string;\n /** Last activity timestamp */\n lastActiveAt: string;\n /** Optional custom metadata */\n metadata?: Record<string, unknown>;\n}\n","/**\n * Decay Engine\n *\n * Implements time-based memory importance decay with\n * importance and access modulation. Memories weaken over\n * time unless reinforced by access or confirmation.\n *\n * @module agent/DecayEngine\n */\n\nimport type { IGraphStorage } from '../types/types.js';\nimport type { AgentEntity, DecayResult, ForgetOptions, ForgetResult } from '../types/agent-memory.js';\nimport { isAgentEntity } from '../types/agent-memory.js';\nimport { AccessTracker } from './AccessTracker.js';\n\n// Re-export for convenience\nexport type { DecayResult, ForgetOptions, ForgetResult } from '../types/agent-memory.js';\n\n/**\n * Configuration options for DecayEngine constructor.\n */\nexport interface DecayEngineConfig {\n /** Base half-life in hours (default: 168 = 1 week) */\n halfLifeHours?: number;\n /** Enable importance-based half-life modulation (default: true) */\n importanceModulation?: boolean;\n /** Enable access frequency-based modulation (default: true) */\n accessModulation?: boolean;\n /** Minimum importance floor (default: 0.1) */\n minImportance?: number;\n}\n\n/**\n * Options for batch decay operations.\n * Extends DecayEngineConfig with operation-specific options.\n */\nexport interface DecayOperationOptions extends DecayEngineConfig {\n /** Dry run - calculate but don't update entities */\n dryRun?: boolean;\n}\n\n/**\n * Options for reinforcing a memory.\n */\nexport interface ReinforcementOptions {\n /** Number of confirmations to add (default: 1) */\n confirmationBoost?: number;\n /** Amount to boost confidence by (0-1 scale) */\n confidenceBoost?: number;\n}\n\n/**\n * Implements time-based memory importance decay.\n *\n * The DecayEngine uses exponential decay to model how memories weaken\n * over time unless reinforced. This mimics natural cognitive processes\n * where unused information gradually becomes less accessible.\n *\n * Key features:\n * - Exponential decay formula with configurable half-life\n * - Importance modulation: important memories decay slower\n * - Access modulation: frequently accessed memories decay slower\n * - Strength multiplier from confirmations and access count\n * - Minimum importance floor prevents complete forgetting\n *\n * @example\n * ```typescript\n * const decay = new DecayEngine(storage, accessTracker);\n * const effective = decay.calculateEffectiveImportance(entity);\n * const decayed = await decay.getDecayedMemories(0.1);\n * await decay.reinforceMemory('entity_name');\n * ```\n */\nexport class DecayEngine {\n private readonly storage: IGraphStorage;\n private readonly accessTracker: AccessTracker;\n private readonly config: Required<DecayEngineConfig>;\n\n constructor(\n storage: IGraphStorage,\n accessTracker: AccessTracker,\n config: DecayEngineConfig = {}\n ) {\n this.storage = storage;\n this.accessTracker = accessTracker;\n this.config = {\n halfLifeHours: config.halfLifeHours ?? 168, // 1 week\n importanceModulation: config.importanceModulation ?? true,\n accessModulation: config.accessModulation ?? true,\n minImportance: config.minImportance ?? 0.1,\n };\n }\n\n // ==================== Decay Factor Calculation ====================\n\n /**\n * Calculate decay factor based on time since last access.\n *\n * Uses exponential decay: e^(-ln(2) * age_hours / half_life_hours)\n * - Returns 1.0 for just-accessed memories\n * - Returns 0.5 after one half-life\n * - Approaches 0 for very old memories\n *\n * @param lastAccessedAt - ISO 8601 timestamp of last access\n * @param halfLifeHours - Base half-life in hours\n * @param importanceBoost - Optional boost factor (0-10 scale) that extends half-life\n * @returns Decay factor between 0.0 and 1.0\n */\n calculateDecayFactor(\n lastAccessedAt: string,\n halfLifeHours: number,\n importanceBoost?: number\n ): number {\n if (!lastAccessedAt) {\n return 0; // No access history = fully decayed\n }\n\n const now = Date.now();\n const lastAccess = new Date(lastAccessedAt).getTime();\n const ageHours = (now - lastAccess) / (1000 * 60 * 60);\n\n // Apply importance boost to half-life if provided and modulation enabled\n let effectiveHalfLife = halfLifeHours;\n if (importanceBoost !== undefined && this.config.importanceModulation) {\n // Importance of 10 doubles the half-life\n effectiveHalfLife = halfLifeHours * (1 + importanceBoost / 10);\n }\n\n // Exponential decay formula\n const decayConstant = Math.LN2 / effectiveHalfLife;\n const decayFactor = Math.exp(-decayConstant * ageHours);\n\n return Math.max(0, Math.min(1, decayFactor));\n }\n\n /**\n * Static utility to calculate decay factor without instance.\n *\n * @param lastAccessedAt - ISO 8601 timestamp of last access\n * @param halfLifeHours - Half-life in hours (default: 168 = 1 week)\n * @returns Decay factor between 0.0 and 1.0\n */\n static calculateDecayFactorStatic(\n lastAccessedAt: string,\n halfLifeHours: number = 168\n ): number {\n if (!lastAccessedAt) return 0;\n\n const now = Date.now();\n const lastAccess = new Date(lastAccessedAt).getTime();\n const ageHours = (now - lastAccess) / (1000 * 60 * 60);\n\n const decayConstant = Math.LN2 / halfLifeHours;\n return Math.max(0, Math.min(1, Math.exp(-decayConstant * ageHours)));\n }\n\n // ==================== Effective Importance Calculation ====================\n\n /**\n * Calculate strength multiplier from confirmations and access count.\n *\n * Formula: 1 + (confirmationCount * 0.1) + (accessCount * 0.01)\n * - Each confirmation adds 10% strength\n * - Each 100 accesses add 1% strength\n *\n * @param entity - AgentEntity to calculate strength for\n * @returns Strength multiplier >= 1.0\n */\n private calculateStrengthMultiplier(entity: AgentEntity): number {\n const confirmationBoost = (entity.confirmationCount ?? 0) * 0.1;\n const accessBoost = (entity.accessCount ?? 0) * 0.01;\n return 1 + confirmationBoost + accessBoost;\n }\n\n /**\n * Calculate effective importance considering decay and strength.\n *\n * Formula: base_importance * decay_factor * strength_multiplier\n *\n * - base_importance: Entity's stated importance (0-10)\n * - decay_factor: Time-based decay (0-1)\n * - strength_multiplier: Boost from confirmations and accesses\n *\n * Result is clamped to minimum importance floor.\n *\n * @param entity - AgentEntity to calculate importance for\n * @returns Effective importance value\n */\n calculateEffectiveImportance(entity: AgentEntity): number {\n // Get base importance (default to 5 if not set)\n const baseImportance = entity.importance ?? 5;\n\n // Determine timestamp for decay calculation\n const decayTimestamp = entity.lastAccessedAt ?? entity.createdAt;\n if (!decayTimestamp) {\n // No timestamp = use base importance with min floor\n return Math.max(baseImportance, this.config.minImportance);\n }\n\n // Calculate decay factor with importance modulation\n const importanceBoost = this.config.importanceModulation ? baseImportance : undefined;\n const decayFactor = this.calculateDecayFactor(\n decayTimestamp,\n this.config.halfLifeHours,\n importanceBoost\n );\n\n // Calculate strength multiplier if access modulation enabled\n let strengthMultiplier = 1;\n if (this.config.accessModulation) {\n strengthMultiplier = this.calculateStrengthMultiplier(entity);\n }\n\n // Combine factors\n const effectiveImportance = baseImportance * decayFactor * strengthMultiplier;\n\n // Apply minimum floor\n return Math.max(effectiveImportance, this.config.minImportance);\n }\n\n // ==================== Decayed Memory Queries ====================\n\n /**\n * Get memories that have decayed below threshold.\n *\n * Queries all AgentEntity records and returns those with\n * effective importance below the specified threshold.\n *\n * @param threshold - Importance threshold (0-10 scale)\n * @returns Array of decayed AgentEntity records\n */\n async getDecayedMemories(threshold: number): Promise<AgentEntity[]> {\n const graph = await this.storage.loadGraph();\n const decayed: AgentEntity[] = [];\n\n for (const entity of graph.entities) {\n // Check if this is an AgentEntity\n if (!isAgentEntity(entity)) continue;\n\n // Calculate effective importance\n const effectiveImportance = this.calculateEffectiveImportance(entity);\n\n // Add to results if below threshold\n if (effectiveImportance < threshold) {\n decayed.push(entity);\n }\n }\n\n return decayed;\n }\n\n /**\n * Get memories at risk of being forgotten.\n *\n * Returns memories with effective importance between minImportance and threshold.\n * These are memories that have decayed significantly but haven't yet reached\n * the forgetting threshold.\n *\n * @param threshold - Upper threshold (default: 1.0)\n * @returns Array of at-risk AgentEntity records\n */\n async getMemoriesAtRisk(threshold: number = 1.0): Promise<AgentEntity[]> {\n const graph = await this.storage.loadGraph();\n const atRisk: AgentEntity[] = [];\n const minImportance = this.config.minImportance;\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n\n const effectiveImportance = this.calculateEffectiveImportance(entity);\n\n // At risk if between min floor and threshold\n if (effectiveImportance >= minImportance && effectiveImportance < threshold) {\n atRisk.push(entity);\n }\n }\n\n return atRisk;\n }\n\n // ==================== Memory Reinforcement ====================\n\n /**\n * Reinforce a memory to strengthen it against decay.\n *\n * Strengthening includes:\n * - Resetting decay timer (updating lastAccessedAt)\n * - Incrementing confirmationCount\n * - Optionally boosting confidence\n * - Recording access via AccessTracker\n *\n * @param entityName - Name of entity to reinforce\n * @param options - Reinforcement options\n * @throws Error if entity not found\n */\n async reinforceMemory(\n entityName: string,\n options?: ReinforcementOptions\n ): Promise<void> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity) {\n throw new Error(`Entity not found: ${entityName}`);\n }\n\n const now = new Date().toISOString();\n const agentEntity = entity as AgentEntity;\n\n const updates: Partial<AgentEntity> = {\n lastModified: now,\n lastAccessedAt: now, // Reset decay timer\n };\n\n // Increment confirmation count\n const currentConfirmations = agentEntity.confirmationCount ?? 0;\n const confirmationBoost = options?.confirmationBoost ?? 1;\n updates.confirmationCount = currentConfirmations + confirmationBoost;\n\n // Optionally boost confidence (capped at 1.0)\n if (options?.confidenceBoost) {\n const currentConfidence = agentEntity.confidence ?? 0.5;\n const newConfidence = Math.min(1, currentConfidence + options.confidenceBoost);\n updates.confidence = newConfidence;\n }\n\n // Record access via tracker\n await this.accessTracker.recordAccess(entityName, {\n retrievalMethod: 'direct',\n });\n\n // Persist updates\n await this.storage.updateEntity(entityName, updates as Record<string, unknown>);\n }\n\n // ==================== Forgetting Operations ====================\n\n /**\n * Forget (delete or archive) memories below threshold.\n *\n * Selects memories based on:\n * - Effective importance below threshold\n * - Age filter (olderThanHours)\n * - Tag exclusions (excludeTags)\n *\n * In non-dry-run mode, removes both entities and their relations.\n *\n * @param options - Forget operation options\n * @returns Results of the forget operation\n *\n * @example\n * ```typescript\n * // Preview what would be forgotten\n * const preview = await decay.forgetWeakMemories({\n * effectiveImportanceThreshold: 0.5,\n * excludeTags: ['important', 'permanent'],\n * dryRun: true\n * });\n *\n * // Actually forget\n * const result = await decay.forgetWeakMemories({\n * effectiveImportanceThreshold: 0.5,\n * olderThanHours: 168 // Only forget if > 1 week old\n * });\n * ```\n */\n async forgetWeakMemories(options: ForgetOptions): Promise<ForgetResult> {\n const graph = await this.storage.loadGraph();\n const now = Date.now();\n\n const forgottenNames: string[] = [];\n let memoriesProtected = 0;\n let memoriesTooYoung = 0;\n\n const excludeTagSet = new Set(\n (options.excludeTags ?? []).map(t => t.toLowerCase())\n );\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n\n const agentEntity = entity;\n\n // Check effective importance\n const effectiveImportance = this.calculateEffectiveImportance(agentEntity);\n if (effectiveImportance >= options.effectiveImportanceThreshold) {\n continue;\n }\n\n // Check age filter\n if (options.olderThanHours !== undefined) {\n const createdAt = agentEntity.createdAt\n ? new Date(agentEntity.createdAt).getTime()\n : now;\n const ageHours = (now - createdAt) / (1000 * 60 * 60);\n\n if (ageHours < options.olderThanHours) {\n memoriesTooYoung++;\n continue;\n }\n }\n\n // Check protected tags\n const entityTags = new Set(\n (agentEntity.tags ?? []).map(t => t.toLowerCase())\n );\n const hasProtectedTag = [...excludeTagSet].some(tag => entityTags.has(tag));\n\n if (hasProtectedTag) {\n memoriesProtected++;\n continue;\n }\n\n // Mark for forgetting\n forgottenNames.push(agentEntity.name);\n }\n\n // Execute forgetting if not dry run\n if (!options.dryRun && forgottenNames.length > 0) {\n const forgottenSet = new Set(forgottenNames);\n\n // Remove forgotten entities\n const updatedEntities = graph.entities.filter(\n e => !forgottenSet.has(e.name)\n );\n\n // Remove relations involving forgotten entities\n const updatedRelations = graph.relations.filter(\n r => !forgottenSet.has(r.from) && !forgottenSet.has(r.to)\n );\n\n // Save updated graph\n await this.storage.saveGraph({\n entities: updatedEntities,\n relations: updatedRelations,\n });\n }\n\n return {\n memoriesForgotten: forgottenNames.length,\n forgottenNames,\n memoriesProtected,\n memoriesTooYoung,\n dryRun: options.dryRun ?? false,\n };\n }\n\n // ==================== Batch Decay Operations ====================\n\n /**\n * Apply decay calculations to all memories (batch operation).\n *\n * Can optionally persist updated effective importance values\n * or run in dry-run mode for analysis only.\n *\n * @param options - Decay operation options\n * @returns DecayResult with statistics\n */\n async applyDecay(options: DecayOperationOptions = {}): Promise<DecayResult> {\n const startTime = Date.now();\n const graph = await this.storage.loadGraph();\n\n let entitiesProcessed = 0;\n let totalDecay = 0;\n let memoriesAtRisk = 0;\n\n const minImportance = options.minImportance ?? this.config.minImportance;\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n\n entitiesProcessed++;\n\n // Calculate effective importance\n const effectiveImportance = this.calculateEffectiveImportance(entity);\n const baseImportance = entity.importance ?? 5;\n\n // Track total decay (difference from base)\n const decayAmount = baseImportance > 0 ? 1 - effectiveImportance / baseImportance : 0;\n totalDecay += decayAmount;\n\n // Count at-risk memories (above min but significantly decayed)\n if (effectiveImportance < 1.0 && effectiveImportance >= minImportance) {\n memoriesAtRisk++;\n }\n }\n\n const processingTimeMs = Date.now() - startTime;\n\n return {\n entitiesProcessed,\n averageDecay: entitiesProcessed > 0 ? totalDecay / entitiesProcessed : 0,\n memoriesAtRisk,\n processingTimeMs,\n };\n }\n\n // ==================== Configuration Access ====================\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<DecayEngineConfig>> {\n return { ...this.config };\n }\n}\n","/**\n * Decay Scheduler\n *\n * Schedules periodic decay and forget operations for autonomous\n * memory management. Memories are periodically processed and\n * weak ones forgotten without manual intervention.\n *\n * @module agent/DecayScheduler\n */\n\nimport type { DecayResult, ForgetResult, ForgetOptions } from '../types/agent-memory.js';\nimport type { DecayEngine } from './DecayEngine.js';\n\n// Re-export for convenience\nexport type { DecayResult, ForgetResult } from '../types/agent-memory.js';\n\n/**\n * Configuration for scheduled decay.\n */\nexport interface DecaySchedulerConfig {\n /** Interval between decay runs in milliseconds (default: 1 hour) */\n decayIntervalMs?: number;\n /** Enable automatic forgetting after decay analysis */\n autoForget?: boolean;\n /** Options for forget operations when autoForget is enabled */\n forgetOptions?: ForgetOptions;\n /** Callback when decay analysis completes */\n onDecayComplete?: (result: DecayResult) => void;\n /** Callback when forget operation completes */\n onForgetComplete?: (result: ForgetResult) => void;\n /** Callback when an error occurs during a cycle */\n onError?: (error: Error) => void;\n}\n\n/**\n * Result of a manual decay cycle run.\n */\nexport interface DecayCycleResult {\n /** Decay analysis results */\n decay: DecayResult;\n /** Forget operation results (if autoForget enabled) */\n forget?: ForgetResult;\n}\n\n/**\n * Schedules periodic decay and forget operations.\n *\n * The DecayScheduler runs decay analysis at configurable intervals\n * and optionally forgets weak memories automatically. This enables\n * autonomous memory management mimicking natural forgetting processes.\n *\n * Key features:\n * - Configurable decay interval\n * - Optional auto-forget after decay\n * - Callbacks for monitoring decay/forget operations\n * - Manual cycle execution via runNow()\n *\n * @example\n * ```typescript\n * const scheduler = new DecayScheduler(decayEngine, {\n * decayIntervalMs: 60 * 60 * 1000, // Hourly\n * autoForget: true,\n * forgetOptions: {\n * effectiveImportanceThreshold: 0.1,\n * excludeTags: ['important', 'permanent'],\n * },\n * onDecayComplete: (result) => {\n * console.log(`Processed ${result.entitiesProcessed} entities`);\n * },\n * onForgetComplete: (result) => {\n * console.log(`Forgot ${result.memoriesForgotten} memories`);\n * },\n * });\n *\n * // Start scheduled decay\n * scheduler.start();\n *\n * // Later: stop scheduled decay\n * scheduler.stop();\n *\n * // Run a single cycle manually\n * const result = await scheduler.runNow();\n * ```\n */\nexport class DecayScheduler {\n private readonly decayEngine: DecayEngine;\n private readonly config: Required<\n Pick<DecaySchedulerConfig, 'decayIntervalMs' | 'autoForget'>\n > &\n Pick<\n DecaySchedulerConfig,\n 'forgetOptions' | 'onDecayComplete' | 'onForgetComplete' | 'onError'\n >;\n private intervalId?: ReturnType<typeof setInterval>;\n private running: boolean = false;\n\n constructor(decayEngine: DecayEngine, config: DecaySchedulerConfig = {}) {\n this.decayEngine = decayEngine;\n this.config = {\n decayIntervalMs: config.decayIntervalMs ?? 60 * 60 * 1000, // Default 1 hour\n autoForget: config.autoForget ?? false,\n forgetOptions: config.forgetOptions,\n onDecayComplete: config.onDecayComplete,\n onForgetComplete: config.onForgetComplete,\n onError: config.onError,\n };\n }\n\n /**\n * Start the scheduled decay process.\n *\n * Begins periodic execution of decay cycles at the configured interval.\n * Also runs one cycle immediately on start.\n *\n * Multiple calls to start() are idempotent - only the first starts the scheduler.\n */\n start(): void {\n if (this.running) return;\n\n this.running = true;\n this.intervalId = setInterval(\n () => this.runDecayCycle(),\n this.config.decayIntervalMs\n );\n\n // Run immediately on start\n this.runDecayCycle();\n }\n\n /**\n * Stop the scheduled decay process.\n *\n * Halts periodic execution. Any in-progress cycle will complete,\n * but no new cycles will be started.\n */\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n this.running = false;\n }\n\n /**\n * Check if scheduler is currently running.\n *\n * @returns True if the scheduler is active\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Get the configured decay interval.\n *\n * @returns Interval in milliseconds\n */\n getInterval(): number {\n return this.config.decayIntervalMs;\n }\n\n /**\n * Run a single decay cycle.\n *\n * Called automatically by the scheduler, but can also be\n * invoked manually. Runs decay analysis and optionally\n * forgets weak memories.\n *\n * @internal\n */\n private async runDecayCycle(): Promise<void> {\n try {\n // Run decay analysis\n const decayResult = await this.decayEngine.applyDecay();\n this.config.onDecayComplete?.(decayResult);\n\n // Run forget if enabled and configured\n if (this.config.autoForget && this.config.forgetOptions) {\n const forgetResult = await this.decayEngine.forgetWeakMemories(\n this.config.forgetOptions\n );\n this.config.onForgetComplete?.(forgetResult);\n }\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error instanceof Error ? error : new Error(String(error)));\n } else {\n // Default: log to console if no error handler provided\n console.error('Decay cycle error:', error);\n }\n }\n }\n\n /**\n * Run a decay cycle manually (on-demand).\n *\n * This method allows triggering a decay cycle outside of the\n * scheduled interval. Useful for:\n * - Testing\n * - User-initiated cleanup\n * - One-time decay operations\n *\n * @returns Results of the decay cycle\n *\n * @example\n * ```typescript\n * const result = await scheduler.runNow();\n * console.log(`Processed: ${result.decay.entitiesProcessed}`);\n * if (result.forget) {\n * console.log(`Forgot: ${result.forget.memoriesForgotten}`);\n * }\n * ```\n */\n async runNow(): Promise<DecayCycleResult> {\n const decay = await this.decayEngine.applyDecay();\n\n let forget: ForgetResult | undefined;\n if (this.config.autoForget && this.config.forgetOptions) {\n forget = await this.decayEngine.forgetWeakMemories(this.config.forgetOptions);\n }\n\n return { decay, forget };\n }\n}\n","/**\n * Summarization Service\n *\n * Provides text summarization using LLM providers or fallback algorithms.\n * Supports similarity detection, observation grouping, and abstraction levels.\n *\n * @module agent/SummarizationService\n */\n\n/**\n * Interface for summarization providers.\n */\nexport interface ISummarizationProvider {\n /** Summarize multiple texts into one */\n summarize(texts: string[]): Promise<string>;\n /** Check if provider is available */\n isAvailable(): boolean;\n}\n\n/**\n * Configuration for SummarizationService.\n */\nexport interface SummarizationConfig {\n /** Provider name ('openai', 'local', or 'none') */\n provider?: string;\n /** API key for LLM provider */\n apiKey?: string;\n /** Model to use for summarization */\n model?: string;\n /** Maximum tokens for summary output */\n maxTokens?: number;\n /** Default similarity threshold (0-1) */\n defaultSimilarityThreshold?: number;\n}\n\n/**\n * Result of grouping similar observations.\n */\nexport interface GroupingResult {\n /** Groups of similar observations */\n groups: string[][];\n /** Number of groups created */\n groupCount: number;\n /** Original observation count */\n originalCount: number;\n}\n\n/**\n * Service for summarizing text using LLM or fallback algorithms.\n *\n * SummarizationService provides text summarization capabilities with:\n * - LLM provider support (when configured)\n * - Fallback concatenation for local operation\n * - Text similarity calculation using TF-IDF\n * - Observation grouping by similarity\n *\n * @example\n * ```typescript\n * const service = new SummarizationService();\n *\n * // Summarize multiple observations\n * const summary = await service.summarize([\n * 'User likes Italian food',\n * 'User prefers Italian cuisine',\n * 'User enjoys pasta dishes'\n * ]);\n *\n * // Calculate similarity between texts\n * const similarity = service.calculateSimilarity(\n * 'User likes Italian food',\n * 'User prefers Italian cuisine'\n * );\n *\n * // Group similar observations\n * const groups = await service.groupSimilarObservations(\n * observations,\n * 0.8 // threshold\n * );\n * ```\n */\nexport class SummarizationService {\n private provider?: ISummarizationProvider;\n private readonly config: Required<SummarizationConfig>;\n\n constructor(config: SummarizationConfig = {}) {\n this.config = {\n provider: config.provider ?? 'none',\n apiKey: config.apiKey ?? '',\n model: config.model ?? '',\n maxTokens: config.maxTokens ?? 150,\n defaultSimilarityThreshold: config.defaultSimilarityThreshold ?? 0.8,\n };\n this.initProvider();\n }\n\n /**\n * Initialize LLM provider if configured.\n * @internal\n */\n private initProvider(): void {\n // Provider implementations would be registered here\n // For now, we use fallback summarization only\n if (\n this.config.provider === 'openai' &&\n this.config.apiKey\n ) {\n // OpenAI provider would be initialized here\n // this.provider = new OpenAISummarizer(this.config);\n }\n }\n\n // ==================== Summarization ====================\n\n /**\n * Summarize multiple texts into a single summary.\n *\n * Uses LLM provider if available, otherwise falls back to\n * algorithmic summarization.\n *\n * @param texts - Array of texts to summarize\n * @returns Combined summary\n */\n async summarize(texts: string[]): Promise<string> {\n if (texts.length === 0) return '';\n if (texts.length === 1) return texts[0];\n\n if (this.provider?.isAvailable()) {\n try {\n return await this.provider.summarize(texts);\n } catch {\n // Fall through to fallback\n }\n }\n\n return this.fallbackSummarize(texts);\n }\n\n /**\n * Fallback summarization using sentence extraction.\n *\n * Extracts unique sentences from all texts and combines them.\n *\n * @param texts - Array of texts to summarize\n * @returns Combined summary\n * @internal\n */\n private fallbackSummarize(texts: string[]): string {\n if (texts.length === 0) return '';\n if (texts.length === 1) return texts[0];\n\n // Extract unique sentences/phrases\n const sentences = new Set<string>();\n for (const text of texts) {\n // Split by sentence endings or newlines\n const parts = text\n .split(/[.!?]+|\\n/)\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n parts.forEach((p) => sentences.add(p));\n }\n\n // If we have too many sentences, take the most informative\n const sentenceArray = Array.from(sentences);\n if (sentenceArray.length <= 3) {\n return sentenceArray.join('. ') + '.';\n }\n\n // For longer groups, pick representative sentences\n // Take first, middle, and last to capture breadth\n const representative = [\n sentenceArray[0],\n sentenceArray[Math.floor(sentenceArray.length / 2)],\n sentenceArray[sentenceArray.length - 1],\n ];\n\n return representative.join('. ') + '.';\n }\n\n // ==================== Similarity Detection ====================\n\n /**\n * Calculate similarity between two texts using TF-IDF cosine similarity.\n *\n * @param text1 - First text\n * @param text2 - Second text\n * @returns Similarity score (0-1)\n */\n calculateSimilarity(text1: string, text2: string): number {\n const tokens1 = this.tokenize(text1);\n const tokens2 = this.tokenize(text2);\n\n if (tokens1.length === 0 || tokens2.length === 0) {\n return 0;\n }\n\n // Build vocabulary from both texts\n const allTokens = new Set([...tokens1, ...tokens2]);\n\n // Build term frequency vectors\n const vec1 = this.buildTFVector(tokens1, allTokens);\n const vec2 = this.buildTFVector(tokens2, allTokens);\n\n // Calculate cosine similarity\n return this.cosineSimilarity(vec1, vec2);\n }\n\n /**\n * Tokenize text into words.\n * @internal\n */\n private tokenize(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, '')\n .split(/\\s+/)\n .filter((t) => t.length > 0);\n }\n\n /**\n * Build term frequency vector.\n * @internal\n */\n private buildTFVector(tokens: string[], vocab: Set<string>): number[] {\n const freq = new Map<string, number>();\n for (const t of tokens) {\n freq.set(t, (freq.get(t) ?? 0) + 1);\n }\n return Array.from(vocab).map((t) => freq.get(t) ?? 0);\n }\n\n /**\n * Calculate cosine similarity between vectors.\n * @internal\n */\n private cosineSimilarity(vec1: number[], vec2: number[]): number {\n let dot = 0;\n let norm1 = 0;\n let norm2 = 0;\n\n for (let i = 0; i < vec1.length; i++) {\n dot += vec1[i] * vec2[i];\n norm1 += vec1[i] * vec1[i];\n norm2 += vec2[i] * vec2[i];\n }\n\n if (norm1 === 0 || norm2 === 0) return 0;\n return dot / (Math.sqrt(norm1) * Math.sqrt(norm2));\n }\n\n // ==================== Observation Grouping ====================\n\n /**\n * Group similar observations by similarity threshold.\n *\n * @param observations - Array of observations to group\n * @param threshold - Similarity threshold (0-1, default from config)\n * @returns Grouped observations\n */\n async groupSimilarObservations(\n observations: string[],\n threshold?: number\n ): Promise<GroupingResult> {\n const effectiveThreshold = threshold ?? this.config.defaultSimilarityThreshold;\n\n if (observations.length <= 1) {\n return {\n groups: observations.length === 1 ? [observations] : [],\n groupCount: observations.length,\n originalCount: observations.length,\n };\n }\n\n const groups: string[][] = [];\n const assigned = new Set<number>();\n\n for (let i = 0; i < observations.length; i++) {\n if (assigned.has(i)) continue;\n\n const group = [observations[i]];\n assigned.add(i);\n\n for (let j = i + 1; j < observations.length; j++) {\n if (assigned.has(j)) continue;\n\n const similarity = this.calculateSimilarity(\n observations[i],\n observations[j]\n );\n\n if (similarity >= effectiveThreshold) {\n group.push(observations[j]);\n assigned.add(j);\n }\n }\n\n groups.push(group);\n }\n\n return {\n groups,\n groupCount: groups.length,\n originalCount: observations.length,\n };\n }\n\n /**\n * Summarize grouped observations.\n *\n * @param groups - Groups of observations from groupSimilarObservations\n * @returns Array of summaries (one per group)\n */\n async summarizeGroups(groups: string[][]): Promise<string[]> {\n const summaries: string[] = [];\n\n for (const group of groups) {\n if (group.length === 1) {\n summaries.push(group[0]);\n } else {\n const summary = await this.summarize(group);\n summaries.push(summary);\n }\n }\n\n return summaries;\n }\n\n // ==================== Configuration Access ====================\n\n /**\n * Check if LLM provider is available.\n */\n isLLMAvailable(): boolean {\n return this.provider?.isAvailable() ?? false;\n }\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<SummarizationConfig>> {\n return { ...this.config };\n }\n\n /**\n * Register a summarization provider.\n *\n * @param provider - Provider to register\n */\n registerProvider(provider: ISummarizationProvider): void {\n this.provider = provider;\n }\n}\n","/**\n * Salience Engine\n *\n * Calculates context-aware memory relevance scores based on\n * recency, frequency, context, and novelty factors.\n *\n * @module agent/SalienceEngine\n */\n\nimport type { IGraphStorage } from '../types/types.js';\nimport type {\n AgentEntity,\n SalienceContext,\n ScoredEntity,\n SalienceComponents,\n} from '../types/agent-memory.js';\nimport { isAgentEntity } from '../types/agent-memory.js';\nimport { AccessTracker } from './AccessTracker.js';\nimport { DecayEngine } from './DecayEngine.js';\nimport { SummarizationService } from './SummarizationService.js';\n\n/**\n * Configuration for SalienceEngine.\n */\nexport interface SalienceEngineConfig {\n /** Weight for base importance (default: 0.25) */\n importanceWeight?: number;\n /** Weight for recency boost (default: 0.25) */\n recencyWeight?: number;\n /** Weight for frequency boost (default: 0.2) */\n frequencyWeight?: number;\n /** Weight for context relevance (default: 0.2) */\n contextWeight?: number;\n /** Weight for novelty bonus (default: 0.1) */\n noveltyWeight?: number;\n /** Recency decay hours (default: 24) */\n recencyDecayHours?: number;\n /** Boost factor for session match (default: 1.0) */\n sessionBoostFactor?: number;\n /** Boost factor for recent entities (default: 0.7) */\n recentEntityBoostFactor?: number;\n /** Enable TF-IDF similarity for task/query matching (default: true) */\n useSemanticSimilarity?: boolean;\n /** Threshold for observation uniqueness (default: 0.5) */\n uniquenessThreshold?: number;\n}\n\n/**\n * Calculates multi-factor salience scores for memories.\n *\n * The SalienceEngine combines multiple signals to produce a single\n * relevance score that accounts for both intrinsic memory importance\n * and contextual relevance:\n *\n * - Base importance (from DecayEngine effective importance)\n * - Recency boost (time since last access)\n * - Frequency boost (access count normalized)\n * - Context relevance (task/session/query matching)\n * - Novelty bonus (inversely related to recent access)\n *\n * @example\n * ```typescript\n * const engine = new SalienceEngine(storage, accessTracker, decayEngine);\n * const context: SalienceContext = { currentTask: 'booking', queryText: 'hotel' };\n * const scored = await engine.calculateSalience(entity, context);\n * console.log(`Salience: ${scored.salienceScore}`);\n * ```\n */\nexport class SalienceEngine {\n private readonly storage: IGraphStorage;\n private readonly accessTracker: AccessTracker;\n private readonly decayEngine: DecayEngine;\n private readonly summarizationService: SummarizationService;\n private readonly config: Required<SalienceEngineConfig>;\n\n constructor(\n storage: IGraphStorage,\n accessTracker: AccessTracker,\n decayEngine: DecayEngine,\n config: SalienceEngineConfig = {}\n ) {\n this.storage = storage;\n this.accessTracker = accessTracker;\n this.decayEngine = decayEngine;\n this.summarizationService = new SummarizationService();\n this.config = {\n importanceWeight: config.importanceWeight ?? 0.25,\n recencyWeight: config.recencyWeight ?? 0.25,\n frequencyWeight: config.frequencyWeight ?? 0.2,\n contextWeight: config.contextWeight ?? 0.2,\n noveltyWeight: config.noveltyWeight ?? 0.1,\n recencyDecayHours: config.recencyDecayHours ?? 24,\n sessionBoostFactor: config.sessionBoostFactor ?? 1.0,\n recentEntityBoostFactor: config.recentEntityBoostFactor ?? 0.7,\n useSemanticSimilarity: config.useSemanticSimilarity ?? true,\n uniquenessThreshold: config.uniquenessThreshold ?? 0.5,\n };\n }\n\n // ==================== Main Salience Calculation ====================\n\n /**\n * Calculate salience score for an entity in the given context.\n *\n * Combines multiple factors with configurable weights:\n * - Base importance: DecayEngine effective importance normalized to 0-1\n * - Recency: Exponential decay from last access time\n * - Frequency: Log-normalized access count relative to max\n * - Context: Text similarity to current task/session/query\n * - Novelty: Inverse of recency (rewards less recently accessed)\n *\n * @param entity - AgentEntity to calculate salience for\n * @param context - Context information for relevance scoring\n * @returns Scored entity with salience score and component breakdown\n */\n async calculateSalience(\n entity: AgentEntity,\n context: SalienceContext\n ): Promise<ScoredEntity> {\n // Calculate component scores (all normalized to 0-1)\n const baseImportance = this.calculateBaseImportance(entity);\n const recencyBoost = this.calculateRecencyBoost(entity, context);\n const frequencyBoost = await this.calculateFrequencyBoost(entity);\n const contextRelevance = this.calculateContextRelevance(entity, context);\n const noveltyBoost = this.calculateNoveltyBoost(entity, context);\n\n // Apply weights and sum\n const salienceScore =\n baseImportance * this.config.importanceWeight +\n recencyBoost * this.config.recencyWeight +\n frequencyBoost * this.config.frequencyWeight +\n contextRelevance * this.config.contextWeight +\n noveltyBoost * this.config.noveltyWeight;\n\n const components: SalienceComponents = {\n baseImportance,\n recencyBoost,\n frequencyBoost,\n contextRelevance,\n noveltyBoost,\n };\n\n return {\n entity,\n salienceScore,\n components,\n };\n }\n\n /**\n * Rank multiple entities by salience score.\n *\n * @param entities - Entities to rank\n * @param context - Context for relevance scoring\n * @returns Sorted array of scored entities (highest salience first)\n */\n async rankEntitiesBySalience(\n entities: AgentEntity[],\n context: SalienceContext\n ): Promise<ScoredEntity[]> {\n const scored = await Promise.all(\n entities.map((e) => this.calculateSalience(e, context))\n );\n return scored.sort((a, b) => b.salienceScore - a.salienceScore);\n }\n\n /**\n * Get top N entities by salience from storage.\n *\n * @param context - Context for relevance scoring\n * @param limit - Maximum number of entities to return (default: 10)\n * @returns Top entities sorted by salience\n */\n async getTopSalient(\n context: SalienceContext,\n limit: number = 10\n ): Promise<ScoredEntity[]> {\n const graph = await this.storage.loadGraph();\n const agentEntities = graph.entities.filter(isAgentEntity) as AgentEntity[];\n\n const ranked = await this.rankEntitiesBySalience(agentEntities, context);\n return ranked.slice(0, limit);\n }\n\n // ==================== Component Calculations ====================\n\n /**\n * Calculate base importance component.\n * Uses DecayEngine effective importance normalized to 0-1.\n *\n * @param entity - Entity to calculate for\n * @returns Score between 0 and 1\n */\n private calculateBaseImportance(entity: AgentEntity): number {\n // Use effective importance from decay engine\n const effective = this.decayEngine.calculateEffectiveImportance(entity);\n // Normalize to 0-1 (importance is on 0-10 scale)\n return Math.min(1, effective / 10);\n }\n\n /**\n * Calculate recency boost component.\n * Uses exponential decay from last access time.\n *\n * @param entity - Entity to calculate for\n * @param context - Context with temporal focus\n * @returns Score between 0 and 1\n */\n private calculateRecencyBoost(\n entity: AgentEntity,\n context: SalienceContext\n ): number {\n // Determine timestamp for recency calculation\n const lastAccess = entity.lastAccessedAt ?? entity.createdAt;\n if (!lastAccess) {\n return 0; // No timestamp = minimum recency\n }\n\n // Use static recency calculation\n const baseRecency = AccessTracker.calculateRecencyScoreFromTimestamp(\n lastAccess,\n this.config.recencyDecayHours\n );\n\n // Apply temporal focus adjustment\n if (context.temporalFocus === 'recent') {\n // Boost recent items more\n return Math.pow(baseRecency, 0.5); // Square root to boost high values\n } else if (context.temporalFocus === 'historical') {\n // Reduce recency impact for historical focus\n return Math.pow(baseRecency, 2); // Square to reduce high values\n }\n\n // Balanced (default)\n return baseRecency;\n }\n\n /**\n * Calculate frequency boost component.\n * Log-normalized access count relative to maximum.\n * Uses AccessTracker stats when available for more accurate counts.\n *\n * @param entity - Entity to calculate for\n * @returns Score between 0 and 1\n */\n private async calculateFrequencyBoost(entity: AgentEntity): Promise<number> {\n // Try to get access stats from tracker for more accurate count\n const stats = await this.accessTracker.getAccessStats(entity.name);\n const accessCount = stats.totalAccesses > 0 ? stats.totalAccesses : (entity.accessCount ?? 0);\n\n if (accessCount === 0) return 0;\n\n // Get max access count for normalization\n const maxAccess = await this.getMaxAccessCount();\n if (maxAccess === 0) return 0;\n\n // Use logarithmic scaling to prevent dominance by high-access entities\n // log(count + 1) / log(max + 1) gives a 0-1 range\n return Math.log(accessCount + 1) / Math.log(maxAccess + 1);\n }\n\n /**\n * Calculate context relevance component.\n * Matches entity against current task, session, and query using\n * TF-IDF similarity when enabled.\n *\n * @param entity - Entity to calculate for\n * @param context - Context to match against\n * @returns Score between 0 and 1\n */\n private calculateContextRelevance(\n entity: AgentEntity,\n context: SalienceContext\n ): number {\n let relevanceScore = 0;\n let factors = 0;\n\n // Task relevance using semantic similarity or keyword matching\n if (context.currentTask) {\n factors++;\n relevanceScore += this.calculateTaskRelevance(entity, context.currentTask);\n }\n\n // Session context scoring with configurable boost\n if (context.currentSession) {\n factors++;\n relevanceScore += this.calculateSessionRelevance(entity, context.currentSession);\n }\n\n // Query text matching using semantic similarity\n if (context.queryText) {\n factors++;\n relevanceScore += this.calculateQueryRelevance(entity, context.queryText);\n }\n\n // User intent matching with semantic similarity\n if (context.userIntent) {\n factors++;\n relevanceScore += this.calculateIntentRelevance(entity, context.userIntent);\n }\n\n // Recent entities matching with configurable boost\n if (context.recentEntities && context.recentEntities.length > 0) {\n factors++;\n if (context.recentEntities.includes(entity.name)) {\n relevanceScore += this.config.recentEntityBoostFactor;\n }\n }\n\n // Normalize by number of factors considered\n return factors > 0 ? relevanceScore / factors : 0;\n }\n\n /**\n * Calculate task relevance score.\n * Uses TF-IDF similarity when enabled, falls back to keyword matching.\n *\n * @param entity - Entity to score\n * @param taskDescription - Task to match against\n * @returns Score between 0 and 1\n */\n calculateTaskRelevance(entity: AgentEntity, taskDescription: string): number {\n // Exact task ID match\n if (entity.taskId === taskDescription) {\n return 1.0;\n }\n\n // Build entity text for comparison\n const entityText = this.buildEntityText(entity);\n\n if (this.config.useSemanticSimilarity) {\n // Use TF-IDF cosine similarity\n return this.summarizationService.calculateSimilarity(entityText, taskDescription);\n }\n\n // Fallback to keyword matching\n const taskLower = taskDescription.toLowerCase();\n if (entity.name.toLowerCase().includes(taskLower)) {\n return 0.8;\n }\n if (entity.observations?.some((o) => o.toLowerCase().includes(taskLower))) {\n return 0.5;\n }\n\n return 0;\n }\n\n /**\n * Calculate session relevance score.\n * Applies configurable boost factor for session matches.\n *\n * @param entity - Entity to score\n * @param sessionId - Session to match\n * @returns Score between 0 and 1\n */\n calculateSessionRelevance(entity: AgentEntity, sessionId: string): number {\n if (entity.sessionId === sessionId) {\n return this.config.sessionBoostFactor;\n }\n return 0;\n }\n\n /**\n * Calculate query text relevance score.\n * Uses TF-IDF similarity when enabled for semantic matching.\n *\n * @param entity - Entity to score\n * @param queryText - Query to match\n * @returns Score between 0 and 1\n */\n calculateQueryRelevance(entity: AgentEntity, queryText: string): number {\n // Build entity text for comparison\n const entityText = this.buildEntityText(entity);\n\n if (this.config.useSemanticSimilarity) {\n // Use TF-IDF cosine similarity for semantic matching\n return this.summarizationService.calculateSimilarity(entityText, queryText);\n }\n\n // Fallback to keyword matching\n const queryLower = queryText.toLowerCase();\n if (entity.name.toLowerCase().includes(queryLower)) {\n return 1.0;\n }\n if (entity.entityType.toLowerCase().includes(queryLower)) {\n return 0.7;\n }\n if (entity.observations?.some((o) => o.toLowerCase().includes(queryLower))) {\n return 0.5;\n }\n\n return 0;\n }\n\n /**\n * Calculate user intent relevance score.\n * Uses semantic similarity to match entity content against intent.\n *\n * @param entity - Entity to score\n * @param userIntent - Intent to match\n * @returns Score between 0 and 1\n */\n calculateIntentRelevance(entity: AgentEntity, userIntent: string): number {\n const entityText = this.buildEntityText(entity);\n\n if (this.config.useSemanticSimilarity) {\n return this.summarizationService.calculateSimilarity(entityText, userIntent);\n }\n\n // Fallback to keyword matching\n const intentLower = userIntent.toLowerCase();\n if (entity.observations?.some((o) => o.toLowerCase().includes(intentLower))) {\n return 0.8;\n }\n\n return 0;\n }\n\n /**\n * Build searchable text from entity for similarity comparison.\n *\n * @param entity - Entity to extract text from\n * @returns Combined text from entity fields\n */\n private buildEntityText(entity: AgentEntity): string {\n const parts: string[] = [\n entity.name,\n entity.entityType,\n ...(entity.observations ?? []),\n ];\n return parts.join(' ');\n }\n\n /**\n * Calculate novelty boost component.\n * Rewards entities that haven't been accessed recently and have unique observations.\n *\n * Novelty factors:\n * - Inverse access frequency (rare = more novel)\n * - Time since last access (long unaccessed = novel)\n * - Unique observations ratio (unique content = novel)\n *\n * @param entity - Entity to calculate for\n * @param context - Context with temporal focus\n * @returns Score between 0 and 1\n */\n private calculateNoveltyBoost(\n entity: AgentEntity,\n context: SalienceContext\n ): number {\n // Factor 1: Time-based novelty (inverse of recency)\n const lastAccess = entity.lastAccessedAt ?? entity.createdAt;\n let timeNovelty: number;\n if (!lastAccess) {\n timeNovelty = 1.0; // Never accessed = maximum novelty\n } else {\n const recency = AccessTracker.calculateRecencyScoreFromTimestamp(\n lastAccess,\n this.config.recencyDecayHours\n );\n timeNovelty = 1 - recency;\n }\n\n // Factor 2: Access frequency novelty (rare = more novel)\n const accessCount = entity.accessCount ?? 0;\n const frequencyNovelty = accessCount === 0 ? 1.0 : 1 / (1 + Math.log(accessCount + 1));\n\n // Factor 3: Observation uniqueness\n const uniquenessScore = this.calculateObservationUniqueness(entity);\n\n // Combine factors (weighted average)\n let novelty = (timeNovelty * 0.5) + (frequencyNovelty * 0.3) + (uniquenessScore * 0.2);\n\n // Adjust based on temporal focus\n if (context.temporalFocus === 'historical') {\n // Historical focus increases novelty importance\n novelty = Math.pow(novelty, 0.5); // Square root boosts mid values\n } else if (context.temporalFocus === 'recent') {\n // Recent focus decreases novelty importance\n novelty = Math.pow(novelty, 2); // Square reduces mid values\n }\n\n // Reduce novelty if entity is in recent context\n if (context.recentEntities?.includes(entity.name)) {\n novelty *= 0.5;\n }\n\n return Math.min(1, novelty);\n }\n\n /**\n * Calculate observation uniqueness score.\n * Measures how unique the entity's observations are compared to each other.\n * Higher uniqueness = more novel content.\n *\n * @param entity - Entity to evaluate\n * @returns Score between 0 and 1\n */\n private calculateObservationUniqueness(entity: AgentEntity): number {\n const observations = entity.observations ?? [];\n if (observations.length <= 1) {\n return 1.0; // Single or no observations = maximally unique\n }\n\n // Calculate average similarity between observations\n let totalSimilarity = 0;\n let comparisons = 0;\n\n for (let i = 0; i < observations.length; i++) {\n for (let j = i + 1; j < observations.length; j++) {\n const similarity = this.summarizationService.calculateSimilarity(\n observations[i],\n observations[j]\n );\n totalSimilarity += similarity;\n comparisons++;\n }\n }\n\n if (comparisons === 0) {\n return 1.0;\n }\n\n // Average similarity - higher similarity = lower uniqueness\n const avgSimilarity = totalSimilarity / comparisons;\n\n // Invert: low similarity = high uniqueness\n return 1 - avgSimilarity;\n }\n\n // ==================== Helper Methods ====================\n\n /**\n * Get the maximum access count across all entities.\n * Used for normalizing frequency scores.\n */\n private async getMaxAccessCount(): Promise<number> {\n const graph = await this.storage.loadGraph();\n let max = 0;\n\n for (const entity of graph.entities) {\n if (isAgentEntity(entity)) {\n const count = (entity as AgentEntity).accessCount ?? 0;\n if (count > max) max = count;\n }\n }\n\n return max;\n }\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<SalienceEngineConfig>> {\n return { ...this.config };\n }\n\n /**\n * Get most salient entities using heap-based selection.\n * More efficient than getTopSalient for large datasets with small limit.\n * Time complexity: O(n log k) where n is total entities, k is limit.\n *\n * @param context - Context for relevance scoring\n * @param limit - Maximum number of entities to return (default: 10)\n * @returns Top entities sorted by salience (descending)\n */\n async getMostSalient(\n context: SalienceContext,\n limit: number = 10\n ): Promise<ScoredEntity[]> {\n const graph = await this.storage.loadGraph();\n const agentEntities = graph.entities.filter(isAgentEntity) as AgentEntity[];\n\n if (agentEntities.length === 0) {\n return [];\n }\n\n // For small datasets or large limits, use simple sort\n if (agentEntities.length <= limit * 3) {\n return this.getTopSalient(context, limit);\n }\n\n // Use min-heap for O(n log k) selection\n const heap: ScoredEntity[] = [];\n\n for (const entity of agentEntities) {\n const scored = await this.calculateSalience(entity, context);\n\n if (heap.length < limit) {\n // Heap not full - insert and maintain heap property\n this.heapInsert(heap, scored);\n } else if (scored.salienceScore > heap[0].salienceScore) {\n // New score is higher than minimum in heap - replace minimum\n heap[0] = scored;\n this.heapSiftDown(heap, 0);\n }\n }\n\n // Extract sorted results (highest first)\n const result: ScoredEntity[] = [];\n while (heap.length > 0) {\n result.unshift(this.heapExtractMin(heap)!);\n }\n\n return result;\n }\n\n /**\n * Calculate similarity between two entities for diversity checking.\n *\n * @param entity1 - First entity\n * @param entity2 - Second entity\n * @returns Similarity score between 0 and 1\n */\n calculateEntitySimilarity(entity1: AgentEntity, entity2: AgentEntity): number {\n const text1 = this.buildEntityText(entity1);\n const text2 = this.buildEntityText(entity2);\n return this.summarizationService.calculateSimilarity(text1, text2);\n }\n\n // ==================== Heap Operations ====================\n\n /**\n * Insert into min-heap (by salience score).\n * @internal\n */\n private heapInsert(heap: ScoredEntity[], item: ScoredEntity): void {\n heap.push(item);\n this.heapSiftUp(heap, heap.length - 1);\n }\n\n /**\n * Extract minimum from heap.\n * @internal\n */\n private heapExtractMin(heap: ScoredEntity[]): ScoredEntity | undefined {\n if (heap.length === 0) return undefined;\n\n const min = heap[0];\n const last = heap.pop()!;\n\n if (heap.length > 0) {\n heap[0] = last;\n this.heapSiftDown(heap, 0);\n }\n\n return min;\n }\n\n /**\n * Sift up to maintain heap property.\n * @internal\n */\n private heapSiftUp(heap: ScoredEntity[], index: number): void {\n while (index > 0) {\n const parentIndex = Math.floor((index - 1) / 2);\n if (heap[index].salienceScore >= heap[parentIndex].salienceScore) {\n break;\n }\n [heap[index], heap[parentIndex]] = [heap[parentIndex], heap[index]];\n index = parentIndex;\n }\n }\n\n /**\n * Sift down to maintain heap property.\n * @internal\n */\n private heapSiftDown(heap: ScoredEntity[], index: number): void {\n const length = heap.length;\n\n while (true) {\n const leftChild = 2 * index + 1;\n const rightChild = 2 * index + 2;\n let smallest = index;\n\n if (leftChild < length && heap[leftChild].salienceScore < heap[smallest].salienceScore) {\n smallest = leftChild;\n }\n if (rightChild < length && heap[rightChild].salienceScore < heap[smallest].salienceScore) {\n smallest = rightChild;\n }\n\n if (smallest === index) break;\n\n [heap[index], heap[smallest]] = [heap[smallest], heap[index]];\n index = smallest;\n }\n }\n}\n","/**\n * Context Window Manager\n *\n * Manages memory retrieval within LLM token budget constraints.\n * Uses salience scoring to prioritize the most relevant memories.\n *\n * @module agent/ContextWindowManager\n */\n\nimport type { IGraphStorage } from '../types/types.js';\nimport type {\n AgentEntity,\n SalienceContext,\n ContextRetrievalOptions,\n ContextPackage,\n TokenBreakdown,\n ExcludedEntity,\n ScoredEntity,\n} from '../types/agent-memory.js';\nimport { isAgentEntity } from '../types/agent-memory.js';\nimport { SalienceEngine } from './SalienceEngine.js';\n\n/**\n * Configuration for ContextWindowManager.\n */\nexport interface ContextWindowManagerConfig {\n /** Default maximum tokens (default: 4000) */\n defaultMaxTokens?: number;\n /** Token estimation multiplier (default: 1.3, roughly words to tokens) */\n tokenMultiplier?: number;\n /** Reserve buffer for system/formatting (default: 100) */\n reserveBuffer?: number;\n /** Maximum entities to consider (default: 1000) */\n maxEntitiesToConsider?: number;\n /** Budget percentage for working memory (default: 0.3 = 30%) */\n workingBudgetPct?: number;\n /** Budget percentage for episodic memory (default: 0.3 = 30%) */\n episodicBudgetPct?: number;\n /** Budget percentage for semantic memory (default: 0.4 = 40%) */\n semanticBudgetPct?: number;\n /** Number of recent sessions to include for episodic (default: 3) */\n recentSessionCount?: number;\n /** Similarity threshold for diversity enforcement (default: 0.8) */\n diversityThreshold?: number;\n /** Enable diversity enforcement (default: true) */\n enforceDiversity?: boolean;\n}\n\n/**\n * Spillover tracking result.\n */\nexport interface SpilloverResult {\n /** Entities that didn't fit in context */\n spilledEntities: ExcludedEntity[];\n /** Suggestions for pagination/follow-up */\n suggestions: string[];\n /** Token count of spilled content */\n spilledTokens: number;\n /** Next page cursor for pagination */\n nextPageCursor?: string;\n}\n\n/**\n * Manages memory retrieval within LLM token budget constraints.\n *\n * The ContextWindowManager combines salience scoring with token estimation\n * to select the most relevant memories that fit within a given token budget.\n * It uses a greedy algorithm that maximizes total salience while respecting\n * the budget constraint.\n *\n * @example\n * ```typescript\n * const manager = new ContextWindowManager(storage, salienceEngine);\n * const result = await manager.retrieveForContext({\n * maxTokens: 4000,\n * context: { currentTask: 'booking' },\n * includeWorkingMemory: true,\n * mustInclude: ['user_preferences'],\n * });\n * console.log(`Retrieved ${result.memories.length} memories using ${result.totalTokens} tokens`);\n * ```\n */\nexport class ContextWindowManager {\n private readonly storage: IGraphStorage;\n private readonly salienceEngine: SalienceEngine;\n private readonly config: Required<ContextWindowManagerConfig>;\n\n constructor(\n storage: IGraphStorage,\n salienceEngine: SalienceEngine,\n config: ContextWindowManagerConfig = {}\n ) {\n this.storage = storage;\n this.salienceEngine = salienceEngine;\n this.config = {\n defaultMaxTokens: config.defaultMaxTokens ?? 4000,\n tokenMultiplier: config.tokenMultiplier ?? 1.3,\n reserveBuffer: config.reserveBuffer ?? 100,\n maxEntitiesToConsider: config.maxEntitiesToConsider ?? 1000,\n workingBudgetPct: config.workingBudgetPct ?? 0.3,\n episodicBudgetPct: config.episodicBudgetPct ?? 0.3,\n semanticBudgetPct: config.semanticBudgetPct ?? 0.4,\n recentSessionCount: config.recentSessionCount ?? 3,\n diversityThreshold: config.diversityThreshold ?? 0.8,\n enforceDiversity: config.enforceDiversity ?? true,\n };\n }\n\n // ==================== Token Estimation ====================\n\n /**\n * Estimate token count for an entity.\n *\n * Uses a simple heuristic: word count * multiplier (default 1.3).\n * Includes name, entityType, and all observations.\n *\n * @param entity - Entity to estimate tokens for\n * @returns Estimated token count\n */\n estimateTokens(entity: AgentEntity): number {\n const parts: string[] = [\n entity.name,\n entity.entityType,\n ...(entity.observations ?? []),\n ];\n\n // Add metadata fields if present\n if (entity.memoryType) parts.push(entity.memoryType);\n if (entity.sessionId) parts.push(entity.sessionId);\n if (entity.taskId) parts.push(entity.taskId);\n\n const text = parts.join(' ');\n const wordCount = text.split(/\\s+/).filter((w) => w.length > 0).length;\n\n return Math.ceil(wordCount * this.config.tokenMultiplier);\n }\n\n /**\n * Estimate total tokens for multiple entities.\n *\n * @param entities - Entities to estimate\n * @returns Total estimated tokens\n */\n estimateTotalTokens(entities: AgentEntity[]): number {\n return entities.reduce((sum, e) => sum + this.estimateTokens(e), 0);\n }\n\n // ==================== Prioritization ====================\n\n /**\n * Prioritize entities to fit within token budget.\n *\n * Uses a greedy algorithm that selects entities by salience/token ratio\n * to maximize total salience within the budget constraint.\n *\n * @param entities - Candidate entities\n * @param budget - Maximum tokens allowed\n * @param context - Salience context for scoring\n * @param mustInclude - Entity names that must be included\n * @returns Prioritized entities and excluded entities\n */\n async prioritize(\n entities: AgentEntity[],\n budget: number,\n context: SalienceContext = {},\n mustInclude: string[] = []\n ): Promise<{ selected: ScoredEntity[]; excluded: ExcludedEntity[] }> {\n const mustIncludeSet = new Set(mustInclude);\n\n // Score all entities\n const scored = await this.salienceEngine.rankEntitiesBySalience(entities, context);\n\n // Calculate tokens for each\n const withTokens = scored.map((s) => ({\n ...s,\n tokens: this.estimateTokens(s.entity),\n }));\n\n // Separate must-include from optional\n const required = withTokens.filter((s) => mustIncludeSet.has(s.entity.name));\n const optional = withTokens.filter((s) => !mustIncludeSet.has(s.entity.name));\n\n // Calculate salience/token efficiency for optional entities\n const withEfficiency = optional.map((s) => ({\n ...s,\n efficiency: s.tokens > 0 ? s.salienceScore / s.tokens : 0,\n }));\n\n // Sort by efficiency (descending) for greedy selection\n withEfficiency.sort((a, b) => b.efficiency - a.efficiency);\n\n // Start with must-include entities\n const selected: ScoredEntity[] = required.map((r) => ({\n entity: r.entity,\n salienceScore: r.salienceScore,\n components: r.components,\n }));\n let usedTokens = required.reduce((sum, r) => sum + r.tokens, 0);\n\n const excluded: ExcludedEntity[] = [];\n\n // Check if must-include already exceeds budget\n if (usedTokens > budget) {\n // Mark some must-include as excluded (but still include them)\n // This is a warning case - must-include takes priority\n }\n\n // Greedily add optional entities by efficiency\n for (const candidate of withEfficiency) {\n if (usedTokens + candidate.tokens <= budget) {\n selected.push({\n entity: candidate.entity,\n salienceScore: candidate.salienceScore,\n components: candidate.components,\n });\n usedTokens += candidate.tokens;\n } else {\n excluded.push({\n entity: candidate.entity,\n reason: 'budget_exceeded',\n tokens: candidate.tokens,\n salience: candidate.salienceScore,\n });\n }\n }\n\n return { selected, excluded };\n }\n\n // ==================== Main Retrieval Method ====================\n\n /**\n * Retrieve memories for context within token budget.\n *\n * @param options - Retrieval options\n * @returns Context package with memories, token info, and suggestions\n */\n async retrieveForContext(options: ContextRetrievalOptions): Promise<ContextPackage> {\n const {\n maxTokens = this.config.defaultMaxTokens,\n context = {},\n includeWorkingMemory = true,\n includeEpisodicRecent = true,\n includeSemanticRelevant = true,\n mustInclude = [],\n minSalience = 0,\n } = options;\n\n // Effective budget after reserve\n const effectiveBudget = maxTokens - this.config.reserveBuffer;\n\n // Load all agent entities\n const graph = await this.storage.loadGraph();\n const allEntities = graph.entities.filter(isAgentEntity) as AgentEntity[];\n\n // Filter by memory type based on options\n let candidates = allEntities.filter((e) => {\n if (!includeWorkingMemory && e.memoryType === 'working') return false;\n if (!includeEpisodicRecent && e.memoryType === 'episodic') return false;\n if (!includeSemanticRelevant && e.memoryType === 'semantic') return false;\n return true;\n });\n\n // Limit candidates for performance\n if (candidates.length > this.config.maxEntitiesToConsider) {\n // Pre-filter by salience to reduce candidates\n const preScored = await this.salienceEngine.rankEntitiesBySalience(candidates, context);\n candidates = preScored\n .slice(0, this.config.maxEntitiesToConsider)\n .map((s) => s.entity);\n }\n\n // Prioritize within budget\n const { selected, excluded } = await this.prioritize(\n candidates,\n effectiveBudget,\n context,\n mustInclude\n );\n\n // Filter by minimum salience\n const filtered = selected.filter((s) => s.salienceScore >= minSalience);\n const lowSalienceExcluded = selected\n .filter((s) => s.salienceScore < minSalience)\n .map((s) => ({\n entity: s.entity,\n reason: 'low_salience' as const,\n tokens: this.estimateTokens(s.entity),\n salience: s.salienceScore,\n }));\n\n // Calculate breakdown by memory type\n const breakdown = this.calculateBreakdown(filtered, mustInclude);\n\n // Generate suggestions for excluded high-salience entities\n const suggestions = this.generateSuggestions(excluded);\n\n return {\n memories: filtered.map((s) => s.entity),\n totalTokens: this.estimateTotalTokens(filtered.map((s) => s.entity)),\n breakdown,\n excluded: [...excluded, ...lowSalienceExcluded],\n suggestions,\n };\n }\n\n // ==================== Type-Specific Retrieval ====================\n\n /**\n * Retrieve working memory entities for a session.\n *\n * @param sessionId - Session to retrieve working memory for\n * @param budget - Token budget for working memory\n * @param context - Salience context\n * @returns Working memory entities within budget\n */\n async retrieveWorkingMemory(\n sessionId: string | undefined,\n budget: number,\n context: SalienceContext = {}\n ): Promise<{ entities: AgentEntity[]; tokens: number }> {\n const graph = await this.storage.loadGraph();\n let candidates = graph.entities\n .filter(isAgentEntity)\n .filter((e) => (e as AgentEntity).memoryType === 'working') as AgentEntity[];\n\n // Filter by session if provided\n if (sessionId) {\n candidates = candidates.filter((e) => e.sessionId === sessionId);\n }\n\n // Score and select within budget\n const scored = await this.salienceEngine.rankEntitiesBySalience(candidates, context);\n const selected: AgentEntity[] = [];\n let usedTokens = 0;\n\n for (const s of scored) {\n const tokens = this.estimateTokens(s.entity);\n if (usedTokens + tokens <= budget) {\n selected.push(s.entity);\n usedTokens += tokens;\n }\n }\n\n return { entities: selected, tokens: usedTokens };\n }\n\n /**\n * Retrieve recent episodic memories.\n *\n * @param budget - Token budget for episodic memory\n * @param context - Salience context\n * @returns Recent episodic entities within budget\n */\n async retrieveEpisodicRecent(\n budget: number,\n context: SalienceContext = {}\n ): Promise<{ entities: AgentEntity[]; tokens: number }> {\n const graph = await this.storage.loadGraph();\n const episodic = graph.entities\n .filter(isAgentEntity)\n .filter((e) => (e as AgentEntity).memoryType === 'episodic') as AgentEntity[];\n\n // Sort by creation time (most recent first)\n const sorted = episodic.sort((a, b) => {\n const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return timeB - timeA;\n });\n\n // Get unique sessions from recent memories\n const recentSessions = new Set<string>();\n for (const e of sorted) {\n if (e.sessionId) {\n recentSessions.add(e.sessionId);\n if (recentSessions.size >= this.config.recentSessionCount) break;\n }\n }\n\n // Filter to recent sessions\n const candidates = recentSessions.size > 0\n ? sorted.filter((e) => !e.sessionId || recentSessions.has(e.sessionId))\n : sorted;\n\n // Score and select within budget\n const scored = await this.salienceEngine.rankEntitiesBySalience(candidates, context);\n const selected: AgentEntity[] = [];\n let usedTokens = 0;\n\n for (const s of scored) {\n const tokens = this.estimateTokens(s.entity);\n if (usedTokens + tokens <= budget) {\n selected.push(s.entity);\n usedTokens += tokens;\n }\n }\n\n return { entities: selected, tokens: usedTokens };\n }\n\n /**\n * Retrieve semantically relevant memories.\n *\n * @param budget - Token budget for semantic memory\n * @param context - Salience context\n * @returns Relevant semantic entities within budget\n */\n async retrieveSemanticRelevant(\n budget: number,\n context: SalienceContext = {}\n ): Promise<{ entities: AgentEntity[]; tokens: number }> {\n const graph = await this.storage.loadGraph();\n const semantic = graph.entities\n .filter(isAgentEntity)\n .filter((e) => (e as AgentEntity).memoryType === 'semantic') as AgentEntity[];\n\n // Score by salience (which includes context relevance)\n const scored = await this.salienceEngine.rankEntitiesBySalience(semantic, context);\n const selected: AgentEntity[] = [];\n let usedTokens = 0;\n\n for (const s of scored) {\n const tokens = this.estimateTokens(s.entity);\n if (usedTokens + tokens <= budget) {\n selected.push(s.entity);\n usedTokens += tokens;\n }\n }\n\n return { entities: selected, tokens: usedTokens };\n }\n\n /**\n * Retrieve must-include entities with budget warning.\n *\n * @param names - Entity names to include\n * @param budget - Available budget\n * @returns Entities, tokens used, and any warnings\n */\n async retrieveMustInclude(\n names: string[],\n budget: number\n ): Promise<{ entities: AgentEntity[]; tokens: number; warnings: string[] }> {\n const warnings: string[] = [];\n const entities: AgentEntity[] = [];\n let totalTokens = 0;\n\n for (const name of names) {\n const entity = this.storage.getEntityByName(name);\n if (entity && isAgentEntity(entity)) {\n const agentEntity = entity as AgentEntity;\n const tokens = this.estimateTokens(agentEntity);\n entities.push(agentEntity);\n totalTokens += tokens;\n } else {\n warnings.push(`Must-include entity '${name}' not found`);\n }\n }\n\n if (totalTokens > budget) {\n warnings.push(\n `Must-include entities (${totalTokens} tokens) exceed available budget (${budget} tokens)`\n );\n }\n\n return { entities, tokens: totalTokens, warnings };\n }\n\n /**\n * Retrieve memories with budget allocation across memory types.\n *\n * Allocates budget percentages to each memory type:\n * - Working: 30% (configurable)\n * - Episodic: 30% (configurable)\n * - Semantic: 40% (configurable)\n *\n * Must-include entities are subtracted from total first.\n *\n * @param options - Retrieval options\n * @returns Context package with allocated retrieval\n */\n async retrieveWithBudgetAllocation(\n options: ContextRetrievalOptions\n ): Promise<ContextPackage> {\n const {\n maxTokens = this.config.defaultMaxTokens,\n context = {},\n includeWorkingMemory = true,\n includeEpisodicRecent = true,\n includeSemanticRelevant = true,\n mustInclude = [],\n minSalience = 0,\n } = options;\n\n const effectiveBudget = maxTokens - this.config.reserveBuffer;\n const allSuggestions: string[] = [];\n const allExcluded: ExcludedEntity[] = [];\n\n // Handle must-include first\n const mustIncludeResult = await this.retrieveMustInclude(mustInclude, effectiveBudget);\n allSuggestions.push(...mustIncludeResult.warnings);\n\n // Remaining budget after must-include\n const remainingBudget = Math.max(0, effectiveBudget - mustIncludeResult.tokens);\n\n // Calculate allocated budgets\n const workingBudget = includeWorkingMemory\n ? Math.floor(remainingBudget * this.config.workingBudgetPct)\n : 0;\n const episodicBudget = includeEpisodicRecent\n ? Math.floor(remainingBudget * this.config.episodicBudgetPct)\n : 0;\n const semanticBudget = includeSemanticRelevant\n ? Math.floor(remainingBudget * this.config.semanticBudgetPct)\n : 0;\n\n // Retrieve from each source\n const workingResult = includeWorkingMemory\n ? await this.retrieveWorkingMemory(context.currentSession, workingBudget, context)\n : { entities: [], tokens: 0 };\n\n const episodicResult = includeEpisodicRecent\n ? await this.retrieveEpisodicRecent(episodicBudget, context)\n : { entities: [], tokens: 0 };\n\n const semanticResult = includeSemanticRelevant\n ? await this.retrieveSemanticRelevant(semanticBudget, context)\n : { entities: [], tokens: 0 };\n\n // Combine all memories\n const allMemories = [\n ...mustIncludeResult.entities,\n ...workingResult.entities,\n ...episodicResult.entities,\n ...semanticResult.entities,\n ];\n\n // Deduplicate by name (must-include takes priority)\n const seen = new Set<string>();\n const deduped: AgentEntity[] = [];\n for (const e of allMemories) {\n if (!seen.has(e.name)) {\n seen.add(e.name);\n deduped.push(e);\n }\n }\n\n // Filter by minimum salience\n const scored = await this.salienceEngine.rankEntitiesBySalience(deduped, context);\n const mustIncludeSet = new Set(mustInclude);\n const filtered = scored.filter(\n (s) => s.salienceScore >= minSalience || mustIncludeSet.has(s.entity.name)\n );\n\n // Track low salience exclusions\n const lowSalienceExcluded = scored\n .filter((s) => s.salienceScore < minSalience && !mustIncludeSet.has(s.entity.name))\n .map((s) => ({\n entity: s.entity,\n reason: 'low_salience' as const,\n tokens: this.estimateTokens(s.entity),\n salience: s.salienceScore,\n }));\n allExcluded.push(...lowSalienceExcluded);\n\n // Calculate breakdown\n const breakdown: TokenBreakdown = {\n working: workingResult.tokens,\n episodic: episodicResult.tokens,\n semantic: semanticResult.tokens,\n procedural: 0,\n mustInclude: mustIncludeResult.tokens,\n };\n\n return {\n memories: filtered.map((s) => s.entity),\n totalTokens: this.estimateTotalTokens(filtered.map((s) => s.entity)),\n breakdown,\n excluded: allExcluded,\n suggestions: allSuggestions,\n };\n }\n\n // ==================== Helper Methods ====================\n\n /**\n * Calculate token breakdown by memory type.\n */\n private calculateBreakdown(\n entities: ScoredEntity[],\n mustInclude: string[]\n ): TokenBreakdown {\n const mustIncludeSet = new Set(mustInclude);\n const breakdown: TokenBreakdown = {\n working: 0,\n episodic: 0,\n semantic: 0,\n procedural: 0,\n mustInclude: 0,\n };\n\n for (const scored of entities) {\n const tokens = this.estimateTokens(scored.entity);\n\n if (mustIncludeSet.has(scored.entity.name)) {\n breakdown.mustInclude += tokens;\n } else {\n const memType = scored.entity.memoryType ?? 'working';\n if (memType in breakdown) {\n breakdown[memType as keyof Omit<TokenBreakdown, 'mustInclude'>] += tokens;\n }\n }\n }\n\n return breakdown;\n }\n\n /**\n * Generate suggestions for high-salience excluded entities.\n */\n private generateSuggestions(excluded: ExcludedEntity[]): string[] {\n const suggestions: string[] = [];\n\n // Find high-salience excluded entities\n const highSalience = excluded\n .filter((e) => (e.salience ?? 0) > 0.5)\n .sort((a, b) => (b.salience ?? 0) - (a.salience ?? 0))\n .slice(0, 3);\n\n for (const e of highSalience) {\n suggestions.push(\n `Consider including '${e.entity.name}' (salience: ${(e.salience ?? 0).toFixed(2)}, tokens: ${e.tokens}) if budget increases`\n );\n }\n\n if (excluded.length > 5) {\n suggestions.push(\n `${excluded.length} entities excluded due to budget constraints`\n );\n }\n\n return suggestions;\n }\n\n // ==================== Spillover Handling ====================\n\n /**\n * Handle spillover when content exceeds budget.\n * Tracks excluded entities, generates suggestions, and provides pagination cursor.\n *\n * @param excluded - Entities that were excluded\n * @param context - Salience context for prioritization\n * @param pageSize - Number of entities per page (default: 10)\n * @returns Spillover result with pagination support\n */\n handleSpillover(\n excluded: ExcludedEntity[],\n _context: SalienceContext = {},\n pageSize: number = 10\n ): SpilloverResult {\n // Sort by salience for priority preservation\n const sorted = [...excluded].sort(\n (a, b) => (b.salience ?? 0) - (a.salience ?? 0)\n );\n\n // Calculate total spillover\n const spilledTokens = sorted.reduce((sum, e) => sum + e.tokens, 0);\n\n // Generate pagination cursor based on lowest salience entity in current context\n const nextPageCursor = sorted.length > pageSize\n ? this.createPageCursor(sorted[pageSize - 1])\n : undefined;\n\n // Generate suggestions for follow-up\n const suggestions = this.generateSpilloverSuggestions(sorted, spilledTokens);\n\n return {\n spilledEntities: sorted.slice(0, pageSize),\n suggestions,\n spilledTokens,\n nextPageCursor,\n };\n }\n\n /**\n * Retrieve next page of spillover content.\n *\n * @param cursor - Pagination cursor from previous spillover\n * @param budget - Token budget for this page\n * @param context - Salience context\n * @returns Next page of entities and updated cursor\n */\n async retrieveSpilloverPage(\n cursor: string,\n budget: number,\n context: SalienceContext = {}\n ): Promise<{ entities: AgentEntity[]; nextCursor?: string; tokens: number }> {\n const { maxSalience } = this.parsePageCursor(cursor);\n\n // Get all entities below the cursor salience\n const graph = await this.storage.loadGraph();\n const allEntities = graph.entities.filter(isAgentEntity) as AgentEntity[];\n\n // Score and filter by cursor\n const scored = await this.salienceEngine.rankEntitiesBySalience(allEntities, context);\n const belowCursor = scored.filter((s) => s.salienceScore < maxSalience);\n\n // Select within budget\n const selected: AgentEntity[] = [];\n let usedTokens = 0;\n let lastSalience = maxSalience;\n\n for (const s of belowCursor) {\n const tokens = this.estimateTokens(s.entity);\n if (usedTokens + tokens <= budget) {\n selected.push(s.entity);\n usedTokens += tokens;\n lastSalience = s.salienceScore;\n } else {\n break;\n }\n }\n\n // Create cursor for next page if more content remains\n const remaining = belowCursor.filter(\n (s) => !selected.some((e) => e.name === s.entity.name)\n );\n const nextCursor = remaining.length > 0\n ? this.createPageCursor({\n entity: selected[selected.length - 1] ?? belowCursor[0].entity,\n reason: 'budget_exceeded',\n tokens: 0,\n salience: lastSalience,\n })\n : undefined;\n\n return {\n entities: selected,\n nextCursor,\n tokens: usedTokens,\n };\n }\n\n /**\n * Create pagination cursor from excluded entity.\n * @internal\n */\n private createPageCursor(excluded: ExcludedEntity): string {\n return Buffer.from(\n JSON.stringify({\n maxSalience: excluded.salience ?? 0,\n lastEntity: excluded.entity.name,\n })\n ).toString('base64');\n }\n\n /**\n * Parse pagination cursor.\n * @internal\n */\n private parsePageCursor(cursor: string): { maxSalience: number; lastEntity: string } {\n try {\n return JSON.parse(Buffer.from(cursor, 'base64').toString('utf-8'));\n } catch {\n return { maxSalience: 1.0, lastEntity: '' };\n }\n }\n\n /**\n * Generate suggestions for spillover content.\n * @internal\n */\n private generateSpilloverSuggestions(\n spilledEntities: ExcludedEntity[],\n spilledTokens: number\n ): string[] {\n const suggestions: string[] = [];\n\n if (spilledEntities.length > 0) {\n // Suggest high-priority content\n const highPriority = spilledEntities.filter((e) => (e.salience ?? 0) > 0.7);\n if (highPriority.length > 0) {\n suggestions.push(\n `${highPriority.length} high-salience memories available for follow-up retrieval`\n );\n }\n\n // Token summary\n suggestions.push(\n `${spilledTokens} tokens of content available in next page(s)`\n );\n\n // Memory type breakdown\n const byType = new Map<string, number>();\n for (const e of spilledEntities) {\n const type = e.entity.memoryType ?? 'unknown';\n byType.set(type, (byType.get(type) ?? 0) + 1);\n }\n const typeBreakdown = Array.from(byType.entries())\n .map(([type, count]) => `${count} ${type}`)\n .join(', ');\n suggestions.push(`Spillover breakdown: ${typeBreakdown}`);\n }\n\n return suggestions;\n }\n\n // ==================== Diversity Enforcement ====================\n\n /**\n * Enforce diversity in selected entities by detecting and replacing duplicates.\n *\n * @param entities - Selected entities to check\n * @param candidates - Pool of candidate replacements\n * @param context - Salience context\n * @returns Diversified entities and replaced entities\n */\n async enforceDiversity(\n entities: ScoredEntity[],\n candidates: ScoredEntity[],\n _context: SalienceContext = {}\n ): Promise<{ diversified: ScoredEntity[]; replaced: Array<{ original: AgentEntity; replacement: AgentEntity }> }> {\n if (!this.config.enforceDiversity || entities.length <= 1) {\n return { diversified: entities, replaced: [] };\n }\n\n const diversified: ScoredEntity[] = [];\n const replaced: Array<{ original: AgentEntity; replacement: AgentEntity }> = [];\n const usedNames = new Set<string>();\n\n for (let i = 0; i < entities.length; i++) {\n const current = entities[i];\n let isDuplicate = false;\n\n // Check similarity against already diversified entities\n for (const included of diversified) {\n const similarity = this.salienceEngine.calculateEntitySimilarity(\n current.entity,\n included.entity\n );\n\n if (similarity > this.config.diversityThreshold) {\n isDuplicate = true;\n break;\n }\n }\n\n if (!isDuplicate) {\n diversified.push(current);\n usedNames.add(current.entity.name);\n } else {\n // Find a diverse replacement from candidates\n const replacement = this.findDiverseReplacement(\n current,\n diversified,\n candidates,\n usedNames\n );\n\n if (replacement) {\n diversified.push(replacement);\n usedNames.add(replacement.entity.name);\n replaced.push({\n original: current.entity,\n replacement: replacement.entity,\n });\n }\n // If no replacement found, skip this entity\n }\n }\n\n return { diversified, replaced };\n }\n\n /**\n * Find a diverse replacement for a duplicate entity.\n * @internal\n */\n private findDiverseReplacement(\n _duplicate: ScoredEntity,\n diversified: ScoredEntity[],\n candidates: ScoredEntity[],\n usedNames: Set<string>\n ): ScoredEntity | null {\n // Sort candidates by salience (descending)\n const sortedCandidates = [...candidates].sort(\n (a, b) => b.salienceScore - a.salienceScore\n );\n\n for (const candidate of sortedCandidates) {\n // Skip if already used\n if (usedNames.has(candidate.entity.name)) continue;\n\n // Check diversity against all diversified entities\n let isDiverse = true;\n for (const included of diversified) {\n const similarity = this.salienceEngine.calculateEntitySimilarity(\n candidate.entity,\n included.entity\n );\n\n if (similarity > this.config.diversityThreshold) {\n isDiverse = false;\n break;\n }\n }\n\n if (isDiverse) {\n return candidate;\n }\n }\n\n return null;\n }\n\n /**\n * Calculate diversity score for a set of entities.\n * Higher score means more diverse content.\n *\n * @param entities - Entities to evaluate\n * @returns Diversity score between 0 and 1\n */\n calculateDiversityScore(entities: AgentEntity[]): number {\n if (entities.length <= 1) return 1.0;\n\n let totalSimilarity = 0;\n let comparisons = 0;\n\n for (let i = 0; i < entities.length; i++) {\n for (let j = i + 1; j < entities.length; j++) {\n const similarity = this.salienceEngine.calculateEntitySimilarity(\n entities[i],\n entities[j]\n );\n totalSimilarity += similarity;\n comparisons++;\n }\n }\n\n // Invert: low similarity = high diversity\n const avgSimilarity = comparisons > 0 ? totalSimilarity / comparisons : 0;\n return 1 - avgSimilarity;\n }\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<ContextWindowManagerConfig>> {\n return { ...this.config };\n }\n}\n","/**\n * Memory Formatter\n *\n * Formats memories for LLM consumption with customizable templates.\n * Respects token limits and provides both prompt and JSON formats.\n *\n * @module agent/MemoryFormatter\n */\n\nimport type { AgentEntity, ContextPackage } from '../types/agent-memory.js';\n\n/**\n * Configuration for MemoryFormatter.\n */\nexport interface MemoryFormatterConfig {\n /** Default maximum output tokens (default: 2000) */\n defaultMaxTokens?: number;\n /** Token estimation multiplier (default: 1.3) */\n tokenMultiplier?: number;\n /** Include timestamps in output (default: true) */\n includeTimestamps?: boolean;\n /** Include salience scores in output (default: false) */\n includeSalience?: boolean;\n /** Include memory type in output (default: true) */\n includeMemoryType?: boolean;\n /** Custom template for prompt format */\n promptTemplate?: string;\n}\n\n/**\n * Formats memories for LLM consumption.\n *\n * Provides multiple output formats optimized for different use cases:\n * - Prompt format: Human-readable text for inclusion in prompts\n * - JSON format: Structured data for tool use\n * - Compact format: Minimal tokens for constrained contexts\n *\n * @example\n * ```typescript\n * const formatter = new MemoryFormatter();\n * const promptText = formatter.formatForPrompt(memories, { maxTokens: 1000 });\n * const jsonData = formatter.formatAsJSON(contextPackage);\n * ```\n */\nexport class MemoryFormatter {\n private readonly config: Required<MemoryFormatterConfig>;\n private readonly defaultPromptTemplate: string;\n\n constructor(config: MemoryFormatterConfig = {}) {\n this.config = {\n defaultMaxTokens: config.defaultMaxTokens ?? 2000,\n tokenMultiplier: config.tokenMultiplier ?? 1.3,\n includeTimestamps: config.includeTimestamps ?? true,\n includeSalience: config.includeSalience ?? false,\n includeMemoryType: config.includeMemoryType ?? true,\n promptTemplate: config.promptTemplate ?? '',\n };\n\n this.defaultPromptTemplate = `## {name} ({type})\n{observations}\n{metadata}`;\n }\n\n // ==================== Prompt Format ====================\n\n /**\n * Format memories as text for LLM prompts.\n *\n * @param memories - Memories to format\n * @param options - Formatting options\n * @returns Formatted text string\n */\n formatForPrompt(\n memories: AgentEntity[],\n options: {\n maxTokens?: number;\n header?: string;\n separator?: string;\n } = {}\n ): string {\n const { maxTokens = this.config.defaultMaxTokens, header, separator = '\\n\\n' } = options;\n\n const parts: string[] = [];\n let estimatedTokens = 0;\n\n // Add header if provided\n if (header) {\n parts.push(header);\n estimatedTokens += this.estimateTokens(header);\n }\n\n // Format each memory\n for (const memory of memories) {\n const formatted = this.formatSingleMemory(memory);\n const memoryTokens = this.estimateTokens(formatted);\n\n if (estimatedTokens + memoryTokens > maxTokens) {\n // Add truncation indicator\n parts.push('... (additional memories truncated)');\n break;\n }\n\n parts.push(formatted);\n estimatedTokens += memoryTokens;\n }\n\n return parts.join(separator);\n }\n\n /**\n * Format a single memory entry.\n *\n * @param memory - Memory to format\n * @returns Formatted string\n */\n formatSingleMemory(memory: AgentEntity): string {\n const template = this.config.promptTemplate || this.defaultPromptTemplate;\n\n // Build observations text\n const observations = (memory.observations ?? [])\n .map((o) => `- ${o}`)\n .join('\\n');\n\n // Build metadata text\n const metadataParts: string[] = [];\n if (this.config.includeMemoryType && memory.memoryType) {\n metadataParts.push(`Type: ${memory.memoryType}`);\n }\n if (this.config.includeTimestamps) {\n if (memory.createdAt) {\n metadataParts.push(`Created: ${this.formatTimestamp(memory.createdAt)}`);\n }\n if (memory.lastAccessedAt) {\n metadataParts.push(`Last accessed: ${this.formatTimestamp(memory.lastAccessedAt)}`);\n }\n }\n const metadata = metadataParts.length > 0 ? `[${metadataParts.join(' | ')}]` : '';\n\n // Apply template\n return template\n .replace('{name}', memory.name)\n .replace('{type}', memory.entityType)\n .replace('{observations}', observations)\n .replace('{metadata}', metadata)\n .trim();\n }\n\n // ==================== JSON Format ====================\n\n /**\n * Format memories as JSON for tool use.\n *\n * @param contextPackage - Context package with memories\n * @param options - Formatting options\n * @returns JSON-serializable object\n */\n formatAsJSON(\n contextPackage: ContextPackage,\n options: {\n includeBreakdown?: boolean;\n includeSuggestions?: boolean;\n compact?: boolean;\n } = {}\n ): object {\n const { includeBreakdown = true, includeSuggestions = true, compact = false } = options;\n\n if (compact) {\n return this.formatCompactJSON(contextPackage);\n }\n\n const result: Record<string, unknown> = {\n memories: contextPackage.memories.map((m) => this.memoryToJSON(m)),\n totalTokens: contextPackage.totalTokens,\n };\n\n if (includeBreakdown) {\n result.breakdown = contextPackage.breakdown;\n }\n\n if (includeSuggestions && contextPackage.suggestions.length > 0) {\n result.suggestions = contextPackage.suggestions;\n }\n\n if (contextPackage.excluded.length > 0) {\n result.excludedCount = contextPackage.excluded.length;\n }\n\n return result;\n }\n\n /**\n * Convert a single memory to JSON format.\n *\n * @param memory - Memory to convert\n * @returns JSON-serializable object\n */\n memoryToJSON(memory: AgentEntity): object {\n const result: Record<string, unknown> = {\n name: memory.name,\n type: memory.entityType,\n observations: memory.observations ?? [],\n };\n\n if (this.config.includeMemoryType && memory.memoryType) {\n result.memoryType = memory.memoryType;\n }\n\n if (this.config.includeTimestamps) {\n if (memory.createdAt) result.createdAt = memory.createdAt;\n if (memory.lastAccessedAt) result.lastAccessedAt = memory.lastAccessedAt;\n }\n\n if (memory.sessionId) result.sessionId = memory.sessionId;\n if (memory.taskId) result.taskId = memory.taskId;\n if (memory.importance !== undefined) result.importance = memory.importance;\n if (memory.confidence !== undefined) result.confidence = memory.confidence;\n\n return result;\n }\n\n /**\n * Format as compact JSON with minimal fields.\n * @internal\n */\n private formatCompactJSON(contextPackage: ContextPackage): object {\n return {\n m: contextPackage.memories.map((m) => ({\n n: m.name,\n t: m.entityType,\n o: m.observations ?? [],\n })),\n tokens: contextPackage.totalTokens,\n };\n }\n\n // ==================== Compact Format ====================\n\n /**\n * Format memories in minimal token format.\n * Useful for very constrained contexts.\n *\n * @param memories - Memories to format\n * @param maxTokens - Maximum tokens\n * @returns Compact text string\n */\n formatCompact(memories: AgentEntity[], maxTokens: number): string {\n const parts: string[] = [];\n let estimatedTokens = 0;\n\n for (const memory of memories) {\n // Ultra-compact format: name: first observation\n const firstObs = (memory.observations ?? [])[0] ?? '';\n const compact = `${memory.name}: ${firstObs}`;\n const tokens = this.estimateTokens(compact);\n\n if (estimatedTokens + tokens > maxTokens) break;\n\n parts.push(compact);\n estimatedTokens += tokens;\n }\n\n return parts.join('\\n');\n }\n\n // ==================== Specialized Formats ====================\n\n /**\n * Format memories grouped by type.\n *\n * @param memories - Memories to format\n * @returns Grouped text format\n */\n formatByType(memories: AgentEntity[]): string {\n const groups = new Map<string, AgentEntity[]>();\n\n for (const memory of memories) {\n const type = memory.memoryType ?? 'other';\n if (!groups.has(type)) {\n groups.set(type, []);\n }\n groups.get(type)!.push(memory);\n }\n\n const sections: string[] = [];\n const typeOrder = ['working', 'episodic', 'semantic', 'procedural', 'other'];\n\n for (const type of typeOrder) {\n const typeMemories = groups.get(type);\n if (typeMemories && typeMemories.length > 0) {\n sections.push(`### ${type.charAt(0).toUpperCase() + type.slice(1)} Memory\\n`);\n sections.push(\n typeMemories\n .map((m) => this.formatSingleMemory(m))\n .join('\\n\\n')\n );\n }\n }\n\n return sections.join('\\n\\n');\n }\n\n /**\n * Format memories as a summary.\n *\n * @param contextPackage - Context package\n * @returns Summary text\n */\n formatSummary(contextPackage: ContextPackage): string {\n const lines: string[] = [\n `## Memory Context Summary`,\n `Total memories: ${contextPackage.memories.length}`,\n `Estimated tokens: ${contextPackage.totalTokens}`,\n ``,\n ];\n\n // Breakdown\n const { breakdown } = contextPackage;\n lines.push('### Token Breakdown');\n if (breakdown.working > 0) lines.push(`- Working: ${breakdown.working} tokens`);\n if (breakdown.episodic > 0) lines.push(`- Episodic: ${breakdown.episodic} tokens`);\n if (breakdown.semantic > 0) lines.push(`- Semantic: ${breakdown.semantic} tokens`);\n if (breakdown.procedural > 0) lines.push(`- Procedural: ${breakdown.procedural} tokens`);\n if (breakdown.mustInclude > 0) lines.push(`- Must-include: ${breakdown.mustInclude} tokens`);\n\n // Excluded info\n if (contextPackage.excluded.length > 0) {\n lines.push('');\n lines.push(`### Excluded: ${contextPackage.excluded.length} memories`);\n }\n\n // Suggestions\n if (contextPackage.suggestions.length > 0) {\n lines.push('');\n lines.push('### Suggestions');\n for (const suggestion of contextPackage.suggestions) {\n lines.push(`- ${suggestion}`);\n }\n }\n\n return lines.join('\\n');\n }\n\n // ==================== Helper Methods ====================\n\n /**\n * Estimate token count for text.\n * @internal\n */\n private estimateTokens(text: string): number {\n const wordCount = text.split(/\\s+/).filter((w) => w.length > 0).length;\n return Math.ceil(wordCount * this.config.tokenMultiplier);\n }\n\n /**\n * Format timestamp for display.\n * @internal\n */\n private formatTimestamp(isoString: string): string {\n const date = new Date(isoString);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffHours = Math.floor(diffMs / (1000 * 60 * 60));\n const diffDays = Math.floor(diffHours / 24);\n\n if (diffHours < 1) return 'just now';\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n return date.toLocaleDateString();\n }\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<MemoryFormatterConfig>> {\n return { ...this.config };\n }\n}\n","/**\n * Working Memory Manager\n *\n * Manages short-term, session-scoped memories with TTL-based expiration.\n * Working memories are temporary and automatically cleaned up after expiry.\n *\n * @module agent/WorkingMemoryManager\n */\n\nimport type { IGraphStorage, Entity } from '../types/types.js';\nimport type {\n AgentEntity,\n WorkingMemoryOptions,\n} from '../types/agent-memory.js';\nimport { isAgentEntity } from '../types/agent-memory.js';\n\n/**\n * Configuration for WorkingMemoryManager.\n */\nexport interface WorkingMemoryConfig {\n /** Default TTL in hours (default: 24) */\n defaultTTLHours?: number;\n /** Maximum memories per session (default: 100) */\n maxPerSession?: number;\n /** Auto-promote memories meeting threshold (default: false) */\n autoPromote?: boolean;\n /** Confidence threshold for auto-promotion (default: 0.8) */\n autoPromoteConfidenceThreshold?: number;\n /** Confirmation threshold for auto-promotion (default: 2) */\n autoPromoteConfirmationThreshold?: number;\n}\n\n/**\n * Filter options for session memory queries.\n */\nexport interface SessionMemoryFilter {\n /** Filter by entity type */\n entityType?: string;\n /** Filter by task ID */\n taskId?: string;\n /** Minimum importance */\n minImportance?: number;\n /** Maximum importance */\n maxImportance?: number;\n /** Include only non-expired */\n excludeExpired?: boolean;\n}\n\n/**\n * Options for marking memory for promotion.\n */\nexport interface PromotionMarkOptions {\n /** Target memory type after promotion */\n targetType?: 'episodic' | 'semantic';\n /** Priority for promotion (higher = promoted sooner) */\n priority?: number;\n /** Reason for marking */\n reason?: string;\n}\n\n/**\n * Criteria for identifying promotion candidates.\n */\nexport interface PromotionCriteria {\n /** Include explicitly marked memories (default: true) */\n includeMarked?: boolean;\n /** Minimum confidence for auto-promotion */\n minConfidence?: number;\n /** Minimum confirmations for auto-promotion */\n minConfirmations?: number;\n /** Minimum access count */\n minAccessCount?: number;\n}\n\n/**\n * Result of a promotion operation.\n */\nexport interface PromotionResult {\n /** Name of promoted entity */\n entityName: string;\n /** Previous memory type */\n fromType: 'working';\n /** New memory type */\n toType: 'episodic' | 'semantic';\n /** Timestamp of promotion */\n promotedAt: string;\n}\n\n/**\n * Result of a confirmation operation.\n */\nexport interface ConfirmationResult {\n /** Whether the confirmation was recorded */\n confirmed: boolean;\n /** Whether the memory was auto-promoted */\n promoted: boolean;\n}\n\n/**\n * Manages session-scoped working memories with TTL expiration.\n *\n * Working memories are short-term, temporary memories tied to a specific\n * session. They automatically expire after a configurable TTL period.\n * This is the primary interface for creating and managing temporary\n * memories during active conversations.\n *\n * Key features:\n * - Session-scoped memory isolation\n * - Configurable TTL (default 24 hours)\n * - Max memories per session limit\n * - Efficient session index for O(1) lookups\n * - Automatic expiration cleanup\n *\n * @example\n * ```typescript\n * const wmm = new WorkingMemoryManager(storage);\n *\n * // Create a working memory\n * const memory = await wmm.createWorkingMemory(\n * 'session_123',\n * 'User prefers budget hotels under $100/night'\n * );\n *\n * // Get all memories for session\n * const memories = await wmm.getSessionMemories('session_123');\n *\n * // Extend TTL for important memories\n * await wmm.extendTTL([memory.name], 48);\n *\n * // Clean up expired memories\n * const cleared = await wmm.clearExpired();\n * ```\n */\nexport class WorkingMemoryManager {\n private readonly storage: IGraphStorage;\n private readonly config: Required<WorkingMemoryConfig>;\n\n // Index: sessionId -> Set of entity names\n private sessionIndex: Map<string, Set<string>>;\n\n constructor(storage: IGraphStorage, config: WorkingMemoryConfig = {}) {\n this.storage = storage;\n this.config = {\n defaultTTLHours: config.defaultTTLHours ?? 24,\n maxPerSession: config.maxPerSession ?? 100,\n autoPromote: config.autoPromote ?? false,\n autoPromoteConfidenceThreshold: config.autoPromoteConfidenceThreshold ?? 0.8,\n autoPromoteConfirmationThreshold: config.autoPromoteConfirmationThreshold ?? 2,\n };\n this.sessionIndex = new Map();\n }\n\n // ==================== Memory Creation ====================\n\n /**\n * Generate a unique name for a working memory.\n * @internal\n */\n private generateMemoryName(sessionId: string, content: string): string {\n const timestamp = Date.now();\n const contentHash = this.hashContent(content).slice(0, 8);\n return `wm_${sessionId}_${timestamp}_${contentHash}`;\n }\n\n /**\n * Simple hash function for content uniqueness.\n * @internal\n */\n private hashContent(content: string): string {\n let hash = 0;\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16);\n }\n\n /**\n * Calculate expiration timestamp.\n * @internal\n */\n private calculateExpiration(ttlHours: number): string {\n const expiresAt = new Date();\n expiresAt.setTime(expiresAt.getTime() + ttlHours * 60 * 60 * 1000);\n return expiresAt.toISOString();\n }\n\n /**\n * Create a new working memory for a session.\n *\n * Creates an AgentEntity with memoryType='working' that will automatically\n * expire after the TTL period. The memory is associated with the given\n * session and tracked in the session index for efficient retrieval.\n *\n * @param sessionId - Session identifier to associate memory with\n * @param content - The memory content (stored as first observation)\n * @param options - Optional configuration for this memory\n * @returns The created AgentEntity\n * @throws Error if session has reached max memory limit\n *\n * @example\n * ```typescript\n * const memory = await wmm.createWorkingMemory(\n * 'session_123',\n * 'User mentioned they have a budget of $500',\n * {\n * ttlHours: 48,\n * importance: 7,\n * taskId: 'trip_planning',\n * }\n * );\n * ```\n */\n async createWorkingMemory(\n sessionId: string,\n content: string,\n options?: WorkingMemoryOptions\n ): Promise<AgentEntity> {\n // Check session limit\n const sessionMemories = this.sessionIndex.get(sessionId);\n if (sessionMemories && sessionMemories.size >= this.config.maxPerSession) {\n throw new Error(\n `Session ${sessionId} has reached maximum memory limit (${this.config.maxPerSession})`\n );\n }\n\n const now = new Date().toISOString();\n const ttlHours = options?.ttlHours ?? this.config.defaultTTLHours;\n const name = this.generateMemoryName(sessionId, content);\n\n const entity: AgentEntity = {\n // Base Entity fields\n name,\n entityType: options?.entityType ?? 'working_memory',\n observations: [content],\n createdAt: now,\n lastModified: now,\n importance: options?.importance ?? 5,\n\n // AgentEntity fields\n memoryType: 'working',\n sessionId,\n taskId: options?.taskId,\n expiresAt: this.calculateExpiration(ttlHours),\n isWorkingMemory: true,\n accessCount: 0,\n lastAccessedAt: now,\n confidence: options?.confidence ?? 0.5,\n confirmationCount: 0,\n visibility: options?.visibility ?? 'private',\n agentId: options?.agentId,\n };\n\n // Persist to storage\n await this.storage.appendEntity(entity as Entity);\n\n // Update session index\n if (!this.sessionIndex.has(sessionId)) {\n this.sessionIndex.set(sessionId, new Set());\n }\n this.sessionIndex.get(sessionId)!.add(name);\n\n return entity;\n }\n\n // ==================== Memory Retrieval ====================\n\n /**\n * Rebuild session index from storage for a specific session.\n * @internal\n */\n private async rebuildSessionIndex(sessionId: string): Promise<void> {\n const graph = await this.storage.loadGraph();\n const sessionMemories = new Set<string>();\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity;\n\n if (\n agentEntity.sessionId === sessionId &&\n agentEntity.memoryType === 'working'\n ) {\n sessionMemories.add(agentEntity.name);\n }\n }\n\n if (sessionMemories.size > 0) {\n this.sessionIndex.set(sessionId, sessionMemories);\n }\n }\n\n /**\n * Get all working memories for a session.\n *\n * Returns working memories associated with the given session,\n * optionally filtered by various criteria. Uses an in-memory\n * index for efficient lookups.\n *\n * @param sessionId - Session identifier\n * @param filter - Optional filtering criteria\n * @returns Array of AgentEntity matching the criteria\n *\n * @example\n * ```typescript\n * // Get all memories for session\n * const all = await wmm.getSessionMemories('session_123');\n *\n * // Get only task-specific memories\n * const taskMemories = await wmm.getSessionMemories('session_123', {\n * taskId: 'trip_planning',\n * excludeExpired: true,\n * });\n *\n * // Get high-importance memories\n * const important = await wmm.getSessionMemories('session_123', {\n * minImportance: 7,\n * });\n * ```\n */\n async getSessionMemories(\n sessionId: string,\n filter?: SessionMemoryFilter\n ): Promise<AgentEntity[]> {\n let memoryNames = this.sessionIndex.get(sessionId);\n\n if (!memoryNames || memoryNames.size === 0) {\n // Try to rebuild index from storage\n await this.rebuildSessionIndex(sessionId);\n memoryNames = this.sessionIndex.get(sessionId);\n if (!memoryNames || memoryNames.size === 0) {\n return [];\n }\n }\n\n const now = Date.now();\n const memories: AgentEntity[] = [];\n\n for (const name of memoryNames) {\n const entity = this.storage.getEntityByName(name);\n if (!entity || !isAgentEntity(entity)) continue;\n\n const agentEntity = entity;\n\n // Apply filters\n if (filter?.entityType && agentEntity.entityType !== filter.entityType) {\n continue;\n }\n if (filter?.taskId && agentEntity.taskId !== filter.taskId) {\n continue;\n }\n if (\n filter?.minImportance !== undefined &&\n (agentEntity.importance ?? 0) < filter.minImportance\n ) {\n continue;\n }\n if (\n filter?.maxImportance !== undefined &&\n (agentEntity.importance ?? 10) > filter.maxImportance\n ) {\n continue;\n }\n if (filter?.excludeExpired && agentEntity.expiresAt) {\n const expiresAt = new Date(agentEntity.expiresAt).getTime();\n if (expiresAt < now) continue;\n }\n\n memories.push(agentEntity);\n }\n\n return memories;\n }\n\n // ==================== Expiration Management ====================\n\n /**\n * Clear all expired working memories.\n *\n * Scans all working memories and removes those where expiresAt < now.\n * Also removes any relations involving the expired entities.\n *\n * @returns Number of memories cleared\n *\n * @example\n * ```typescript\n * // Clear expired memories periodically\n * const cleared = await wmm.clearExpired();\n * console.log(`Cleared ${cleared} expired memories`);\n * ```\n */\n async clearExpired(): Promise<number> {\n const now = Date.now();\n const graph = await this.storage.loadGraph();\n const expiredNames: string[] = [];\n\n // Find expired working memories\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity;\n\n if (agentEntity.memoryType !== 'working') continue;\n if (!agentEntity.expiresAt) continue;\n\n const expiresAt = new Date(agentEntity.expiresAt).getTime();\n if (expiresAt < now) {\n expiredNames.push(agentEntity.name);\n }\n }\n\n if (expiredNames.length === 0) {\n return 0;\n }\n\n // Remove expired entities\n const expiredSet = new Set(expiredNames);\n const updatedEntities = graph.entities.filter((e) => !expiredSet.has(e.name));\n\n // Remove relations involving expired entities\n const updatedRelations = graph.relations.filter(\n (r) => !expiredSet.has(r.from) && !expiredSet.has(r.to)\n );\n\n // Persist changes\n await this.storage.saveGraph({\n entities: updatedEntities,\n relations: updatedRelations,\n });\n\n // Update session index\n for (const [_sessionId, names] of this.sessionIndex) {\n for (const name of expiredNames) {\n names.delete(name);\n }\n }\n\n return expiredNames.length;\n }\n\n // ==================== TTL Management ====================\n\n /**\n * Extend TTL for specified working memories.\n *\n * Adds additional time to the expiration of the specified memories.\n * If a memory has already expired, its TTL is extended from the\n * current time.\n *\n * @param entityNames - Names of entities to extend\n * @param additionalHours - Hours to add to TTL\n * @throws Error if any entity doesn't exist or isn't working memory\n *\n * @example\n * ```typescript\n * // Extend important memories by 48 hours\n * await wmm.extendTTL(['wm_session_123_abc123'], 48);\n * ```\n */\n async extendTTL(entityNames: string[], additionalHours: number): Promise<void> {\n if (additionalHours <= 0) {\n throw new Error('additionalHours must be positive');\n }\n\n for (const name of entityNames) {\n const entity = this.storage.getEntityByName(name);\n if (!entity) {\n throw new Error(`Entity not found: ${name}`);\n }\n\n if (!isAgentEntity(entity)) {\n throw new Error(`Entity is not an AgentEntity: ${name}`);\n }\n\n const agentEntity = entity;\n if (agentEntity.memoryType !== 'working') {\n throw new Error(`Entity is not working memory: ${name}`);\n }\n\n // Calculate new expiration\n let currentExpires: Date;\n if (agentEntity.expiresAt) {\n currentExpires = new Date(agentEntity.expiresAt);\n // If already expired, start from now\n if (currentExpires.getTime() < Date.now()) {\n currentExpires = new Date();\n }\n } else {\n currentExpires = new Date();\n }\n\n const newExpires = new Date(\n currentExpires.getTime() + additionalHours * 60 * 60 * 1000\n );\n\n await this.storage.updateEntity(name, {\n expiresAt: newExpires.toISOString(),\n lastModified: new Date().toISOString(),\n } as Record<string, unknown>);\n }\n }\n\n // ==================== Promotion Support ====================\n\n /**\n * Mark a memory for promotion consideration.\n *\n * Sets the markedForPromotion flag to true, indicating this memory\n * should be considered for promotion to long-term storage.\n *\n * @param entityName - Name of the entity to mark\n * @param options - Optional promotion options\n * @throws Error if entity doesn't exist or isn't working memory\n *\n * @example\n * ```typescript\n * // Mark for promotion to semantic memory\n * await wmm.markForPromotion('wm_session_1_abc', {\n * targetType: 'semantic',\n * reason: 'User confirmed this preference multiple times'\n * });\n * ```\n */\n async markForPromotion(\n entityName: string,\n options?: PromotionMarkOptions\n ): Promise<void> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity) {\n throw new Error(`Entity not found: ${entityName}`);\n }\n\n if (!isAgentEntity(entity)) {\n throw new Error(`Entity is not an AgentEntity: ${entityName}`);\n }\n\n const agentEntity = entity;\n if (agentEntity.memoryType !== 'working') {\n throw new Error(`Entity is not working memory: ${entityName}`);\n }\n\n const updates: Record<string, unknown> = {\n markedForPromotion: true,\n lastModified: new Date().toISOString(),\n };\n\n // Store target type via tag convention\n if (options?.targetType) {\n const currentTags = agentEntity.tags ?? [];\n const targetTag = `promote_to_${options.targetType}`;\n if (!currentTags.includes(targetTag)) {\n updates.tags = [...currentTags, targetTag];\n }\n }\n\n await this.storage.updateEntity(entityName, updates);\n }\n\n /**\n * Get memories that are candidates for promotion.\n *\n * Returns working memories for the session that either:\n * - Are marked for promotion, or\n * - Meet auto-promotion thresholds (if autoPromote is enabled)\n *\n * Candidates are sorted by priority (higher = promoted sooner).\n *\n * @param sessionId - Session identifier\n * @param criteria - Optional criteria override\n * @returns Array of promotion candidate AgentEntities sorted by priority\n *\n * @example\n * ```typescript\n * // Get candidates using default thresholds\n * const candidates = await wmm.getPromotionCandidates('session_1');\n *\n * // Get candidates with custom criteria\n * const highConfidence = await wmm.getPromotionCandidates('session_1', {\n * minConfidence: 0.9,\n * minConfirmations: 3,\n * });\n * ```\n */\n async getPromotionCandidates(\n sessionId: string,\n criteria?: PromotionCriteria\n ): Promise<AgentEntity[]> {\n const effectiveCriteria = {\n includeMarked: criteria?.includeMarked ?? true,\n minConfidence:\n criteria?.minConfidence ?? this.config.autoPromoteConfidenceThreshold,\n minConfirmations:\n criteria?.minConfirmations ?? this.config.autoPromoteConfirmationThreshold,\n minAccessCount: criteria?.minAccessCount ?? 0,\n };\n\n const memories = await this.getSessionMemories(sessionId);\n const candidates: Array<{ entity: AgentEntity; priority: number }> = [];\n\n for (const memory of memories) {\n let isCandidate = false;\n let priority = 0;\n\n // Check if explicitly marked\n if (effectiveCriteria.includeMarked && memory.markedForPromotion) {\n isCandidate = true;\n priority += 100; // High priority for marked\n }\n\n // Check threshold criteria (only if autoPromote enabled or explicitly checking)\n const meetsConfidence = memory.confidence >= effectiveCriteria.minConfidence;\n const meetsConfirmations =\n memory.confirmationCount >= effectiveCriteria.minConfirmations;\n const meetsAccess = memory.accessCount >= effectiveCriteria.minAccessCount;\n\n if (meetsConfidence && meetsConfirmations && meetsAccess) {\n isCandidate = true;\n priority += memory.confidence * 50;\n priority += memory.confirmationCount * 10;\n priority += memory.accessCount * 1;\n }\n\n if (isCandidate) {\n candidates.push({ entity: memory, priority });\n }\n }\n\n // Sort by priority (descending)\n candidates.sort((a, b) => b.priority - a.priority);\n\n return candidates.map((c) => c.entity);\n }\n\n /**\n * Promote a working memory to long-term storage.\n *\n * Converts the memory from working to episodic or semantic type.\n * Clears TTL-related fields and sets promotion tracking metadata.\n *\n * @param entityName - Entity to promote\n * @param targetType - Target memory type (default: 'episodic')\n * @returns Promotion result with details\n * @throws Error if entity doesn't exist or isn't working memory\n *\n * @example\n * ```typescript\n * // Promote to episodic memory\n * const result = await wmm.promoteMemory('wm_session_1_abc');\n *\n * // Promote to semantic memory\n * const result = await wmm.promoteMemory('wm_session_1_xyz', 'semantic');\n * ```\n */\n async promoteMemory(\n entityName: string,\n targetType: 'episodic' | 'semantic' = 'episodic'\n ): Promise<PromotionResult> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity) {\n throw new Error(`Entity not found: ${entityName}`);\n }\n\n if (!isAgentEntity(entity)) {\n throw new Error(`Entity is not an AgentEntity: ${entityName}`);\n }\n\n const agentEntity = entity;\n if (agentEntity.memoryType !== 'working') {\n throw new Error(`Entity is not working memory: ${entityName}`);\n }\n\n const now = new Date().toISOString();\n\n // Build updates - clear working memory fields and set promotion tracking\n const updates: Record<string, unknown> = {\n // Change memory type\n memoryType: targetType,\n\n // Clear working memory fields\n expiresAt: undefined,\n isWorkingMemory: undefined,\n markedForPromotion: undefined,\n\n // Set promotion tracking\n promotedAt: now,\n promotedFrom: agentEntity.sessionId,\n\n // Update timestamp\n lastModified: now,\n };\n\n // Remove promotion target tags\n if (agentEntity.tags) {\n updates.tags = agentEntity.tags.filter((t) => !t.startsWith('promote_to_'));\n }\n\n // Persist changes\n await this.storage.updateEntity(entityName, updates);\n\n // Remove from session index\n const sessionId = agentEntity.sessionId;\n if (sessionId && this.sessionIndex.has(sessionId)) {\n this.sessionIndex.get(sessionId)!.delete(entityName);\n }\n\n return {\n entityName,\n fromType: 'working',\n toType: targetType,\n promotedAt: now,\n };\n }\n\n /**\n * Increment confirmation count for a working memory.\n *\n * May trigger auto-promotion if enabled and thresholds are met.\n * This is the primary way to strengthen memories during conversations.\n *\n * @param entityName - Entity to confirm\n * @param confidenceBoost - Optional confidence boost (0-1 range, added to current)\n * @returns Confirmation result indicating if promoted\n * @throws Error if entity doesn't exist or isn't working memory\n *\n * @example\n * ```typescript\n * // Confirm a memory\n * const result = await wmm.confirmMemory('wm_session_1_abc');\n * if (result.promoted) {\n * console.log('Memory was auto-promoted!');\n * }\n *\n * // Confirm with confidence boost\n * const result = await wmm.confirmMemory('wm_session_1_xyz', 0.1);\n * ```\n */\n async confirmMemory(\n entityName: string,\n confidenceBoost?: number\n ): Promise<ConfirmationResult> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity) {\n throw new Error(`Entity not found: ${entityName}`);\n }\n\n if (!isAgentEntity(entity)) {\n throw new Error(`Entity is not an AgentEntity: ${entityName}`);\n }\n\n const agentEntity = entity;\n if (agentEntity.memoryType !== 'working') {\n throw new Error(`Entity is not working memory: ${entityName}`);\n }\n\n // Increment confirmation\n const newConfirmations = (agentEntity.confirmationCount ?? 0) + 1;\n let newConfidence = agentEntity.confidence ?? 0.5;\n if (confidenceBoost !== undefined && confidenceBoost > 0) {\n newConfidence = Math.min(1, newConfidence + confidenceBoost);\n }\n\n const updates: Record<string, unknown> = {\n confirmationCount: newConfirmations,\n confidence: newConfidence,\n lastModified: new Date().toISOString(),\n };\n\n await this.storage.updateEntity(entityName, updates);\n\n // Check auto-promotion\n let promoted = false;\n if (this.config.autoPromote) {\n const meetsConfidence =\n newConfidence >= this.config.autoPromoteConfidenceThreshold;\n const meetsConfirmations =\n newConfirmations >= this.config.autoPromoteConfirmationThreshold;\n\n if (meetsConfidence && meetsConfirmations) {\n await this.promoteMemory(entityName, 'semantic');\n promoted = true;\n }\n }\n\n return { confirmed: true, promoted };\n }\n\n // ==================== Configuration Access ====================\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<WorkingMemoryConfig>> {\n return { ...this.config };\n }\n\n /**\n * Get the current session index size.\n */\n getSessionCount(): number {\n return this.sessionIndex.size;\n }\n\n /**\n * Get memory count for a specific session.\n */\n getSessionMemoryCount(sessionId: string): number {\n return this.sessionIndex.get(sessionId)?.size ?? 0;\n }\n}\n","/**\n * Session Manager\n *\n * Manages conversation/task session lifecycle including creation,\n * updates, ending, and session linking for continuity tracking.\n *\n * @module agent/SessionManager\n */\n\nimport type { IGraphStorage, Entity, Relation } from '../types/types.js';\nimport type {\n AgentEntity,\n SessionEntity,\n SessionStatus,\n} from '../types/agent-memory.js';\nimport { isSessionEntity } from '../types/agent-memory.js';\nimport { WorkingMemoryManager } from './WorkingMemoryManager.js';\nimport type { EpisodicMemoryManager } from './EpisodicMemoryManager.js';\n\n/**\n * Configuration for SessionManager.\n */\nexport interface SessionConfig {\n /** Consolidate memories on session end (default: false) */\n consolidateOnEnd?: boolean;\n /** Delete working memories on end (default: false) */\n cleanupOnEnd?: boolean;\n /** Promote high-confidence memories on end (default: true) */\n promoteOnEnd?: boolean;\n /** Create episodic summary on session end (default: true when episodicMemory provided) */\n createSummaryOnEnd?: boolean;\n /** Default agent ID for sessions */\n defaultAgentId?: string;\n}\n\n/**\n * Options for starting a new session.\n */\nexport interface StartSessionOptions {\n /** Description of session goal */\n goalDescription?: string;\n /** Type of task being performed */\n taskType?: string;\n /** Detected user intent */\n userIntent?: string;\n /** Continue from previous session */\n previousSessionId?: string;\n /** Agent ID for multi-agent */\n agentId?: string;\n /** Custom session ID (default: auto-generated) */\n sessionId?: string;\n}\n\n/**\n * Options for querying session history.\n */\nexport interface SessionHistoryOptions {\n /** Filter by status */\n status?: SessionStatus;\n /** Filter by task type */\n taskType?: string;\n /** Filter by agent ID */\n agentId?: string;\n /** Start date filter (ISO 8601) */\n startDate?: string;\n /** End date filter (ISO 8601) */\n endDate?: string;\n /** Maximum results */\n limit?: number;\n /** Offset for pagination */\n offset?: number;\n}\n\n/**\n * Result of ending a session.\n */\nexport interface EndSessionResult {\n /** Updated session entity */\n session: SessionEntity;\n /** Number of memories cleaned up */\n memoriesCleaned: number;\n /** Number of memories promoted */\n memoriesPromoted: number;\n /** Episodic summary created (if enabled) */\n summary?: AgentEntity;\n}\n\n/**\n * Manages session lifecycle for conversations and tasks.\n *\n * SessionManager is the primary interface for conversation/task session\n * management. It coordinates with WorkingMemoryManager for memory scoping\n * and provides lifecycle management including creation, ending, and linking.\n *\n * @example\n * ```typescript\n * const sm = new SessionManager(storage, workingMemory);\n *\n * // Start a new session\n * const session = await sm.startSession({\n * goalDescription: 'Plan a trip to Tokyo',\n * taskType: 'trip_planning',\n * });\n *\n * // Create memories during session\n * await workingMemory.createWorkingMemory(\n * session.sessionId,\n * 'User prefers budget hotels'\n * );\n *\n * // End session with promotion\n * const result = await sm.endSession(session.sessionId, 'completed');\n * console.log(`Promoted ${result.memoriesPromoted} memories`);\n * ```\n */\nexport class SessionManager {\n private readonly storage: IGraphStorage;\n private readonly workingMemory: WorkingMemoryManager;\n private readonly episodicMemory?: EpisodicMemoryManager;\n private readonly config: Required<SessionConfig>;\n\n // Active sessions: sessionId -> SessionEntity\n private activeSessions: Map<string, SessionEntity>;\n\n constructor(\n storage: IGraphStorage,\n workingMemory: WorkingMemoryManager,\n config: SessionConfig = {},\n episodicMemory?: EpisodicMemoryManager\n ) {\n this.storage = storage;\n this.workingMemory = workingMemory;\n this.episodicMemory = episodicMemory;\n this.config = {\n consolidateOnEnd: config.consolidateOnEnd ?? false,\n cleanupOnEnd: config.cleanupOnEnd ?? false,\n promoteOnEnd: config.promoteOnEnd ?? true,\n createSummaryOnEnd: config.createSummaryOnEnd ?? (episodicMemory !== undefined),\n defaultAgentId: config.defaultAgentId ?? 'default',\n };\n this.activeSessions = new Map();\n }\n\n // ==================== Session ID Generation ====================\n\n /**\n * Generate a unique session ID.\n * @internal\n */\n private generateSessionId(): string {\n const timestamp = Date.now();\n const random = Math.random().toString(36).substring(2, 8);\n return `session_${timestamp}_${random}`;\n }\n\n // ==================== Session Creation ====================\n\n /**\n * Start a new session.\n *\n * Creates a SessionEntity with active status and stores it in both\n * storage and the active sessions map. Supports continuation from\n * a previous session via previousSessionId.\n *\n * @param options - Session configuration options\n * @returns Created SessionEntity\n * @throws Error if custom sessionId already exists\n *\n * @example\n * ```typescript\n * // Start a new session\n * const session = await sm.startSession({\n * goalDescription: 'Help with coding task',\n * taskType: 'coding',\n * });\n *\n * // Continue from a previous session\n * const continued = await sm.startSession({\n * previousSessionId: 'session_123_abc',\n * goalDescription: 'Continue trip planning',\n * });\n * ```\n */\n async startSession(options?: StartSessionOptions): Promise<SessionEntity> {\n const now = new Date().toISOString();\n const sessionId = options?.sessionId ?? this.generateSessionId();\n\n // Check if session already exists\n if (this.activeSessions.has(sessionId)) {\n throw new Error(`Session already exists: ${sessionId}`);\n }\n\n // Also check storage for existing session\n const existing = this.storage.getEntityByName(sessionId);\n if (existing) {\n throw new Error(`Session already exists in storage: ${sessionId}`);\n }\n\n // Build observations array\n const observations: string[] = [];\n if (options?.goalDescription) {\n observations.push(`Session goal: ${options.goalDescription}`);\n }\n\n const session: SessionEntity = {\n // Base Entity fields\n name: sessionId,\n entityType: 'session',\n observations,\n createdAt: now,\n lastModified: now,\n importance: 5,\n\n // AgentEntity fields\n memoryType: 'episodic',\n sessionId: sessionId,\n accessCount: 0,\n lastAccessedAt: now,\n confidence: 1.0,\n confirmationCount: 0,\n visibility: 'private',\n agentId: options?.agentId ?? this.config.defaultAgentId,\n\n // SessionEntity fields\n startedAt: now,\n status: 'active',\n goalDescription: options?.goalDescription,\n taskType: options?.taskType,\n userIntent: options?.userIntent,\n memoryCount: 0,\n consolidatedCount: 0,\n previousSessionId: options?.previousSessionId,\n relatedSessionIds: options?.previousSessionId ? [options.previousSessionId] : [],\n };\n\n // If continuing from previous, link back\n if (options?.previousSessionId) {\n const prevSession = this.storage.getEntityByName(options.previousSessionId);\n if (prevSession && isSessionEntity(prevSession)) {\n const relatedIds = prevSession.relatedSessionIds ?? [];\n if (!relatedIds.includes(sessionId)) {\n await this.storage.updateEntity(options.previousSessionId, {\n relatedSessionIds: [...relatedIds, sessionId],\n lastModified: now,\n } as Record<string, unknown>);\n }\n }\n }\n\n // Persist to storage\n await this.storage.appendEntity(session as Entity);\n\n // Track as active\n this.activeSessions.set(sessionId, session);\n\n return session;\n }\n\n // ==================== Session Ending ====================\n\n /**\n * End a session.\n *\n * Updates the session with end timestamp and status, optionally\n * promotes high-confidence memories and cleans up working memories\n * based on configuration.\n *\n * @param sessionId - Session to end\n * @param status - Ending status ('completed' or 'abandoned')\n * @returns End session result with statistics\n * @throws Error if session not found or not active\n *\n * @example\n * ```typescript\n * // End a completed session\n * const result = await sm.endSession('session_123', 'completed');\n * console.log(`Promoted ${result.memoriesPromoted} memories`);\n * console.log(`Cleaned ${result.memoriesCleaned} memories`);\n *\n * // Abandon a session\n * await sm.endSession('session_456', 'abandoned');\n * ```\n */\n async endSession(\n sessionId: string,\n status: 'completed' | 'abandoned' = 'completed'\n ): Promise<EndSessionResult> {\n // Try to get from active sessions first, then storage\n let session = this.activeSessions.get(sessionId);\n if (!session) {\n const stored = this.storage.getEntityByName(sessionId);\n if (stored && isSessionEntity(stored)) {\n session = stored;\n }\n }\n\n if (!session) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n\n if (session.status !== 'active') {\n throw new Error(`Session is not active: ${sessionId}`);\n }\n\n const now = new Date().toISOString();\n let memoriesCleaned = 0;\n let memoriesPromoted = 0;\n\n // Get session memories for statistics\n const sessionMemories = await this.workingMemory.getSessionMemories(sessionId);\n const memoryCount = sessionMemories.length;\n\n // Promote candidates if configured\n if (this.config.promoteOnEnd) {\n const candidates = await this.workingMemory.getPromotionCandidates(sessionId);\n for (const candidate of candidates) {\n try {\n await this.workingMemory.promoteMemory(candidate.name, 'episodic');\n memoriesPromoted++;\n } catch {\n // Continue on promotion failure\n }\n }\n }\n\n // Cleanup working memories if configured\n if (this.config.cleanupOnEnd) {\n const remainingMemories = await this.workingMemory.getSessionMemories(sessionId);\n for (const memory of remainingMemories) {\n // Delete non-promoted working memories\n const graph = await this.storage.getGraphForMutation();\n graph.entities = graph.entities.filter((e) => e.name !== memory.name);\n graph.relations = graph.relations.filter(\n (r) => r.from !== memory.name && r.to !== memory.name\n );\n await this.storage.saveGraph(graph);\n memoriesCleaned++;\n }\n }\n\n // Update session\n const currentObs = session.observations ?? [];\n const updates: Record<string, unknown> = {\n endedAt: now,\n status,\n memoryCount,\n consolidatedCount: memoriesPromoted,\n lastModified: now,\n observations: [...currentObs, `Session ended: ${status} at ${now}`],\n };\n\n await this.storage.updateEntity(sessionId, updates);\n\n // Remove from active sessions\n this.activeSessions.delete(sessionId);\n\n // Build updated session\n const updatedSession: SessionEntity = {\n ...session,\n endedAt: now,\n status,\n memoryCount,\n consolidatedCount: memoriesPromoted,\n lastModified: now,\n observations: [...currentObs, `Session ended: ${status} at ${now}`],\n };\n\n // Create episodic summary if configured\n let summary: AgentEntity | undefined;\n if (this.config.createSummaryOnEnd && this.episodicMemory) {\n summary = await this.createSessionSummary(updatedSession);\n }\n\n return {\n session: updatedSession,\n memoriesCleaned,\n memoriesPromoted,\n summary,\n };\n }\n\n // ==================== Session Summary ====================\n\n /**\n * Create episodic summary of session.\n * @internal\n */\n private async createSessionSummary(\n session: SessionEntity\n ): Promise<AgentEntity | undefined> {\n if (!this.episodicMemory) return undefined;\n\n // Get session working memories\n const memories = await this.workingMemory.getSessionMemories(session.name);\n\n // Create summary content\n const summaryContent = [\n `Session: ${session.goalDescription ?? 'Conversation'}`,\n `Started: ${session.startedAt}`,\n `Ended: ${session.endedAt ?? 'N/A'}`,\n `Status: ${session.status}`,\n `Memories created: ${memories.length}`,\n ].join('\\n');\n\n // Create episodic summary\n const summaryEntity = await this.episodicMemory.createEpisode(summaryContent, {\n sessionId: session.name,\n entityType: 'session_summary',\n importance: 7,\n confidence: 1.0,\n agentId: session.agentId,\n });\n\n // Link session to summary\n await this.storage.appendRelation({\n from: session.name,\n to: summaryEntity.name,\n relationType: 'has_summary',\n createdAt: new Date().toISOString(),\n } as Relation);\n\n return summaryEntity;\n }\n\n // ==================== Session Queries ====================\n\n /**\n * Get active session(s).\n *\n * @param sessionId - Optional specific session ID\n * @returns Active session or undefined\n *\n * @example\n * ```typescript\n * // Get specific active session\n * const session = await sm.getActiveSession('session_123');\n *\n * // Get first active session\n * const any = await sm.getActiveSession();\n * ```\n */\n async getActiveSession(sessionId?: string): Promise<SessionEntity | undefined> {\n if (sessionId) {\n return this.activeSessions.get(sessionId);\n }\n\n // Return first active session if no ID specified\n const [first] = this.activeSessions.values();\n return first;\n }\n\n /**\n * Get all active sessions.\n *\n * @returns Array of active SessionEntity objects\n */\n getActiveSessions(): SessionEntity[] {\n return Array.from(this.activeSessions.values());\n }\n\n /**\n * Get session history with filtering.\n *\n * Returns sessions from storage matching the provided filters,\n * sorted by most recent first with pagination support.\n *\n * @param options - Filter and pagination options\n * @returns Matching sessions\n *\n * @example\n * ```typescript\n * // Get all completed sessions\n * const completed = await sm.getSessionHistory({\n * status: 'completed',\n * });\n *\n * // Get sessions from last week\n * const recent = await sm.getSessionHistory({\n * startDate: '2026-01-06T00:00:00Z',\n * limit: 20,\n * });\n *\n * // Get sessions by task type with pagination\n * const codingSessions = await sm.getSessionHistory({\n * taskType: 'coding',\n * limit: 10,\n * offset: 20,\n * });\n * ```\n */\n async getSessionHistory(options?: SessionHistoryOptions): Promise<SessionEntity[]> {\n const graph = await this.storage.loadGraph();\n let sessions: SessionEntity[] = [];\n\n // Find all sessions\n for (const entity of graph.entities) {\n if (!isSessionEntity(entity)) continue;\n sessions.push(entity);\n }\n\n // Apply filters\n if (options?.status) {\n sessions = sessions.filter((s) => s.status === options.status);\n }\n if (options?.taskType) {\n sessions = sessions.filter((s) => s.taskType === options.taskType);\n }\n if (options?.agentId) {\n sessions = sessions.filter((s) => s.agentId === options.agentId);\n }\n if (options?.startDate) {\n const startTime = new Date(options.startDate).getTime();\n sessions = sessions.filter(\n (s) => new Date(s.startedAt).getTime() >= startTime\n );\n }\n if (options?.endDate) {\n const endTime = new Date(options.endDate).getTime();\n sessions = sessions.filter(\n (s) => new Date(s.startedAt).getTime() <= endTime\n );\n }\n\n // Sort by startedAt descending (most recent first)\n sessions.sort(\n (a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()\n );\n\n // Apply pagination\n const offset = options?.offset ?? 0;\n const limit = options?.limit ?? 100;\n return sessions.slice(offset, offset + limit);\n }\n\n // ==================== Session Linking ====================\n\n /**\n * Link multiple sessions as related.\n *\n * Updates relatedSessionIds on all specified sessions to include\n * references to each other, enabling bidirectional traversal.\n *\n * @param sessionIds - Sessions to link (minimum 2)\n * @throws Error if less than 2 sessions or any not found\n *\n * @example\n * ```typescript\n * // Link three related sessions\n * await sm.linkSessions([\n * 'session_123',\n * 'session_456',\n * 'session_789',\n * ]);\n * ```\n */\n async linkSessions(sessionIds: string[]): Promise<void> {\n if (sessionIds.length < 2) {\n throw new Error('At least 2 sessions required for linking');\n }\n\n const now = new Date().toISOString();\n\n // Verify all sessions exist\n for (const id of sessionIds) {\n const session = this.storage.getEntityByName(id);\n if (!session || !isSessionEntity(session)) {\n throw new Error(`Session not found: ${id}`);\n }\n }\n\n // Update each session with all related IDs\n for (const id of sessionIds) {\n const session = this.storage.getEntityByName(id) as SessionEntity;\n const existingRelated = new Set(session.relatedSessionIds ?? []);\n\n // Add all other session IDs\n for (const otherId of sessionIds) {\n if (otherId !== id) {\n existingRelated.add(otherId);\n }\n }\n\n await this.storage.updateEntity(id, {\n relatedSessionIds: Array.from(existingRelated),\n lastModified: now,\n } as Record<string, unknown>);\n }\n }\n\n /**\n * Get chain of linked sessions starting from a session.\n *\n * Traverses previousSessionId and relatedSessionIds to build\n * a chain of connected sessions, sorted from oldest to newest.\n *\n * @param sessionId - Starting session\n * @returns Chain of sessions (oldest to newest)\n *\n * @example\n * ```typescript\n * // Get full conversation chain\n * const chain = await sm.getSessionChain('session_latest');\n * console.log(`Chain has ${chain.length} sessions`);\n * for (const s of chain) {\n * console.log(`${s.sessionId}: ${s.goalDescription}`);\n * }\n * ```\n */\n async getSessionChain(sessionId: string): Promise<SessionEntity[]> {\n const visited = new Set<string>();\n const chain: SessionEntity[] = [];\n\n const traverse = async (id: string): Promise<void> => {\n if (visited.has(id)) return;\n visited.add(id);\n\n const session = this.storage.getEntityByName(id);\n if (!session || !isSessionEntity(session)) return;\n\n // Traverse to previous\n if (session.previousSessionId && !visited.has(session.previousSessionId)) {\n await traverse(session.previousSessionId);\n }\n\n // Add current\n chain.push(session);\n\n // Traverse to related that continue from this\n for (const relatedId of session.relatedSessionIds ?? []) {\n const related = this.storage.getEntityByName(relatedId);\n if (\n related &&\n isSessionEntity(related) &&\n related.previousSessionId === id\n ) {\n await traverse(relatedId);\n }\n }\n };\n\n await traverse(sessionId);\n\n // Sort by startedAt\n chain.sort(\n (a, b) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime()\n );\n\n return chain;\n }\n\n // ==================== Configuration Access ====================\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<SessionConfig>> {\n return { ...this.config };\n }\n\n /**\n * Get the count of active sessions.\n */\n getActiveSessionCount(): number {\n return this.activeSessions.size;\n }\n}\n","/**\n * Episodic Memory Manager\n *\n * Manages episodic memories (conversations, events, experiences)\n * with temporal ordering and causal relationships.\n *\n * @module agent/EpisodicMemoryManager\n */\n\nimport type { IGraphStorage, Entity, Relation } from '../types/types.js';\nimport type { AgentEntity } from '../types/agent-memory.js';\nimport { isAgentEntity } from '../types/agent-memory.js';\n\n/**\n * Relation types for episodic memory structure.\n */\nexport const EpisodicRelations = {\n /** Temporal sequence: A occurred before B */\n PRECEDES: 'precedes',\n /** Temporal sequence: A occurred after B */\n FOLLOWS: 'follows',\n /** Causal: A caused B to happen */\n CAUSES: 'causes',\n /** Causal: A was caused by B */\n CAUSED_BY: 'caused_by',\n /** Part of event sequence */\n PART_OF_SEQUENCE: 'part_of_sequence',\n} as const;\n\n/**\n * Configuration for EpisodicMemoryManager.\n */\nexport interface EpisodicMemoryConfig {\n /** Auto-create temporal relations (default: true) */\n autoLinkTemporal?: boolean;\n /** Max events per sequence (default: 1000) */\n maxSequenceLength?: number;\n}\n\n/**\n * Options for creating an episodic memory.\n */\nexport interface CreateEpisodeOptions {\n /** Session this episode belongs to */\n sessionId?: string;\n /** Previous event in sequence */\n previousEventId?: string;\n /** Task ID */\n taskId?: string;\n /** Entity type (default: 'episode') */\n entityType?: string;\n /** Importance (0-10) */\n importance?: number;\n /** Confidence (0-1) */\n confidence?: number;\n /** Agent ID */\n agentId?: string;\n}\n\n/**\n * Options for timeline queries.\n */\nexport interface TimelineOptions {\n /** Order: ascending (oldest first) or descending (newest first) */\n order?: 'asc' | 'desc';\n /** Start time filter (ISO 8601) */\n startTime?: string;\n /** End time filter (ISO 8601) */\n endTime?: string;\n /** Maximum results */\n limit?: number;\n /** Offset for pagination */\n offset?: number;\n}\n\n/**\n * Manages episodic memories with temporal and causal structure.\n *\n * Episodic memories represent specific events, conversations, or experiences\n * that have a temporal context. This manager provides:\n * - Event creation with automatic temporal linking\n * - Event sequence management (precedes/follows relations)\n * - Timeline queries for chronological retrieval\n * - Causal relationship tracking (causes/caused_by)\n *\n * @example\n * ```typescript\n * const emm = new EpisodicMemoryManager(storage);\n *\n * // Create individual events\n * const event1 = await emm.createEpisode('User asked about hotels');\n * const event2 = await emm.createEpisode('Found 5 hotels', {\n * previousEventId: event1.name,\n * sessionId: 'session_123'\n * });\n *\n * // Create a sequence of events\n * const events = await emm.createEventSequence([\n * 'User logged in',\n * 'User searched for flights',\n * 'User booked a flight'\n * ], { sessionId: 'session_123' });\n *\n * // Query timeline\n * const timeline = await emm.getTimeline('session_123', { order: 'asc' });\n *\n * // Track causality\n * await emm.addCausalLink(event1.name, event2.name);\n * const chain = await emm.getCausalChain(event1.name, 'causes');\n * ```\n */\nexport class EpisodicMemoryManager {\n private readonly storage: IGraphStorage;\n private readonly config: Required<EpisodicMemoryConfig>;\n\n constructor(storage: IGraphStorage, config: EpisodicMemoryConfig = {}) {\n this.storage = storage;\n this.config = {\n autoLinkTemporal: config.autoLinkTemporal ?? true,\n maxSequenceLength: config.maxSequenceLength ?? 1000,\n };\n }\n\n // ==================== Event Creation ====================\n\n /**\n * Create an episodic memory.\n *\n * @param content - The event content/description\n * @param options - Creation options\n * @returns The created episodic memory entity\n */\n async createEpisode(\n content: string,\n options?: CreateEpisodeOptions\n ): Promise<AgentEntity> {\n const now = new Date().toISOString();\n const timestamp = Date.now();\n const name = `episode_${timestamp}_${Math.random().toString(36).slice(2, 8)}`;\n\n const entity: AgentEntity = {\n name,\n entityType: options?.entityType ?? 'episode',\n observations: [content],\n createdAt: now,\n lastModified: now,\n importance: options?.importance ?? 5,\n memoryType: 'episodic',\n sessionId: options?.sessionId,\n taskId: options?.taskId,\n accessCount: 0,\n confidence: options?.confidence ?? 0.8,\n confirmationCount: 0,\n visibility: 'private',\n agentId: options?.agentId,\n };\n\n // Persist entity\n await this.storage.appendEntity(entity as Entity);\n\n // Link to previous event if specified and auto-link enabled\n if (options?.previousEventId && this.config.autoLinkTemporal) {\n await this.linkEvents(options.previousEventId, name);\n }\n\n return entity;\n }\n\n /**\n * Create multiple events as a sequence.\n *\n * @param contents - Event contents in order\n * @param options - Shared options for all events\n * @returns Created events in order\n */\n async createEventSequence(\n contents: string[],\n options?: Omit<CreateEpisodeOptions, 'previousEventId'>\n ): Promise<AgentEntity[]> {\n const events: AgentEntity[] = [];\n let previousId: string | undefined;\n\n for (const content of contents) {\n const event = await this.createEpisode(content, {\n ...options,\n previousEventId: previousId,\n });\n events.push(event);\n previousId = event.name;\n }\n\n return events;\n }\n\n // ==================== Temporal Linking ====================\n\n /**\n * Link two events in temporal sequence.\n * @internal\n */\n private async linkEvents(beforeId: string, afterId: string): Promise<void> {\n const now = new Date().toISOString();\n\n // Create precedes relation\n const precedesRelation: Relation = {\n from: beforeId,\n to: afterId,\n relationType: EpisodicRelations.PRECEDES,\n createdAt: now,\n };\n\n // Create follows relation (reverse)\n const followsRelation: Relation = {\n from: afterId,\n to: beforeId,\n relationType: EpisodicRelations.FOLLOWS,\n createdAt: now,\n };\n\n await this.storage.appendRelation(precedesRelation);\n await this.storage.appendRelation(followsRelation);\n }\n\n /**\n * Link multiple events into a sequence.\n *\n * @param entityNames - Events to link in order\n */\n async linkSequence(entityNames: string[]): Promise<void> {\n if (entityNames.length < 2) return;\n if (entityNames.length > this.config.maxSequenceLength) {\n throw new Error(\n `Sequence exceeds max length (${this.config.maxSequenceLength})`\n );\n }\n\n // Link each pair\n for (let i = 0; i < entityNames.length - 1; i++) {\n await this.linkEvents(entityNames[i], entityNames[i + 1]);\n }\n }\n\n // ==================== Timeline Queries ====================\n\n /**\n * Get episodic memories for a session as timeline.\n *\n * @param sessionId - Session to query\n * @param options - Timeline options\n * @returns Episodes in chronological order\n */\n async getTimeline(\n sessionId: string,\n options?: TimelineOptions\n ): Promise<AgentEntity[]> {\n const graph = await this.storage.loadGraph();\n let episodes: AgentEntity[] = [];\n\n // Find episodic memories for session\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity as AgentEntity;\n\n if (agentEntity.memoryType !== 'episodic') continue;\n if (agentEntity.sessionId !== sessionId) continue;\n // Exclude session entities themselves\n if (agentEntity.entityType === 'session') continue;\n\n // Apply time filters\n if (options?.startTime && agentEntity.createdAt) {\n if (new Date(agentEntity.createdAt) < new Date(options.startTime)) {\n continue;\n }\n }\n if (options?.endTime && agentEntity.createdAt) {\n if (new Date(agentEntity.createdAt) > new Date(options.endTime)) {\n continue;\n }\n }\n\n episodes.push(agentEntity);\n }\n\n // Sort by createdAt\n const order = options?.order ?? 'asc';\n episodes.sort((a, b) => {\n const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return order === 'asc' ? timeA - timeB : timeB - timeA;\n });\n\n // Apply pagination\n const offset = options?.offset ?? 0;\n const limit = options?.limit ?? episodes.length;\n return episodes.slice(offset, offset + limit);\n }\n\n /**\n * Iterate through timeline forward (oldest to newest).\n *\n * @param sessionId - Session to iterate\n */\n async *iterateForward(sessionId: string): AsyncGenerator<AgentEntity> {\n const timeline = await this.getTimeline(sessionId, { order: 'asc' });\n for (const episode of timeline) {\n yield episode;\n }\n }\n\n /**\n * Iterate through timeline backward (newest to oldest).\n *\n * @param sessionId - Session to iterate\n */\n async *iterateBackward(sessionId: string): AsyncGenerator<AgentEntity> {\n const timeline = await this.getTimeline(sessionId, { order: 'desc' });\n for (const episode of timeline) {\n yield episode;\n }\n }\n\n /**\n * Get the next event after the given event.\n *\n * @param entityName - Current event name\n * @returns Next event or undefined\n */\n async getNextEvent(entityName: string): Promise<AgentEntity | undefined> {\n const relations = this.storage.getRelationsFrom(entityName);\n\n for (const rel of relations) {\n if (rel.relationType === EpisodicRelations.PRECEDES) {\n const entity = this.storage.getEntityByName(rel.to);\n if (entity && isAgentEntity(entity)) {\n return entity as AgentEntity;\n }\n }\n }\n\n return undefined;\n }\n\n /**\n * Get the previous event before the given event.\n *\n * @param entityName - Current event name\n * @returns Previous event or undefined\n */\n async getPreviousEvent(entityName: string): Promise<AgentEntity | undefined> {\n const relations = this.storage.getRelationsFrom(entityName);\n\n for (const rel of relations) {\n if (rel.relationType === EpisodicRelations.FOLLOWS) {\n const entity = this.storage.getEntityByName(rel.to);\n if (entity && isAgentEntity(entity)) {\n return entity as AgentEntity;\n }\n }\n }\n\n return undefined;\n }\n\n // ==================== Causal Relationships ====================\n\n /**\n * Add causal relationship between events.\n *\n * @param causeEntity - Entity that caused the effect\n * @param effectEntity - Entity that was caused\n */\n async addCausalLink(\n causeEntity: string,\n effectEntity: string\n ): Promise<void> {\n // Verify both entities exist\n const cause = this.storage.getEntityByName(causeEntity);\n const effect = this.storage.getEntityByName(effectEntity);\n\n if (!cause) throw new Error(`Cause entity not found: ${causeEntity}`);\n if (!effect) throw new Error(`Effect entity not found: ${effectEntity}`);\n\n const now = new Date().toISOString();\n\n // Create causes relation\n const causesRelation: Relation = {\n from: causeEntity,\n to: effectEntity,\n relationType: EpisodicRelations.CAUSES,\n createdAt: now,\n };\n\n // Create caused_by relation (reverse)\n const causedByRelation: Relation = {\n from: effectEntity,\n to: causeEntity,\n relationType: EpisodicRelations.CAUSED_BY,\n createdAt: now,\n };\n\n await this.storage.appendRelation(causesRelation);\n await this.storage.appendRelation(causedByRelation);\n }\n\n /**\n * Get causal chain from an event.\n *\n * @param entityName - Starting event\n * @param direction - Follow causes or caused_by\n * @returns Chain of events\n */\n async getCausalChain(\n entityName: string,\n direction: 'causes' | 'caused_by'\n ): Promise<AgentEntity[]> {\n const visited = new Set<string>();\n const chain: AgentEntity[] = [];\n\n const relationType =\n direction === 'causes'\n ? EpisodicRelations.CAUSES\n : EpisodicRelations.CAUSED_BY;\n\n const traverse = async (name: string) => {\n if (visited.has(name)) return; // Prevent cycles\n visited.add(name);\n\n const entity = this.storage.getEntityByName(name);\n if (!entity || !isAgentEntity(entity)) return;\n\n chain.push(entity as AgentEntity);\n\n // Find next in chain\n const relations = this.storage.getRelationsFrom(name);\n for (const rel of relations) {\n if (rel.relationType === relationType) {\n await traverse(rel.to);\n }\n }\n };\n\n await traverse(entityName);\n return chain;\n }\n\n /**\n * Get direct causes of an event.\n *\n * @param entityName - Event to query\n * @returns Events that directly caused this event\n */\n async getDirectCauses(entityName: string): Promise<AgentEntity[]> {\n const relations = this.storage.getRelationsTo(entityName);\n const causes: AgentEntity[] = [];\n\n for (const rel of relations) {\n if (rel.relationType === EpisodicRelations.CAUSES) {\n const entity = this.storage.getEntityByName(rel.from);\n if (entity && isAgentEntity(entity)) {\n causes.push(entity as AgentEntity);\n }\n }\n }\n\n return causes;\n }\n\n /**\n * Get direct effects of an event.\n *\n * @param entityName - Event to query\n * @returns Events that were directly caused by this event\n */\n async getDirectEffects(entityName: string): Promise<AgentEntity[]> {\n const relations = this.storage.getRelationsFrom(entityName);\n const effects: AgentEntity[] = [];\n\n for (const rel of relations) {\n if (rel.relationType === EpisodicRelations.CAUSES) {\n const entity = this.storage.getEntityByName(rel.to);\n if (entity && isAgentEntity(entity)) {\n effects.push(entity as AgentEntity);\n }\n }\n }\n\n return effects;\n }\n\n // ==================== Utility Methods ====================\n\n /**\n * Get all episodic memories across all sessions.\n *\n * @param options - Filter and pagination options\n * @returns All episodic memories\n */\n async getAllEpisodes(options?: TimelineOptions): Promise<AgentEntity[]> {\n const graph = await this.storage.loadGraph();\n let episodes: AgentEntity[] = [];\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity as AgentEntity;\n\n if (agentEntity.memoryType !== 'episodic') continue;\n if (agentEntity.entityType === 'session') continue;\n\n // Apply time filters\n if (options?.startTime && agentEntity.createdAt) {\n if (new Date(agentEntity.createdAt) < new Date(options.startTime)) {\n continue;\n }\n }\n if (options?.endTime && agentEntity.createdAt) {\n if (new Date(agentEntity.createdAt) > new Date(options.endTime)) {\n continue;\n }\n }\n\n episodes.push(agentEntity);\n }\n\n // Sort by createdAt\n const order = options?.order ?? 'asc';\n episodes.sort((a, b) => {\n const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return order === 'asc' ? timeA - timeB : timeB - timeA;\n });\n\n // Apply pagination\n const offset = options?.offset ?? 0;\n const limit = options?.limit ?? episodes.length;\n return episodes.slice(offset, offset + limit);\n }\n\n /**\n * Count episodic memories for a session.\n *\n * @param sessionId - Session to count\n * @returns Number of episodic memories\n */\n async getEpisodeCount(sessionId: string): Promise<number> {\n const timeline = await this.getTimeline(sessionId);\n return timeline.length;\n }\n}\n","/**\n * Pattern Detector\n *\n * Identifies recurring templates in text observations using token-based matching.\n * Extracts patterns with variable slots for generalization into semantic memory.\n *\n * @module agent/PatternDetector\n */\n\nimport type { PatternResult } from '../types/agent-memory.js';\n\n/**\n * Internal candidate for pattern detection.\n */\ninterface PatternCandidate {\n /** Template pattern with {X} slots */\n pattern: string;\n /** Extracted variable values */\n variables: string[];\n /** Number of matches */\n occurrences: number;\n /** Source texts that matched */\n sourceTexts: string[];\n}\n\n/**\n * Detects recurring patterns in text observations.\n *\n * PatternDetector identifies common templates by comparing tokenized\n * observations and finding shared structure with variable parts.\n *\n * @example\n * ```typescript\n * const detector = new PatternDetector();\n *\n * const observations = [\n * 'User prefers Italian food',\n * 'User prefers Mexican food',\n * 'User prefers Japanese food',\n * 'Meeting scheduled for Monday',\n * ];\n *\n * const patterns = detector.detectPatterns(observations, 2);\n * // Returns: [{ pattern: 'User prefers {X} food', variables: ['Italian', 'Mexican', 'Japanese'], ... }]\n * ```\n */\nexport class PatternDetector {\n /**\n * Detect patterns in a list of observations.\n *\n * Compares pairs of observations to identify common templates\n * with variable slots. Patterns must appear at least minOccurrences\n * times to be returned.\n *\n * @param observations - Text observations to analyze\n * @param minOccurrences - Minimum pattern frequency (default: 2)\n * @returns Array of detected patterns\n */\n detectPatterns(\n observations: string[],\n minOccurrences: number = 2\n ): PatternResult[] {\n if (observations.length < 2) {\n return [];\n }\n\n const patterns = new Map<string, PatternCandidate>();\n\n // Compare each pair of observations\n for (let i = 0; i < observations.length; i++) {\n for (let j = i + 1; j < observations.length; j++) {\n const template = this.extractTemplate(observations[i], observations[j]);\n if (template) {\n const key = template.pattern;\n if (!patterns.has(key)) {\n patterns.set(key, {\n pattern: template.pattern,\n variables: [],\n occurrences: 0,\n sourceTexts: [],\n });\n }\n const p = patterns.get(key)!;\n p.occurrences++;\n p.variables.push(...template.variables);\n if (!p.sourceTexts.includes(observations[i])) {\n p.sourceTexts.push(observations[i]);\n }\n if (!p.sourceTexts.includes(observations[j])) {\n p.sourceTexts.push(observations[j]);\n }\n }\n }\n }\n\n // Check for additional matches against existing patterns\n for (const candidate of patterns.values()) {\n for (const obs of observations) {\n if (!candidate.sourceTexts.includes(obs)) {\n if (this.matchesPattern(obs, candidate.pattern)) {\n const extracted = this.extractVariables(obs, candidate.pattern);\n if (extracted) {\n candidate.variables.push(...extracted);\n candidate.sourceTexts.push(obs);\n candidate.occurrences++;\n }\n }\n }\n }\n }\n\n // Filter by minimum occurrences and convert to PatternResult\n return Array.from(patterns.values())\n .filter((p) => p.sourceTexts.length >= minOccurrences)\n .map((p) => ({\n pattern: p.pattern,\n variables: [...new Set(p.variables)],\n occurrences: p.sourceTexts.length,\n confidence: Math.min(1, p.sourceTexts.length / observations.length),\n sourceEntities: [],\n }))\n .sort((a, b) => b.occurrences - a.occurrences);\n }\n\n /**\n * Extract a template pattern from two observations.\n *\n * Compares tokens and identifies common structure with variable parts.\n * Returns null if observations don't share a meaningful pattern.\n *\n * @param text1 - First observation\n * @param text2 - Second observation\n * @returns Template with pattern and variables, or null\n * @internal\n */\n private extractTemplate(\n text1: string,\n text2: string\n ): { pattern: string; variables: string[] } | null {\n const tokens1 = this.tokenize(text1);\n const tokens2 = this.tokenize(text2);\n\n // Must have same number of tokens\n if (tokens1.length !== tokens2.length) {\n return null;\n }\n\n // Must have at least 2 tokens\n if (tokens1.length < 2) {\n return null;\n }\n\n const pattern: string[] = [];\n const variables: string[] = [];\n let variableCount = 0;\n let fixedCount = 0;\n\n for (let i = 0; i < tokens1.length; i++) {\n if (tokens1[i].toLowerCase() === tokens2[i].toLowerCase()) {\n pattern.push(tokens1[i]);\n fixedCount++;\n } else {\n pattern.push('{X}');\n variables.push(tokens1[i], tokens2[i]);\n variableCount++;\n }\n }\n\n // Require at least one variable and at least one fixed token\n // Also require more fixed tokens than variables for meaningful patterns\n if (variableCount === 0 || fixedCount === 0) {\n return null;\n }\n\n // Patterns should have more context than variables\n if (fixedCount < variableCount) {\n return null;\n }\n\n return { pattern: pattern.join(' '), variables };\n }\n\n /**\n * Check if an observation matches a pattern template.\n *\n * @param text - Observation to check\n * @param pattern - Pattern template with {X} slots\n * @returns True if observation matches pattern\n * @internal\n */\n private matchesPattern(text: string, pattern: string): boolean {\n const textTokens = this.tokenize(text);\n const patternTokens = pattern.split(' ');\n\n if (textTokens.length !== patternTokens.length) {\n return false;\n }\n\n for (let i = 0; i < patternTokens.length; i++) {\n if (patternTokens[i] !== '{X}') {\n if (textTokens[i].toLowerCase() !== patternTokens[i].toLowerCase()) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n /**\n * Extract variable values from an observation that matches a pattern.\n *\n * @param text - Observation to extract from\n * @param pattern - Pattern template with {X} slots\n * @returns Array of extracted variable values, or null if no match\n * @internal\n */\n private extractVariables(text: string, pattern: string): string[] | null {\n const textTokens = this.tokenize(text);\n const patternTokens = pattern.split(' ');\n\n if (textTokens.length !== patternTokens.length) {\n return null;\n }\n\n const variables: string[] = [];\n for (let i = 0; i < patternTokens.length; i++) {\n if (patternTokens[i] === '{X}') {\n variables.push(textTokens[i]);\n }\n }\n\n return variables.length > 0 ? variables : null;\n }\n\n /**\n * Tokenize text into words.\n *\n * @param text - Text to tokenize\n * @returns Array of word tokens\n * @internal\n */\n private tokenize(text: string): string[] {\n return text\n .split(/\\s+/)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n }\n\n /**\n * Merge variable slots in a pattern where consecutive {X} tokens appear.\n * This creates more generalized patterns.\n *\n * @param pattern - Pattern with {X} slots\n * @returns Pattern with merged consecutive variable slots\n */\n mergeConsecutiveVariables(pattern: string): string {\n return pattern.replace(/(\\{X\\}\\s*)+/g, '{X} ').trim();\n }\n\n /**\n * Calculate the specificity of a pattern.\n * Higher values indicate more specific (less generalized) patterns.\n *\n * @param pattern - Pattern to analyze\n * @returns Specificity score (0-1)\n */\n calculatePatternSpecificity(pattern: string): number {\n const tokens = pattern.split(' ');\n const fixedTokens = tokens.filter((t) => t !== '{X}').length;\n return fixedTokens / tokens.length;\n }\n}\n","/**\n * Rule Evaluator\n *\n * Evaluates consolidation rule conditions against entities.\n * Supports AND/OR logic and caches evaluation results for performance.\n *\n * @module agent/RuleEvaluator\n */\n\nimport type {\n AgentEntity,\n RuleConditions,\n RuleEvaluationResult,\n} from '../types/agent-memory.js';\n\n/**\n * Evaluates rule conditions against entities.\n *\n * @example\n * ```typescript\n * const evaluator = new RuleEvaluator();\n * const result = evaluator.evaluate(entity, {\n * minConfidence: 0.8,\n * minConfirmations: 2,\n * memoryType: 'working',\n * });\n * console.log(`Passed: ${result.passed}`);\n * ```\n */\nexport class RuleEvaluator {\n private cache = new Map<string, RuleEvaluationResult>();\n\n /**\n * Evaluate conditions against an entity.\n *\n * @param entity - Entity to evaluate\n * @param conditions - Conditions to check\n * @returns Evaluation result with details\n */\n evaluate(entity: AgentEntity, conditions: RuleConditions): RuleEvaluationResult {\n const cacheKey = `${entity.name}:${entity.lastModified}:${JSON.stringify(conditions)}`;\n const cached = this.cache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n const details: Record<string, boolean> = {};\n const useAnd = conditions.useAnd !== false;\n\n // Check each condition\n if (conditions.minConfidence !== undefined) {\n details.minConfidence = (entity.confidence ?? 0) >= conditions.minConfidence;\n }\n\n if (conditions.minConfirmations !== undefined) {\n details.minConfirmations =\n (entity.confirmationCount ?? 0) >= conditions.minConfirmations;\n }\n\n if (conditions.minAccessCount !== undefined) {\n details.minAccessCount = (entity.accessCount ?? 0) >= conditions.minAccessCount;\n }\n\n if (conditions.memoryType !== undefined) {\n details.memoryType = entity.memoryType === conditions.memoryType;\n }\n\n if (conditions.entityType !== undefined) {\n details.entityType = entity.entityType === conditions.entityType;\n }\n\n if (conditions.minAgeHours !== undefined) {\n const ageHours = this.calculateAgeHours(entity);\n details.minAgeHours = ageHours >= conditions.minAgeHours;\n }\n\n // Combine results based on AND/OR logic\n const values = Object.values(details);\n const passed =\n values.length === 0\n ? true\n : useAnd\n ? values.every((v) => v)\n : values.some((v) => v);\n\n const result: RuleEvaluationResult = { passed, details };\n this.cache.set(cacheKey, result);\n return result;\n }\n\n /**\n * Calculate entity age in hours.\n *\n * @param entity - Entity to calculate age for\n * @returns Age in hours\n */\n private calculateAgeHours(entity: AgentEntity): number {\n const created = entity.createdAt\n ? new Date(entity.createdAt).getTime()\n : Date.now();\n return (Date.now() - created) / (1000 * 60 * 60);\n }\n\n /**\n * Clear the evaluation cache.\n */\n clearCache(): void {\n this.cache.clear();\n }\n\n /**\n * Get cache size for monitoring.\n */\n getCacheSize(): number {\n return this.cache.size;\n }\n}\n","/**\n * Consolidation Pipeline\n *\n * Orchestrates memory transformation from working to long-term storage.\n * Includes summarization, pattern extraction, and promotion stages.\n *\n * @module agent/ConsolidationPipeline\n */\n\nimport type { IGraphStorage, Entity } from '../types/types.js';\nimport type {\n AgentEntity,\n ConsolidateOptions,\n ConsolidationResult,\n SummarizationResult,\n PatternResult,\n MemoryType,\n MemoryMergeStrategy,\n MergeResult,\n DuplicatePair,\n ConsolidationTrigger,\n ConsolidationRule,\n} from '../types/agent-memory.js';\nimport { isAgentEntity } from '../types/agent-memory.js';\nimport type { WorkingMemoryManager } from './WorkingMemoryManager.js';\nimport type { DecayEngine } from './DecayEngine.js';\nimport { SummarizationService } from './SummarizationService.js';\nimport { PatternDetector } from './PatternDetector.js';\nimport { RuleEvaluator } from './RuleEvaluator.js';\n\n/**\n * Configuration for ConsolidationPipeline.\n */\nexport interface ConsolidationPipelineConfig {\n /** Enable observation summarization (default: true) */\n summarizationEnabled?: boolean;\n /** Enable pattern extraction (default: true) */\n patternExtractionEnabled?: boolean;\n /** Minimum confidence for promotion (default: 0.7) */\n minPromotionConfidence?: number;\n /** Minimum confirmations for promotion (default: 2) */\n minPromotionConfirmations?: number;\n /** Preserve originals after promotion (default: false) */\n preserveOriginals?: boolean;\n /** Similarity threshold for observation grouping (default: 0.8) */\n similarityThreshold?: number;\n}\n\n/**\n * Interface for pluggable pipeline stages.\n */\nexport interface PipelineStage {\n /** Stage name for logging/debugging */\n name: string;\n /** Process entities through this stage */\n process(\n entities: AgentEntity[],\n options: ConsolidateOptions\n ): Promise<StageResult>;\n}\n\n/**\n * Result from a single pipeline stage.\n */\nexport interface StageResult {\n /** Number of entities processed */\n processed: number;\n /** Number of entities transformed */\n transformed: number;\n /** Error messages from this stage */\n errors: string[];\n}\n\n/**\n * Orchestrates memory consolidation from working to long-term storage.\n *\n * ConsolidationPipeline is the central coordinator for transforming\n * working memories into long-term storage. It evaluates promotion\n * criteria, runs through pluggable pipeline stages, and tracks\n * results across all operations.\n *\n * @example\n * ```typescript\n * const pipeline = new ConsolidationPipeline(storage, wmm, decay);\n *\n * // Consolidate all memories from a session\n * const result = await pipeline.consolidateSession('session_123');\n * console.log(`Promoted ${result.memoriesPromoted} memories`);\n *\n * // Promote a specific memory\n * const promoted = await pipeline.promoteMemory('memory_abc', 'semantic');\n *\n * // Register custom pipeline stage\n * pipeline.registerStage({\n * name: 'custom_stage',\n * async process(entities, options) {\n * // Custom processing logic\n * return { processed: entities.length, transformed: 0, errors: [] };\n * }\n * });\n * ```\n */\nexport class ConsolidationPipeline {\n private readonly storage: IGraphStorage;\n private readonly workingMemory: WorkingMemoryManager;\n private readonly decayEngine: DecayEngine;\n private readonly summarizationService: SummarizationService;\n private readonly config: Required<ConsolidationPipelineConfig>;\n private readonly stages: PipelineStage[] = [];\n private readonly patternDetector: PatternDetector;\n private readonly ruleEvaluator: RuleEvaluator;\n private readonly rules: ConsolidationRule[] = [];\n\n constructor(\n storage: IGraphStorage,\n workingMemory: WorkingMemoryManager,\n decayEngine: DecayEngine,\n config: ConsolidationPipelineConfig = {}\n ) {\n this.storage = storage;\n this.workingMemory = workingMemory;\n this.decayEngine = decayEngine;\n this.summarizationService = new SummarizationService({\n defaultSimilarityThreshold: config.similarityThreshold ?? 0.8,\n });\n this.config = {\n summarizationEnabled: config.summarizationEnabled ?? true,\n patternExtractionEnabled: config.patternExtractionEnabled ?? true,\n minPromotionConfidence: config.minPromotionConfidence ?? 0.7,\n minPromotionConfirmations: config.minPromotionConfirmations ?? 2,\n preserveOriginals: config.preserveOriginals ?? false,\n similarityThreshold: config.similarityThreshold ?? 0.8,\n };\n this.patternDetector = new PatternDetector();\n this.ruleEvaluator = new RuleEvaluator();\n }\n\n // ==================== Stage Registration ====================\n\n /**\n * Register a pipeline stage.\n *\n * Stages are executed in registration order during consolidation.\n * Each stage can transform entities and report results.\n *\n * @param stage - Pipeline stage to register\n */\n registerStage(stage: PipelineStage): void {\n this.stages.push(stage);\n }\n\n /**\n * Get registered pipeline stages.\n */\n getStages(): readonly PipelineStage[] {\n return this.stages;\n }\n\n /**\n * Clear all registered stages.\n */\n clearStages(): void {\n this.stages.length = 0;\n }\n\n // ==================== Session Consolidation ====================\n\n /**\n * Consolidate all memories from a session.\n *\n * Processes all working memories for the session, evaluates them\n * against promotion criteria, runs through pipeline stages, and\n * promotes eligible memories to long-term storage.\n *\n * @param sessionId - Session to consolidate\n * @param options - Consolidation options\n * @returns Consolidation result with statistics\n *\n * @example\n * ```typescript\n * // Default consolidation\n * const result = await pipeline.consolidateSession('session_123');\n *\n * // Custom options\n * const result = await pipeline.consolidateSession('session_123', {\n * minConfidence: 0.9,\n * targetType: 'semantic',\n * preserveOriginals: true,\n * });\n * ```\n */\n async consolidateSession(\n sessionId: string,\n options?: ConsolidateOptions\n ): Promise<ConsolidationResult> {\n const result: ConsolidationResult = {\n memoriesProcessed: 0,\n memoriesPromoted: 0,\n memoriesMerged: 0,\n patternsExtracted: 0,\n summariesCreated: 0,\n errors: [],\n };\n\n try {\n // Get all working memories for session\n const memories = await this.workingMemory.getSessionMemories(sessionId);\n result.memoriesProcessed = memories.length;\n\n if (memories.length === 0) {\n return result;\n }\n\n // Merge options with config defaults\n const effectiveOptions: Required<ConsolidateOptions> = {\n summarize: options?.summarize ?? this.config.summarizationEnabled,\n extractPatterns:\n options?.extractPatterns ?? this.config.patternExtractionEnabled,\n minConfidence:\n options?.minConfidence ?? this.config.minPromotionConfidence,\n minConfirmations:\n options?.minConfirmations ?? this.config.minPromotionConfirmations,\n preserveOriginals:\n options?.preserveOriginals ?? this.config.preserveOriginals,\n targetType: options?.targetType ?? 'episodic',\n };\n\n // Filter promotion candidates\n const candidates = memories.filter(\n (m) =>\n (m.confidence ?? 0) >= effectiveOptions.minConfidence &&\n (m.confirmationCount ?? 0) >= effectiveOptions.minConfirmations\n );\n\n // Run through registered pipeline stages\n for (const stage of this.stages) {\n try {\n const stageResult = await stage.process(candidates, effectiveOptions);\n // Aggregate stage results\n result.patternsExtracted += stageResult.transformed;\n result.errors.push(...stageResult.errors);\n } catch (error) {\n result.errors.push(`Stage ${stage.name} failed: ${error}`);\n }\n }\n\n // Promote eligible memories\n for (const candidate of candidates) {\n try {\n await this.promoteMemory(candidate.name, effectiveOptions.targetType);\n result.memoriesPromoted++;\n } catch (error) {\n result.errors.push(`Promotion failed for ${candidate.name}: ${error}`);\n }\n }\n\n // Clean up originals if not preserving\n // Working memories will expire naturally via TTL,\n // but we could actively delete them here if needed\n } catch (error) {\n result.errors.push(`Session consolidation failed: ${error}`);\n }\n\n return result;\n }\n\n // ==================== Individual Memory Promotion ====================\n\n /**\n * Promote a working memory to long-term storage.\n *\n * Updates the memory type, clears working memory fields,\n * sets promotion metadata, and reinforces against decay.\n *\n * @param entityName - Name of entity to promote\n * @param targetType - Target memory type (episodic or semantic)\n * @returns Updated entity\n * @throws Error if entity not found or not working memory\n *\n * @example\n * ```typescript\n * // Promote to episodic (default - preserves temporal context)\n * const episodic = await pipeline.promoteMemory('memory_123', 'episodic');\n *\n * // Promote to semantic (abstracts away temporal context)\n * const semantic = await pipeline.promoteMemory('memory_456', 'semantic');\n * ```\n */\n async promoteMemory(\n entityName: string,\n targetType: MemoryType\n ): Promise<AgentEntity> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity) {\n throw new Error(`Entity not found: ${entityName}`);\n }\n\n if (!isAgentEntity(entity)) {\n throw new Error(`Entity is not an AgentEntity: ${entityName}`);\n }\n\n const agentEntity = entity as AgentEntity;\n if (agentEntity.memoryType !== 'working') {\n throw new Error(`Entity is not working memory: ${entityName}`);\n }\n\n const now = new Date().toISOString();\n const updates: Partial<AgentEntity> = {\n // Change memory type\n memoryType: targetType,\n\n // Clear working memory fields\n isWorkingMemory: false,\n expiresAt: undefined,\n\n // Set promotion metadata\n promotedAt: now,\n promotedFrom: agentEntity.sessionId,\n markedForPromotion: false,\n\n // Update timestamp\n lastModified: now,\n };\n\n // Type-specific processing\n if (targetType === 'semantic') {\n // For semantic memory, we might want to:\n // - Abstract away session-specific context\n // - Generalize observations\n // This is typically handled by summarization stages\n } else if (targetType === 'episodic') {\n // For episodic memory:\n // - Preserve temporal context\n // - Keep sessionId and timestamps intact\n // This is the default behavior\n }\n\n await this.storage.updateEntity(entityName, updates as Partial<Entity>);\n\n // Reinforce the memory to reset decay\n await this.decayEngine.reinforceMemory(entityName);\n\n return { ...agentEntity, ...updates } as AgentEntity;\n }\n\n // ==================== Batch Operations ====================\n\n /**\n * Consolidate multiple sessions.\n *\n * @param sessionIds - Sessions to consolidate\n * @param options - Shared options for all sessions\n * @returns Aggregated consolidation result\n */\n async consolidateSessions(\n sessionIds: string[],\n options?: ConsolidateOptions\n ): Promise<ConsolidationResult> {\n const aggregatedResult: ConsolidationResult = {\n memoriesProcessed: 0,\n memoriesPromoted: 0,\n memoriesMerged: 0,\n patternsExtracted: 0,\n summariesCreated: 0,\n errors: [],\n };\n\n for (const sessionId of sessionIds) {\n const result = await this.consolidateSession(sessionId, options);\n aggregatedResult.memoriesProcessed += result.memoriesProcessed;\n aggregatedResult.memoriesPromoted += result.memoriesPromoted;\n aggregatedResult.memoriesMerged += result.memoriesMerged;\n aggregatedResult.patternsExtracted += result.patternsExtracted;\n aggregatedResult.summariesCreated += result.summariesCreated;\n aggregatedResult.errors.push(...result.errors);\n }\n\n return aggregatedResult;\n }\n\n // ==================== Candidate Evaluation ====================\n\n /**\n * Get promotion candidates from a session without promoting them.\n *\n * @param sessionId - Session to evaluate\n * @param options - Criteria for candidate selection\n * @returns Entities eligible for promotion\n */\n async getPromotionCandidates(\n sessionId: string,\n options?: Pick<ConsolidateOptions, 'minConfidence' | 'minConfirmations'>\n ): Promise<AgentEntity[]> {\n const memories = await this.workingMemory.getSessionMemories(sessionId);\n\n const minConfidence =\n options?.minConfidence ?? this.config.minPromotionConfidence;\n const minConfirmations =\n options?.minConfirmations ?? this.config.minPromotionConfirmations;\n\n return memories.filter(\n (m) =>\n (m.confidence ?? 0) >= minConfidence &&\n (m.confirmationCount ?? 0) >= minConfirmations\n );\n }\n\n /**\n * Check if an entity is eligible for promotion.\n *\n * @param entityName - Entity to check\n * @param options - Criteria for eligibility\n * @returns True if eligible\n */\n async isPromotionEligible(\n entityName: string,\n options?: Pick<ConsolidateOptions, 'minConfidence' | 'minConfirmations'>\n ): Promise<boolean> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity || !isAgentEntity(entity)) {\n return false;\n }\n\n const agentEntity = entity as AgentEntity;\n if (agentEntity.memoryType !== 'working') {\n return false;\n }\n\n const minConfidence =\n options?.minConfidence ?? this.config.minPromotionConfidence;\n const minConfirmations =\n options?.minConfirmations ?? this.config.minPromotionConfirmations;\n\n return (\n (agentEntity.confidence ?? 0) >= minConfidence &&\n (agentEntity.confirmationCount ?? 0) >= minConfirmations\n );\n }\n\n // ==================== Observation Summarization ====================\n\n /**\n * Summarize similar observations in an entity.\n *\n * Groups observations by similarity and creates summaries\n * for each group. Single-observation groups are preserved unchanged.\n *\n * @param entity - Entity whose observations to summarize\n * @param threshold - Similarity threshold (0-1, default from config)\n * @returns Summarization result with compression statistics\n *\n * @example\n * ```typescript\n * const result = await pipeline.summarizeObservations(entity);\n * console.log(`Compressed ${result.originalCount} to ${result.summaryCount}`);\n * console.log(`Compression ratio: ${result.compressionRatio.toFixed(2)}x`);\n * ```\n */\n async summarizeObservations(\n entity: AgentEntity,\n threshold?: number\n ): Promise<SummarizationResult> {\n const observations = entity.observations;\n if (!observations || observations.length < 2) {\n return {\n originalCount: observations?.length ?? 0,\n summaryCount: observations?.length ?? 0,\n compressionRatio: 1,\n summaries: observations ? [...observations] : [],\n sourceObservations: observations ? observations.map((o) => [o]) : [],\n };\n }\n\n const effectiveThreshold = threshold ?? this.config.similarityThreshold;\n\n // Group similar observations\n const groupingResult = await this.summarizationService.groupSimilarObservations(\n observations,\n effectiveThreshold\n );\n\n // Summarize each group\n const summaries = await this.summarizationService.summarizeGroups(\n groupingResult.groups\n );\n\n return {\n originalCount: observations.length,\n summaryCount: summaries.length,\n compressionRatio: observations.length / summaries.length,\n summaries,\n sourceObservations: groupingResult.groups,\n };\n }\n\n /**\n * Apply summarization to entity and update storage.\n *\n * @param entityName - Name of entity to summarize\n * @param threshold - Similarity threshold (optional)\n * @returns Summarization result\n */\n async applySummarizationToEntity(\n entityName: string,\n threshold?: number\n ): Promise<SummarizationResult> {\n const entity = this.storage.getEntityByName(entityName);\n if (!entity || !isAgentEntity(entity)) {\n return {\n originalCount: 0,\n summaryCount: 0,\n compressionRatio: 1,\n summaries: [],\n sourceObservations: [],\n };\n }\n\n const agentEntity = entity as AgentEntity;\n const result = await this.summarizeObservations(agentEntity, threshold);\n\n // Update entity with summarized observations\n if (result.compressionRatio > 1) {\n await this.storage.updateEntity(entityName, {\n observations: result.summaries,\n lastModified: new Date().toISOString(),\n } as Partial<Entity>);\n }\n\n return result;\n }\n\n /**\n * Calculate similarity between two texts.\n *\n * @param text1 - First text\n * @param text2 - Second text\n * @returns Similarity score (0-1)\n */\n calculateSimilarity(text1: string, text2: string): number {\n return this.summarizationService.calculateSimilarity(text1, text2);\n }\n\n /**\n * Get the summarization service for advanced operations.\n */\n getSummarizationService(): SummarizationService {\n return this.summarizationService;\n }\n\n // ==================== Pattern Extraction ====================\n\n /**\n * Extract recurring patterns from observations across entities.\n *\n * Analyzes observations in entities of the specified type to identify\n * common templates with variable slots. Useful for discovering\n * generalizations that can be converted to semantic memory.\n *\n * @param entityType - Type of entities to analyze\n * @param minOccurrences - Minimum times pattern must appear (default: 3)\n * @returns Array of detected patterns\n *\n * @example\n * ```typescript\n * // Find patterns in preference entities\n * const patterns = await pipeline.extractPatterns('preference', 3);\n * for (const p of patterns) {\n * console.log(`Pattern: ${p.pattern}`);\n * console.log(`Values: ${p.variables.join(', ')}`);\n * }\n * ```\n */\n async extractPatterns(\n entityType: string,\n minOccurrences: number = 3\n ): Promise<PatternResult[]> {\n const graph = await this.storage.loadGraph();\n const observations: string[] = [];\n const entityNames: string[] = [];\n\n // Collect all observations from matching entities\n for (const entity of graph.entities) {\n if (entity.entityType === entityType && entity.observations) {\n observations.push(...entity.observations);\n for (let i = 0; i < entity.observations.length; i++) {\n entityNames.push(entity.name);\n }\n }\n }\n\n if (observations.length < minOccurrences) {\n return [];\n }\n\n // Detect patterns using PatternDetector\n const patterns = this.patternDetector.detectPatterns(observations, minOccurrences);\n\n // Associate patterns with source entities\n return patterns.map((pattern) => {\n const sourceEntities = new Set<string>();\n const regex = this.patternToRegex(pattern.pattern);\n\n for (let i = 0; i < observations.length; i++) {\n if (regex.test(observations[i])) {\n sourceEntities.add(entityNames[i]);\n }\n }\n\n return {\n ...pattern,\n sourceEntities: Array.from(sourceEntities),\n };\n });\n }\n\n /**\n * Create a semantic memory entity from a detected pattern.\n *\n * Converts a pattern template into a semantic memory that represents\n * the generalization, with relations to source entities.\n *\n * @param pattern - The detected pattern\n * @param sourceEntityNames - Names of entities that contributed to pattern\n * @returns Created semantic memory entity\n *\n * @example\n * ```typescript\n * const patterns = await pipeline.extractPatterns('preference', 3);\n * for (const p of patterns) {\n * const semantic = await pipeline.createSemanticFromPattern(p, p.sourceEntities);\n * console.log(`Created semantic memory: ${semantic.name}`);\n * }\n * ```\n */\n async createSemanticFromPattern(\n pattern: PatternResult,\n sourceEntityNames: string[]\n ): Promise<AgentEntity> {\n const now = new Date().toISOString();\n const name = `semantic_pattern_${Date.now()}_${this.hashPattern(pattern.pattern)}`;\n\n const entity: AgentEntity = {\n name,\n entityType: 'pattern',\n observations: [\n `Pattern: ${pattern.pattern}`,\n `Known values: ${pattern.variables.join(', ')}`,\n `Observed ${pattern.occurrences} times`,\n ],\n createdAt: now,\n lastModified: now,\n importance: 7, // Patterns are generally important\n memoryType: 'semantic',\n accessCount: 0,\n confidence: pattern.confidence,\n confirmationCount: pattern.occurrences,\n visibility: 'private',\n };\n\n await this.storage.appendEntity(entity as Entity);\n\n // Create relations to source entities\n const graph = await this.storage.loadGraph();\n const existingRelations = new Set(\n graph.relations.map((r) => `${r.from}:${r.to}:${r.relationType}`)\n );\n\n for (const sourceName of sourceEntityNames) {\n const relationKey = `${name}:${sourceName}:derived_from`;\n if (!existingRelations.has(relationKey)) {\n await this.storage.appendRelation({\n from: name,\n to: sourceName,\n relationType: 'derived_from',\n });\n }\n }\n\n return entity;\n }\n\n /**\n * Extract patterns and create semantic memories for significant ones.\n *\n * Combines pattern extraction and semantic memory creation in one operation.\n *\n * @param entityType - Type of entities to analyze\n * @param minOccurrences - Minimum pattern frequency\n * @param minConfidence - Minimum confidence for semantic creation (default: 0.5)\n * @returns Array of created semantic memory entities\n */\n async extractAndCreateSemanticPatterns(\n entityType: string,\n minOccurrences: number = 3,\n minConfidence: number = 0.5\n ): Promise<AgentEntity[]> {\n const patterns = await this.extractPatterns(entityType, minOccurrences);\n const semanticEntities: AgentEntity[] = [];\n\n for (const pattern of patterns) {\n if (pattern.confidence >= minConfidence) {\n const semantic = await this.createSemanticFromPattern(\n pattern,\n pattern.sourceEntities\n );\n semanticEntities.push(semantic);\n }\n }\n\n return semanticEntities;\n }\n\n /**\n * Get the pattern detector for advanced operations.\n */\n getPatternDetector(): PatternDetector {\n return this.patternDetector;\n }\n\n /**\n * Convert a pattern template to a regex for matching.\n * @internal\n */\n private patternToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\\\{X\\\\}/g, '.+?');\n return new RegExp(`^${escaped}$`, 'i');\n }\n\n /**\n * Create a simple hash of a pattern for unique naming.\n * @internal\n */\n private hashPattern(pattern: string): string {\n let hash = 0;\n for (let i = 0; i < pattern.length; i++) {\n hash = ((hash << 5) - hash) + pattern.charCodeAt(i);\n hash = hash & hash;\n }\n return Math.abs(hash).toString(16).slice(0, 8);\n }\n\n // ==================== Memory Merging ====================\n\n /**\n * Merge multiple entities into one using the specified strategy.\n *\n * Strategies:\n * - newest: Keep the most recently modified entity\n * - strongest: Keep entity with highest confidence * confirmations\n * - merge_observations: Combine all observations into first entity\n *\n * @param entityNames - Names of entities to merge\n * @param strategy - Merge strategy to apply\n * @returns The merge result with survivor entity\n * @throws Error if less than 2 entities or entity not found\n *\n * @example\n * ```typescript\n * const result = await pipeline.mergeMemories(\n * ['memory_1', 'memory_2', 'memory_3'],\n * 'strongest'\n * );\n * console.log(`Survivor: ${result.survivor.name}`);\n * ```\n */\n async mergeMemories(\n entityNames: string[],\n strategy: MemoryMergeStrategy\n ): Promise<MergeResult> {\n if (entityNames.length < 2) {\n throw new Error('Need at least 2 entities to merge');\n }\n\n // Load all entities\n const entities: AgentEntity[] = [];\n for (const name of entityNames) {\n const entity = this.storage.getEntityByName(name);\n if (!entity || !isAgentEntity(entity)) {\n throw new Error(`Entity not found or not AgentEntity: ${name}`);\n }\n entities.push(entity as AgentEntity);\n }\n\n // Determine survivor based on strategy\n let survivor: AgentEntity;\n switch (strategy) {\n case 'newest':\n survivor = this.selectNewest(entities);\n break;\n case 'strongest':\n survivor = this.selectStrongest(entities);\n break;\n case 'merge_observations':\n default:\n survivor = entities[0];\n break;\n }\n\n // Merge observations from all entities into survivor\n const allObservations = new Set<string>();\n for (const entity of entities) {\n entity.observations?.forEach((o) => allObservations.add(o));\n }\n\n const mergedObservations = Array.from(allObservations);\n\n // Calculate merged metadata\n const updates: Partial<AgentEntity> = {\n observations: mergedObservations,\n confirmationCount: entities.reduce(\n (sum, e) => sum + (e.confirmationCount ?? 0),\n 0\n ),\n accessCount: entities.reduce((sum, e) => sum + (e.accessCount ?? 0), 0),\n lastModified: new Date().toISOString(),\n };\n\n // Update survivor\n await this.storage.updateEntity(survivor.name, updates as Partial<Entity>);\n\n // Record merge in audit trail\n await this.recordMerge(entityNames, survivor.name, strategy);\n\n // Remove other entities (delete them from storage)\n const graph = await this.storage.loadGraph();\n const mergedNames = entityNames.filter((n) => n !== survivor.name);\n\n // Filter out merged entities and their relations\n const filteredEntities = graph.entities.filter(\n (e) => !mergedNames.includes(e.name)\n );\n\n // Retarget relations\n const seenRelations = new Set<string>();\n const filteredRelations = graph.relations\n .map((r) => {\n let from = r.from;\n let to = r.to;\n\n // Retarget from merged entities\n if (mergedNames.includes(from)) from = survivor.name;\n if (mergedNames.includes(to)) to = survivor.name;\n\n return { ...r, from, to };\n })\n .filter((r) => {\n // Skip self-relations\n if (r.from === r.to) return false;\n\n // Skip duplicates\n const key = `${r.from}|${r.to}|${r.relationType}`;\n if (seenRelations.has(key)) return false;\n seenRelations.add(key);\n\n return true;\n });\n\n await this.storage.saveGraph({\n entities: filteredEntities,\n relations: filteredRelations,\n });\n\n return {\n survivor: { ...survivor, ...updates } as AgentEntity,\n mergedEntities: entityNames,\n mergedCount: entityNames.length,\n strategy,\n observationCount: mergedObservations.length,\n };\n }\n\n /**\n * Find potential duplicate entities based on similarity.\n *\n * @param threshold - Similarity threshold (0-1, default 0.9)\n * @returns Array of duplicate pairs sorted by similarity\n *\n * @example\n * ```typescript\n * const duplicates = await pipeline.findDuplicates(0.85);\n * for (const dup of duplicates) {\n * console.log(`${dup.entity1} ~ ${dup.entity2}: ${dup.similarity}`);\n * }\n * ```\n */\n async findDuplicates(threshold: number = 0.9): Promise<DuplicatePair[]> {\n const graph = await this.storage.loadGraph();\n const duplicates: DuplicatePair[] = [];\n\n const agentEntities = graph.entities.filter((e) =>\n isAgentEntity(e)\n ) as AgentEntity[];\n\n for (let i = 0; i < agentEntities.length; i++) {\n for (let j = i + 1; j < agentEntities.length; j++) {\n const e1 = agentEntities[i];\n const e2 = agentEntities[j];\n\n // Skip if different sessions for working memories\n if (\n e1.memoryType === 'working' &&\n e2.memoryType === 'working' &&\n e1.sessionId !== e2.sessionId\n ) {\n continue;\n }\n\n const similarity = this.calculateEntitySimilarity(e1, e2);\n if (similarity >= threshold) {\n duplicates.push({\n entity1: e1.name,\n entity2: e2.name,\n similarity,\n });\n }\n }\n }\n\n return duplicates.sort((a, b) => b.similarity - a.similarity);\n }\n\n /**\n * Auto-merge duplicates above threshold.\n *\n * @param threshold - Similarity threshold for duplicates\n * @param strategy - Merge strategy to use\n * @returns Array of merge results\n */\n async autoMergeDuplicates(\n threshold: number = 0.9,\n strategy: MemoryMergeStrategy = 'strongest'\n ): Promise<MergeResult[]> {\n const duplicates = await this.findDuplicates(threshold);\n const results: MergeResult[] = [];\n const mergedEntities = new Set<string>();\n\n for (const dup of duplicates) {\n // Skip if either entity was already merged\n if (mergedEntities.has(dup.entity1) || mergedEntities.has(dup.entity2)) {\n continue;\n }\n\n try {\n const result = await this.mergeMemories(\n [dup.entity1, dup.entity2],\n strategy\n );\n results.push(result);\n\n // Track merged entities\n mergedEntities.add(dup.entity1);\n mergedEntities.add(dup.entity2);\n } catch {\n // Entity may have been deleted in previous merge\n continue;\n }\n }\n\n return results;\n }\n\n /**\n * Get merge history for an entity.\n *\n * @param entityName - Name of entity to check history for\n * @returns Array of merge audit entities\n */\n async getMergeHistory(entityName: string): Promise<Entity[]> {\n const graph = await this.storage.loadGraph();\n return graph.entities.filter(\n (e) =>\n e.entityType === 'merge_audit' &&\n e.observations?.some((o) => o.includes(entityName))\n );\n }\n\n /**\n * Select the newest entity from a list.\n * @internal\n */\n private selectNewest(entities: AgentEntity[]): AgentEntity {\n return entities.reduce((newest, e) => {\n const newestTime = new Date(\n newest.lastModified ?? newest.createdAt ?? 0\n ).getTime();\n const eTime = new Date(e.lastModified ?? e.createdAt ?? 0).getTime();\n return eTime > newestTime ? e : newest;\n });\n }\n\n /**\n * Select the strongest entity from a list.\n * @internal\n */\n private selectStrongest(entities: AgentEntity[]): AgentEntity {\n return entities.reduce((strongest, e) => {\n const strongestScore =\n (strongest.confidence ?? 0) * (strongest.confirmationCount ?? 1);\n const eScore = (e.confidence ?? 0) * (e.confirmationCount ?? 1);\n return eScore > strongestScore ? e : strongest;\n });\n }\n\n /**\n * Calculate similarity between two entities.\n * @internal\n */\n private calculateEntitySimilarity(\n e1: AgentEntity,\n e2: AgentEntity\n ): number {\n // Must have same entity type\n if (e1.entityType !== e2.entityType) return 0;\n\n // Compare observations\n const obs1 = e1.observations?.join(' ') ?? '';\n const obs2 = e2.observations?.join(' ') ?? '';\n\n return this.calculateSimilarity(obs1, obs2);\n }\n\n /**\n * Record a merge operation in the audit trail.\n * @internal\n */\n private async recordMerge(\n mergedNames: string[],\n survivorName: string,\n strategy: MemoryMergeStrategy\n ): Promise<void> {\n const now = new Date().toISOString();\n const auditEntity = {\n name: `merge_audit_${Date.now()}`,\n entityType: 'merge_audit',\n observations: [\n `Merged: ${mergedNames.join(', ')}`,\n `Survivor: ${survivorName}`,\n `Strategy: ${strategy}`,\n `Timestamp: ${now}`,\n ],\n createdAt: now,\n lastModified: now,\n importance: 3,\n };\n\n await this.storage.appendEntity(auditEntity);\n }\n\n // ==================== Configuration Access ====================\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<ConsolidationPipelineConfig>> {\n return { ...this.config };\n }\n\n // ==================== Rule Management ====================\n\n /**\n * Add a consolidation rule.\n *\n * @param rule - Rule to add\n *\n * @example\n * ```typescript\n * pipeline.addRule({\n * name: 'Promote high-confidence memories',\n * trigger: 'session_end',\n * conditions: { minConfidence: 0.9, memoryType: 'working' },\n * action: 'promote_to_episodic',\n * enabled: true,\n * });\n * ```\n */\n addRule(rule: ConsolidationRule): void {\n this.rules.push(rule);\n }\n\n /**\n * Remove a rule by name.\n *\n * @param name - Name of rule to remove\n * @returns true if rule was found and removed\n */\n removeRule(name: string): boolean {\n const index = this.rules.findIndex((r) => r.name === name);\n if (index !== -1) {\n this.rules.splice(index, 1);\n return true;\n }\n return false;\n }\n\n /**\n * Get all registered rules.\n */\n getRules(): readonly ConsolidationRule[] {\n return this.rules;\n }\n\n /**\n * Clear all rules.\n */\n clearRules(): void {\n this.rules.length = 0;\n }\n\n /**\n * Get the RuleEvaluator instance.\n */\n getRuleEvaluator(): RuleEvaluator {\n return this.ruleEvaluator;\n }\n\n // ==================== Auto-Consolidation ====================\n\n /**\n * Run automatic consolidation based on configured rules.\n *\n * Processes all rules matching the given trigger and executes\n * actions for entities that meet the rule conditions.\n *\n * @param trigger - The trigger that invoked consolidation\n * @returns Aggregate result of all rule executions\n *\n * @example\n * ```typescript\n * // Run consolidation triggered by session end\n * const result = await pipeline.runAutoConsolidation('session_end');\n * console.log(`Promoted ${result.memoriesPromoted} memories`);\n *\n * // Manual trigger for testing\n * const manualResult = await pipeline.runAutoConsolidation('manual');\n * ```\n */\n async runAutoConsolidation(\n trigger: ConsolidationTrigger\n ): Promise<ConsolidationResult> {\n const result: ConsolidationResult = {\n memoriesProcessed: 0,\n memoriesPromoted: 0,\n memoriesMerged: 0,\n patternsExtracted: 0,\n summariesCreated: 0,\n errors: [],\n };\n\n // Get rules matching this trigger, sorted by priority\n const matchingRules = this.rules\n .filter((r) => r.enabled && r.trigger === trigger)\n .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));\n\n if (matchingRules.length === 0) {\n return result;\n }\n\n // Get all agent entities\n const graph = await this.storage.loadGraph();\n const entities = graph.entities.filter((e) =>\n isAgentEntity(e)\n ) as AgentEntity[];\n result.memoriesProcessed = entities.length;\n\n // Process each rule\n for (const rule of matchingRules) {\n const ruleResult = await this.executeRule(rule, entities);\n result.memoriesPromoted += ruleResult.promoted;\n result.memoriesMerged += ruleResult.merged;\n result.summariesCreated += ruleResult.summarized;\n result.errors.push(...ruleResult.errors);\n }\n\n // Clear evaluation cache after processing\n this.ruleEvaluator.clearCache();\n\n return result;\n }\n\n /**\n * Execute a single rule against a set of entities.\n *\n * @param rule - Rule to execute\n * @param entities - Entities to evaluate\n * @returns Execution results\n * @internal\n */\n private async executeRule(\n rule: ConsolidationRule,\n entities: AgentEntity[]\n ): Promise<{\n promoted: number;\n merged: number;\n summarized: number;\n errors: string[];\n }> {\n const result = {\n promoted: 0,\n merged: 0,\n summarized: 0,\n errors: [] as string[],\n };\n\n // Find matching entities\n const matches = entities.filter((e) =>\n this.ruleEvaluator.evaluate(e, rule.conditions).passed\n );\n\n // Execute action for matches\n for (const entity of matches) {\n try {\n switch (rule.action) {\n case 'promote_to_episodic':\n if (entity.memoryType === 'working') {\n await this.promoteMemory(entity.name, 'episodic');\n result.promoted++;\n }\n break;\n\n case 'promote_to_semantic':\n if (entity.memoryType === 'working') {\n await this.promoteMemory(entity.name, 'semantic');\n result.promoted++;\n }\n break;\n\n case 'summarize':\n const summarized = await this.applySummarizationToEntity(\n entity.name\n );\n if (summarized.compressionRatio > 0) {\n result.summarized++;\n }\n break;\n\n case 'merge_duplicates':\n // Skip merge_duplicates action for individual entities\n // This action is handled separately in autoMergeDuplicates\n break;\n\n case 'archive':\n // Archive action - mark for archival (future implementation)\n break;\n }\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error);\n result.errors.push(`Rule ${rule.name} failed for ${entity.name}: ${message}`);\n }\n }\n\n // Handle merge_duplicates action at rule level (not per entity)\n if (rule.action === 'merge_duplicates') {\n try {\n const mergeResults = await this.autoMergeDuplicates(0.9, 'strongest');\n result.merged = mergeResults.length;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n result.errors.push(`Rule ${rule.name} merge failed: ${message}`);\n }\n }\n\n return result;\n }\n\n /**\n * Manually trigger consolidation.\n *\n * Convenience method for triggering consolidation without a specific event.\n */\n async triggerManualConsolidation(): Promise<ConsolidationResult> {\n return this.runAutoConsolidation('manual');\n }\n}\n","/**\n * Conflict Resolver\n *\n * Detects and resolves conflicting memories from different agents.\n *\n * @module agent/ConflictResolver\n */\n\nimport type { AgentEntity, ConflictInfo, ConflictStrategy } from '../types/agent-memory.js';\nimport type { AgentMetadata } from '../types/agent-memory.js';\nimport { EventEmitter } from 'events';\n\n/**\n * Configuration for ConflictResolver.\n */\nexport interface ConflictResolverConfig {\n /** Similarity threshold for conflict detection (0-1, default: 0.7) */\n similarityThreshold?: number;\n /** Default resolution strategy */\n defaultStrategy?: ConflictStrategy;\n /** Enable negation detection */\n detectNegations?: boolean;\n}\n\n/**\n * Result of conflict resolution.\n */\nexport interface ResolutionResult {\n /** Resolved memory */\n resolvedMemory: AgentEntity;\n /** Strategy used */\n strategy: ConflictStrategy;\n /** Memories that were merged/resolved */\n sourceMemories: string[];\n /** Audit trail entry */\n auditEntry: string;\n}\n\n/**\n * Resolves conflicts between memories from different agents.\n *\n * @example\n * ```typescript\n * const resolver = new ConflictResolver();\n * const conflicts = resolver.detectConflicts(memories);\n * const resolved = resolver.resolveConflict(conflictInfo, memories, agentMetadata, 'trusted_agent');\n * ```\n */\nexport class ConflictResolver extends EventEmitter {\n private readonly config: Required<ConflictResolverConfig>;\n\n constructor(config: ConflictResolverConfig = {}) {\n super();\n this.config = {\n similarityThreshold: config.similarityThreshold ?? 0.7,\n defaultStrategy: config.defaultStrategy ?? 'most_recent',\n detectNegations: config.detectNegations ?? true,\n };\n }\n\n /**\n * Detect conflicts between memories.\n *\n * Compares memories from different agents and identifies contradictions\n * based on observation similarity and negation patterns.\n *\n * @param memories - Memories to check for conflicts\n * @returns Array of detected conflicts\n */\n detectConflicts(memories: AgentEntity[]): ConflictInfo[] {\n const conflicts: ConflictInfo[] = [];\n const checked = new Set<string>();\n\n for (let i = 0; i < memories.length; i++) {\n for (let j = i + 1; j < memories.length; j++) {\n const m1 = memories[i];\n const m2 = memories[j];\n\n // Skip same-agent memories\n if (m1.agentId === m2.agentId) continue;\n\n // Create unique pair key\n const pairKey = [m1.name, m2.name].sort().join('::');\n if (checked.has(pairKey)) continue;\n checked.add(pairKey);\n\n // Check for conflict\n const conflictResult = this.checkConflict(m1, m2);\n if (conflictResult) {\n conflicts.push(conflictResult);\n\n // Emit conflict event\n this.emit('memory:conflict', conflictResult);\n }\n }\n }\n\n return conflicts;\n }\n\n /**\n * Check if two memories conflict.\n * @internal\n */\n private checkConflict(m1: AgentEntity, m2: AgentEntity): ConflictInfo | null {\n // Check similarity\n const similarity = this.calculateSimilarity(m1, m2);\n\n if (similarity >= this.config.similarityThreshold) {\n // High similarity indicates same topic - check for contradictions\n const hasNegation =\n this.config.detectNegations && this.detectNegation(m1, m2);\n\n if (hasNegation) {\n return {\n primaryMemory: m1.name,\n conflictingMemories: [m2.name],\n detectionMethod: 'negation',\n similarityScore: similarity,\n suggestedStrategy: this.suggestStrategy(m1, m2),\n detectedAt: new Date().toISOString(),\n };\n }\n\n // High similarity with different observations = potential conflict\n if (!this.observationsEqual(m1, m2)) {\n return {\n primaryMemory: m1.name,\n conflictingMemories: [m2.name],\n detectionMethod: 'similarity',\n similarityScore: similarity,\n suggestedStrategy: this.suggestStrategy(m1, m2),\n detectedAt: new Date().toISOString(),\n };\n }\n }\n\n return null;\n }\n\n /**\n * Calculate similarity between two memories using TF-style scoring.\n * @internal\n */\n private calculateSimilarity(m1: AgentEntity, m2: AgentEntity): number {\n // Combine name and observations into text\n const text1 = this.memoryToText(m1).toLowerCase();\n const text2 = this.memoryToText(m2).toLowerCase();\n\n // Simple Jaccard-like similarity\n const words1 = new Set(text1.split(/\\s+/).filter((w) => w.length > 2));\n const words2 = new Set(text2.split(/\\s+/).filter((w) => w.length > 2));\n\n if (words1.size === 0 || words2.size === 0) return 0;\n\n let intersection = 0;\n for (const word of words1) {\n if (words2.has(word)) intersection++;\n }\n\n const union = words1.size + words2.size - intersection;\n return union > 0 ? intersection / union : 0;\n }\n\n /**\n * Convert memory to text for comparison.\n * @internal\n */\n private memoryToText(memory: AgentEntity): string {\n const parts = [memory.name, memory.entityType ?? ''];\n if (memory.observations) {\n parts.push(...memory.observations);\n }\n return parts.join(' ');\n }\n\n /**\n * Detect negation patterns in observations.\n * @internal\n */\n private detectNegation(m1: AgentEntity, m2: AgentEntity): boolean {\n const obs1 = (m1.observations ?? []).map((o) => o.toLowerCase());\n const obs2 = (m2.observations ?? []).map((o) => o.toLowerCase());\n\n const negationPatterns = [\n /\\bnot\\b/,\n /\\bno\\b/,\n /\\bnever\\b/,\n /\\bdon't\\b/,\n /\\bdoesn't\\b/,\n /\\bisn't\\b/,\n /\\bwasn't\\b/,\n /\\bweren't\\b/,\n /\\bwon't\\b/,\n /\\bcan't\\b/,\n /\\bcannot\\b/,\n /\\bfalse\\b/,\n /\\bincorrect\\b/,\n ];\n\n // Check if one memory has negation while the other doesn't\n // for similar content\n for (const o1 of obs1) {\n for (const o2 of obs2) {\n const sim = this.textSimilarity(o1, o2);\n if (sim > 0.5) {\n const hasNeg1 = negationPatterns.some((p) => p.test(o1));\n const hasNeg2 = negationPatterns.some((p) => p.test(o2));\n if (hasNeg1 !== hasNeg2) {\n return true;\n }\n }\n }\n }\n\n return false;\n }\n\n /**\n * Simple text similarity.\n * @internal\n */\n private textSimilarity(t1: string, t2: string): number {\n const words1 = new Set(t1.split(/\\s+/).filter((w) => w.length > 2));\n const words2 = new Set(t2.split(/\\s+/).filter((w) => w.length > 2));\n\n if (words1.size === 0 || words2.size === 0) return 0;\n\n let intersection = 0;\n for (const word of words1) {\n if (words2.has(word)) intersection++;\n }\n\n const union = words1.size + words2.size - intersection;\n return union > 0 ? intersection / union : 0;\n }\n\n /**\n * Check if two memories have equal observations.\n * @internal\n */\n private observationsEqual(m1: AgentEntity, m2: AgentEntity): boolean {\n const obs1 = m1.observations ?? [];\n const obs2 = m2.observations ?? [];\n\n if (obs1.length !== obs2.length) return false;\n\n const sorted1 = [...obs1].sort();\n const sorted2 = [...obs2].sort();\n\n return sorted1.every((o, i) => o === sorted2[i]);\n }\n\n /**\n * Suggest the best resolution strategy based on memory properties.\n * @internal\n */\n private suggestStrategy(m1: AgentEntity, m2: AgentEntity): ConflictStrategy {\n // If one has much higher confidence, use highest_confidence\n const conf1 = m1.confidence ?? 0.5;\n const conf2 = m2.confidence ?? 0.5;\n if (Math.abs(conf1 - conf2) > 0.3) {\n return 'highest_confidence';\n }\n\n // If one has more confirmations, use most_confirmations\n const confirm1 = m1.confirmationCount ?? 0;\n const confirm2 = m2.confirmationCount ?? 0;\n if (Math.abs(confirm1 - confirm2) >= 3) {\n return 'most_confirmations';\n }\n\n // Default to most_recent\n return this.config.defaultStrategy;\n }\n\n /**\n * Resolve a conflict using the specified strategy.\n *\n * @param conflict - Conflict information\n * @param memories - All memories involved\n * @param agents - Agent metadata for trust-based resolution\n * @param strategy - Resolution strategy (uses suggested if not specified)\n * @returns Resolution result\n */\n resolveConflict(\n conflict: ConflictInfo,\n memories: AgentEntity[],\n agents: Map<string, AgentMetadata>,\n strategy?: ConflictStrategy\n ): ResolutionResult {\n const resolveStrategy = strategy ?? conflict.suggestedStrategy;\n\n // Gather conflicting memories\n const allNames = [conflict.primaryMemory, ...conflict.conflictingMemories];\n const conflictingMemories = memories.filter((m) =>\n allNames.includes(m.name)\n );\n\n if (conflictingMemories.length === 0) {\n throw new Error('No conflicting memories found');\n }\n\n let resolvedMemory: AgentEntity;\n let auditEntry: string;\n\n switch (resolveStrategy) {\n case 'most_recent':\n resolvedMemory = this.resolveMostRecent(conflictingMemories);\n auditEntry = `Resolved conflict using most_recent strategy. Selected: ${resolvedMemory.name}`;\n break;\n\n case 'highest_confidence':\n resolvedMemory = this.resolveHighestConfidence(conflictingMemories);\n auditEntry = `Resolved conflict using highest_confidence strategy. Selected: ${resolvedMemory.name}`;\n break;\n\n case 'most_confirmations':\n resolvedMemory = this.resolveMostConfirmations(conflictingMemories);\n auditEntry = `Resolved conflict using most_confirmations strategy. Selected: ${resolvedMemory.name}`;\n break;\n\n case 'trusted_agent':\n resolvedMemory = this.resolveTrustedAgent(conflictingMemories, agents);\n auditEntry = `Resolved conflict using trusted_agent strategy. Selected: ${resolvedMemory.name}`;\n break;\n\n case 'merge_all':\n resolvedMemory = this.resolveMergeAll(conflictingMemories);\n auditEntry = `Resolved conflict using merge_all strategy. Merged ${conflictingMemories.length} memories`;\n break;\n\n default:\n throw new Error(`Unknown resolution strategy: ${resolveStrategy}`);\n }\n\n // Emit resolution event\n this.emit('memory:conflict_resolved', {\n conflict,\n strategy: resolveStrategy,\n resolvedMemory: resolvedMemory.name,\n });\n\n return {\n resolvedMemory,\n strategy: resolveStrategy,\n sourceMemories: allNames,\n auditEntry,\n };\n }\n\n /**\n * Resolve by selecting most recently modified memory.\n * @internal\n */\n private resolveMostRecent(memories: AgentEntity[]): AgentEntity {\n return memories.reduce((best, m) => {\n const bestTime = best.lastModified ?? best.createdAt ?? '1970-01-01';\n const mTime = m.lastModified ?? m.createdAt ?? '1970-01-01';\n return mTime > bestTime ? m : best;\n });\n }\n\n /**\n * Resolve by selecting highest confidence memory.\n * @internal\n */\n private resolveHighestConfidence(memories: AgentEntity[]): AgentEntity {\n return memories.reduce((best, m) => {\n const bestConf = best.confidence ?? 0.5;\n const mConf = m.confidence ?? 0.5;\n return mConf > bestConf ? m : best;\n });\n }\n\n /**\n * Resolve by selecting most confirmed memory.\n * @internal\n */\n private resolveMostConfirmations(memories: AgentEntity[]): AgentEntity {\n return memories.reduce((best, m) => {\n const bestConf = best.confirmationCount ?? 0;\n const mConf = m.confirmationCount ?? 0;\n return mConf > bestConf ? m : best;\n });\n }\n\n /**\n * Resolve by selecting memory from most trusted agent.\n * @internal\n */\n private resolveTrustedAgent(\n memories: AgentEntity[],\n agents: Map<string, AgentMetadata>\n ): AgentEntity {\n return memories.reduce((best, m) => {\n const bestAgent = best.agentId ? agents.get(best.agentId) : undefined;\n const mAgent = m.agentId ? agents.get(m.agentId) : undefined;\n const bestTrust = bestAgent?.trustLevel ?? 0.5;\n const mTrust = mAgent?.trustLevel ?? 0.5;\n return mTrust > bestTrust ? m : best;\n });\n }\n\n /**\n * Resolve by merging all observations into one memory.\n * @internal\n */\n private resolveMergeAll(memories: AgentEntity[]): AgentEntity {\n // Use most recent as base\n const base = this.resolveMostRecent(memories);\n\n // Collect unique observations from all memories\n const allObservations = new Set<string>();\n for (const m of memories) {\n if (m.observations) {\n for (const o of m.observations) {\n allObservations.add(o);\n }\n }\n }\n\n // Calculate merged confidence (average)\n const avgConfidence =\n memories.reduce((sum, m) => sum + (m.confidence ?? 0.5), 0) /\n memories.length;\n\n // Sum confirmations\n const totalConfirmations = memories.reduce(\n (sum, m) => sum + (m.confirmationCount ?? 0),\n 0\n );\n\n // Create merged memory\n return {\n ...base,\n observations: Array.from(allObservations),\n confidence: avgConfidence,\n confirmationCount: totalConfirmations,\n lastModified: new Date().toISOString(),\n };\n }\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<ConflictResolverConfig>> {\n return { ...this.config };\n }\n}\n","/**\n * Multi-Agent Memory Manager\n *\n * Manages memory for multiple AI agents with ownership tracking,\n * visibility controls, and cross-agent collaboration.\n *\n * @module agent/MultiAgentMemoryManager\n */\n\nimport type { IGraphStorage, Entity } from '../types/types.js';\nimport type {\n AgentEntity,\n AgentMetadata,\n AgentType,\n MemoryVisibility,\n ConflictStrategy,\n ConflictInfo,\n} from '../types/agent-memory.js';\nimport { isAgentEntity } from '../types/agent-memory.js';\nimport { EventEmitter } from 'events';\nimport { ConflictResolver, type ResolutionResult } from './ConflictResolver.js';\n\n/**\n * Configuration for MultiAgentMemoryManager.\n */\nexport interface MultiAgentConfig {\n /** Default agent ID for single-agent scenarios */\n defaultAgentId?: string;\n /** Default visibility for new memories */\n defaultVisibility?: MemoryVisibility;\n /** Allow cross-agent memory access (default: true) */\n allowCrossAgent?: boolean;\n /** Require agent registration before use (default: false) */\n requireRegistration?: boolean;\n}\n\n/**\n * Manages memory for multiple AI agents.\n *\n * Provides agent registration, memory ownership tracking, and visibility\n * controls for multi-agent collaboration scenarios.\n *\n * @example\n * ```typescript\n * const manager = new MultiAgentMemoryManager(storage);\n * await manager.registerAgent('agent_1', { name: 'Assistant', type: 'llm' });\n * const memory = await manager.createAgentMemory('agent_1', {\n * name: 'user_preference',\n * observations: ['Likes Italian food'],\n * });\n * ```\n */\nexport class MultiAgentMemoryManager extends EventEmitter {\n private readonly storage: IGraphStorage;\n private readonly config: Required<MultiAgentConfig>;\n private readonly agents: Map<string, AgentMetadata> = new Map();\n\n constructor(storage: IGraphStorage, config: MultiAgentConfig = {}) {\n super();\n this.storage = storage;\n this.config = {\n defaultAgentId: config.defaultAgentId ?? 'default',\n defaultVisibility: config.defaultVisibility ?? 'private',\n allowCrossAgent: config.allowCrossAgent ?? true,\n requireRegistration: config.requireRegistration ?? false,\n };\n\n // Register default agent\n this.agents.set(this.config.defaultAgentId, {\n name: 'Default Agent',\n type: 'default',\n trustLevel: 1.0,\n capabilities: ['read', 'write'],\n createdAt: new Date().toISOString(),\n lastActiveAt: new Date().toISOString(),\n });\n }\n\n // ==================== Agent Registration ====================\n\n /**\n * Register a new agent.\n *\n * @param agentId - Unique agent identifier\n * @param metadata - Agent metadata\n * @returns Complete agent metadata\n * @throws Error if agent ID already exists\n */\n async registerAgent(\n agentId: string,\n metadata: Partial<AgentMetadata>\n ): Promise<AgentMetadata> {\n // Validate unique ID\n if (this.agents.has(agentId)) {\n throw new Error(`Agent already registered: ${agentId}`);\n }\n\n const now = new Date().toISOString();\n\n // Build complete metadata with defaults\n const completeMetadata: AgentMetadata = {\n name: metadata.name ?? agentId,\n type: metadata.type ?? 'llm',\n trustLevel: metadata.trustLevel ?? 0.5,\n capabilities: metadata.capabilities ?? ['read', 'write'],\n createdAt: now,\n lastActiveAt: now,\n metadata: metadata.metadata,\n };\n\n // Store in memory\n this.agents.set(agentId, completeMetadata);\n\n // Create agent entity for persistence\n const agentEntity: Entity = {\n name: `agent:${agentId}`,\n entityType: 'agent',\n observations: [\n `Name: ${completeMetadata.name}`,\n `Type: ${completeMetadata.type}`,\n `Trust Level: ${completeMetadata.trustLevel}`,\n `Capabilities: ${completeMetadata.capabilities.join(', ')}`,\n ],\n };\n\n await this.storage.appendEntity(agentEntity);\n\n // Emit registration event\n this.emit('agent:registered', agentId, completeMetadata);\n\n return completeMetadata;\n }\n\n /**\n * Unregister an agent.\n *\n * @param agentId - Agent to unregister\n * @returns True if unregistered, false if not found\n */\n async unregisterAgent(agentId: string): Promise<boolean> {\n if (!this.agents.has(agentId)) {\n return false;\n }\n\n // Don't allow unregistering default agent\n if (agentId === this.config.defaultAgentId) {\n throw new Error('Cannot unregister default agent');\n }\n\n this.agents.delete(agentId);\n\n // Remove agent entity from storage\n const agentEntityName = `agent:${agentId}`;\n const graph = await this.storage.getGraphForMutation();\n const index = graph.entities.findIndex((e) => e.name === agentEntityName);\n if (index !== -1) {\n graph.entities.splice(index, 1);\n await this.storage.saveGraph(graph);\n }\n\n // Emit unregistration event\n this.emit('agent:unregistered', agentId);\n\n return true;\n }\n\n // ==================== Agent Queries ====================\n\n /**\n * Get agent metadata by ID.\n *\n * @param agentId - Agent identifier\n * @returns Agent metadata or undefined\n */\n getAgent(agentId: string): AgentMetadata | undefined {\n return this.agents.get(agentId);\n }\n\n /**\n * Check if an agent is registered.\n *\n * @param agentId - Agent identifier\n * @returns True if registered\n */\n hasAgent(agentId: string): boolean {\n return this.agents.has(agentId);\n }\n\n /**\n * List all registered agents.\n *\n * @param filter - Optional filter criteria\n * @returns Array of agent metadata with IDs\n */\n listAgents(filter?: {\n type?: AgentType;\n minTrustLevel?: number;\n capability?: string;\n }): Array<{ id: string; metadata: AgentMetadata }> {\n let entries = Array.from(this.agents.entries());\n\n if (filter?.type) {\n entries = entries.filter(([, a]) => a.type === filter.type);\n }\n if (filter?.minTrustLevel !== undefined) {\n entries = entries.filter(([, a]) => a.trustLevel >= filter.minTrustLevel!);\n }\n if (filter?.capability) {\n entries = entries.filter(([, a]) => a.capabilities.includes(filter.capability!));\n }\n\n // Sort by trust level (highest first)\n return entries\n .sort((a, b) => b[1].trustLevel - a[1].trustLevel)\n .map(([id, metadata]) => ({ id, metadata }));\n }\n\n /**\n * Get number of registered agents.\n */\n getAgentCount(): number {\n return this.agents.size;\n }\n\n // ==================== Memory Operations ====================\n\n /**\n * Create a memory owned by an agent.\n *\n * @param agentId - Owning agent's ID\n * @param entity - Partial entity data\n * @returns Created AgentEntity\n */\n async createAgentMemory(\n agentId: string,\n entity: Partial<AgentEntity>\n ): Promise<AgentEntity> {\n // Validate agent exists if required\n if (this.config.requireRegistration && !this.agents.has(agentId)) {\n throw new Error(`Agent not registered: ${agentId}`);\n }\n\n // Use default agent if not registered and not requiring registration\n const effectiveAgentId = this.agents.has(agentId)\n ? agentId\n : this.config.defaultAgentId;\n\n const now = new Date().toISOString();\n const name = entity.name ?? `memory_${effectiveAgentId}_${Date.now()}`;\n\n const agentEntity: AgentEntity = {\n // Base entity fields\n name,\n entityType: entity.entityType ?? 'memory',\n observations: entity.observations ?? [],\n createdAt: now,\n lastModified: now,\n importance: entity.importance ?? 5,\n\n // Agent memory fields\n agentId: effectiveAgentId,\n visibility: entity.visibility ?? this.config.defaultVisibility,\n memoryType: entity.memoryType ?? 'working',\n accessCount: 0,\n lastAccessedAt: now,\n confidence: entity.confidence ?? 0.5,\n confirmationCount: entity.confirmationCount ?? 0,\n };\n\n await this.storage.appendEntity(agentEntity as Entity);\n\n // Update agent's last active\n this.updateLastActive(effectiveAgentId);\n\n // Emit creation event\n this.emit('memory:created', agentEntity);\n\n return agentEntity;\n }\n\n /**\n * Get all memories owned by an agent.\n *\n * @param agentId - Agent identifier\n * @returns Agent's memories\n */\n async getAgentMemories(agentId: string): Promise<AgentEntity[]> {\n const graph = await this.storage.loadGraph();\n const memories: AgentEntity[] = [];\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity as AgentEntity;\n\n if (agentEntity.agentId === agentId) {\n memories.push(agentEntity);\n }\n }\n\n return memories;\n }\n\n /**\n * Get memories visible to an agent.\n *\n * Includes:\n * - Agent's own memories (any visibility)\n * - Other agents' 'shared' memories\n * - All agents' 'public' memories\n *\n * @param agentId - Requesting agent's ID\n * @returns Visible memories\n */\n async getVisibleMemories(agentId: string): Promise<AgentEntity[]> {\n const graph = await this.storage.loadGraph();\n const memories: AgentEntity[] = [];\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity as AgentEntity;\n\n // Own memories are always visible\n if (agentEntity.agentId === agentId) {\n memories.push(agentEntity);\n continue;\n }\n\n // Check visibility for cross-agent access\n if (this.config.allowCrossAgent) {\n if (agentEntity.visibility === 'public') {\n memories.push(agentEntity);\n } else if (agentEntity.visibility === 'shared') {\n memories.push(agentEntity);\n }\n }\n }\n\n return memories;\n }\n\n /**\n * Transfer memory ownership to another agent.\n *\n * @param memoryName - Memory to transfer\n * @param fromAgentId - Current owner\n * @param toAgentId - New owner\n * @returns Updated memory or null if not found/unauthorized\n */\n async transferMemory(\n memoryName: string,\n fromAgentId: string,\n toAgentId: string\n ): Promise<AgentEntity | null> {\n const entity = this.storage.getEntityByName(memoryName);\n if (!entity || !isAgentEntity(entity)) {\n return null;\n }\n\n const memory = entity as AgentEntity;\n\n // Verify ownership\n if (memory.agentId !== fromAgentId) {\n return null;\n }\n\n // Update ownership\n memory.agentId = toAgentId;\n memory.lastModified = new Date().toISOString();\n await this.storage.updateEntity(memoryName, {\n lastModified: memory.lastModified,\n } as Partial<Entity>);\n // Update in-memory via saveGraph for agent-specific fields\n const graph = await this.storage.getGraphForMutation();\n const entityIndex = graph.entities.findIndex((e) => e.name === memoryName);\n if (entityIndex !== -1) {\n (graph.entities[entityIndex] as AgentEntity).agentId = toAgentId;\n await this.storage.saveGraph(graph);\n }\n\n // Emit transfer event\n this.emit('memory:transferred', memoryName, fromAgentId, toAgentId);\n\n return memory;\n }\n\n /**\n * Share memory with other agents by changing visibility.\n *\n * @param memoryName - Memory to share\n * @param agentId - Owner agent\n * @param visibility - New visibility level\n * @returns Updated memory or null if not found/unauthorized\n */\n async setMemoryVisibility(\n memoryName: string,\n agentId: string,\n visibility: MemoryVisibility\n ): Promise<AgentEntity | null> {\n const entity = this.storage.getEntityByName(memoryName);\n if (!entity || !isAgentEntity(entity)) {\n return null;\n }\n\n const memory = entity as AgentEntity;\n\n // Verify ownership\n if (memory.agentId !== agentId) {\n return null;\n }\n\n // Update visibility\n memory.visibility = visibility;\n memory.lastModified = new Date().toISOString();\n // Update in storage via saveGraph for agent-specific fields\n const graph = await this.storage.getGraphForMutation();\n const entityIndex = graph.entities.findIndex((e) => e.name === memoryName);\n if (entityIndex !== -1) {\n (graph.entities[entityIndex] as AgentEntity).visibility = visibility;\n (graph.entities[entityIndex] as AgentEntity).lastModified = memory.lastModified;\n await this.storage.saveGraph(graph);\n }\n\n // Emit visibility change event\n this.emit('memory:visibility_changed', memoryName, visibility);\n\n return memory;\n }\n\n /**\n * Share a memory with all registered agents.\n *\n * Convenience method that sets visibility to 'shared'.\n *\n * @param memoryName - Memory to share\n * @param agentId - Owner agent\n * @returns Updated memory or null if not found/unauthorized\n */\n async shareMemory(\n memoryName: string,\n agentId: string\n ): Promise<AgentEntity | null> {\n return this.setMemoryVisibility(memoryName, agentId, 'shared');\n }\n\n /**\n * Make a memory public to all agents (including unregistered).\n *\n * Convenience method that sets visibility to 'public'.\n *\n * @param memoryName - Memory to make public\n * @param agentId - Owner agent\n * @returns Updated memory or null if not found/unauthorized\n */\n async makePublic(\n memoryName: string,\n agentId: string\n ): Promise<AgentEntity | null> {\n return this.setMemoryVisibility(memoryName, agentId, 'public');\n }\n\n /**\n * Make a memory private to owner only.\n *\n * Convenience method that sets visibility to 'private'.\n *\n * @param memoryName - Memory to make private\n * @param agentId - Owner agent\n * @returns Updated memory or null if not found/unauthorized\n */\n async makePrivate(\n memoryName: string,\n agentId: string\n ): Promise<AgentEntity | null> {\n return this.setMemoryVisibility(memoryName, agentId, 'private');\n }\n\n /**\n * Filter entities by visibility for a specific agent.\n *\n * Returns only entities that the agent is allowed to see:\n * - Own memories (any visibility)\n * - 'shared' memories from other agents (if allowCrossAgent)\n * - 'public' memories from other agents (if allowCrossAgent)\n *\n * @param entities - Entities to filter\n * @param agentId - Requesting agent's ID\n * @returns Filtered entities\n */\n filterByVisibility(entities: Entity[], agentId: string): AgentEntity[] {\n const visible: AgentEntity[] = [];\n\n for (const entity of entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity as AgentEntity;\n\n // Own memories are always visible\n if (agentEntity.agentId === agentId) {\n visible.push(agentEntity);\n continue;\n }\n\n // Cross-agent visibility check\n if (this.config.allowCrossAgent) {\n if (\n agentEntity.visibility === 'public' ||\n agentEntity.visibility === 'shared'\n ) {\n visible.push(agentEntity);\n }\n }\n }\n\n return visible;\n }\n\n /**\n * Check if a specific memory is visible to an agent.\n *\n * @param memoryName - Memory name to check\n * @param agentId - Agent requesting access\n * @returns True if visible, false otherwise\n */\n isMemoryVisible(memoryName: string, agentId: string): boolean {\n const entity = this.storage.getEntityByName(memoryName);\n if (!entity || !isAgentEntity(entity)) {\n return false;\n }\n\n const memory = entity as AgentEntity;\n\n // Own memories are always visible\n if (memory.agentId === agentId) {\n return true;\n }\n\n // Cross-agent visibility\n if (this.config.allowCrossAgent) {\n return memory.visibility === 'public' || memory.visibility === 'shared';\n }\n\n return false;\n }\n\n /**\n * Get memories by type with visibility filtering.\n *\n * @param agentId - Requesting agent's ID\n * @param entityType - Entity type to filter by\n * @returns Visible memories of specified type\n */\n async getVisibleMemoriesByType(\n agentId: string,\n entityType: string\n ): Promise<AgentEntity[]> {\n const allVisible = await this.getVisibleMemories(agentId);\n return allVisible.filter((m) => m.entityType === entityType);\n }\n\n /**\n * Search memories with automatic visibility filtering.\n *\n * Wraps basic search functionality and filters results based on\n * the requesting agent's visibility permissions.\n *\n * @param agentId - Requesting agent's ID\n * @param query - Search query (entity name or observation content)\n * @returns Visible memories matching the query\n */\n async searchVisibleMemories(\n agentId: string,\n query: string\n ): Promise<AgentEntity[]> {\n const graph = await this.storage.loadGraph();\n const queryLower = query.toLowerCase();\n const matches: AgentEntity[] = [];\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity as AgentEntity;\n\n // Check visibility first\n const isVisible =\n agentEntity.agentId === agentId ||\n (this.config.allowCrossAgent &&\n (agentEntity.visibility === 'public' ||\n agentEntity.visibility === 'shared'));\n\n if (!isVisible) continue;\n\n // Check if entity matches query\n const nameMatch = agentEntity.name.toLowerCase().includes(queryLower);\n const obsMatch = agentEntity.observations?.some((o) =>\n o.toLowerCase().includes(queryLower)\n );\n\n if (nameMatch || obsMatch) {\n matches.push(agentEntity);\n }\n }\n\n return matches;\n }\n\n // ==================== Cross-Agent Operations ====================\n\n /**\n * Get memories shared between two or more agents.\n *\n * @param agentIds - Array of agent IDs to find shared memories between\n * @param options - Filter options\n * @returns Memories accessible to all specified agents\n */\n async getSharedMemories(\n agentIds: string[],\n options?: {\n entityType?: string;\n startDate?: string;\n endDate?: string;\n }\n ): Promise<AgentEntity[]> {\n if (agentIds.length < 2) {\n return [];\n }\n\n const graph = await this.storage.loadGraph();\n const shared: AgentEntity[] = [];\n\n for (const entity of graph.entities) {\n if (!isAgentEntity(entity)) continue;\n const agentEntity = entity as AgentEntity;\n\n // Check if all agents can see this memory\n const visibleToAll = agentIds.every((agentId) => {\n // Own memories are visible\n if (agentEntity.agentId === agentId) return true;\n // Shared/public memories are visible if cross-agent allowed\n if (this.config.allowCrossAgent) {\n return (\n agentEntity.visibility === 'public' ||\n agentEntity.visibility === 'shared'\n );\n }\n return false;\n });\n\n if (!visibleToAll) continue;\n\n // Apply optional filters\n if (options?.entityType && agentEntity.entityType !== options.entityType) {\n continue;\n }\n if (options?.startDate && agentEntity.createdAt && agentEntity.createdAt < options.startDate) {\n continue;\n }\n if (options?.endDate && agentEntity.createdAt && agentEntity.createdAt > options.endDate) {\n continue;\n }\n\n shared.push(agentEntity);\n }\n\n return shared;\n }\n\n /**\n * Search across multiple agents' visible memories with optional trust weighting.\n *\n * @param requestingAgentId - Agent performing the search\n * @param query - Search query\n * @param options - Search options\n * @returns Ranked search results\n */\n async searchCrossAgent(\n requestingAgentId: string,\n query: string,\n options?: {\n agentIds?: string[];\n useTrustWeighting?: boolean;\n trustWeight?: number;\n entityType?: string;\n }\n ): Promise<Array<{\n memory: AgentEntity;\n relevanceScore: number;\n trustScore: number;\n combinedScore: number;\n }>> {\n const useTrustWeighting = options?.useTrustWeighting ?? false;\n const trustWeight = options?.trustWeight ?? 0.3;\n const queryLower = query.toLowerCase();\n\n // Get all visible memories\n const visibleMemories = await this.getVisibleMemories(requestingAgentId);\n\n // Filter by agent IDs if specified\n let filteredMemories = visibleMemories;\n if (options?.agentIds && options.agentIds.length > 0) {\n filteredMemories = visibleMemories.filter(\n (m) => m.agentId && options.agentIds!.includes(m.agentId)\n );\n }\n\n // Filter by entity type if specified\n if (options?.entityType) {\n filteredMemories = filteredMemories.filter(\n (m) => m.entityType === options.entityType\n );\n }\n\n // Search and score results\n const results: Array<{\n memory: AgentEntity;\n relevanceScore: number;\n trustScore: number;\n combinedScore: number;\n }> = [];\n\n for (const memory of filteredMemories) {\n // Calculate relevance score (simple TF-style scoring)\n const nameMatch = memory.name.toLowerCase().includes(queryLower);\n const obsMatches =\n memory.observations?.filter((o) => o.toLowerCase().includes(queryLower))\n .length ?? 0;\n\n const relevanceScore = nameMatch ? 0.5 : 0;\n const obsScore = Math.min(obsMatches * 0.1, 0.5);\n const totalRelevance = relevanceScore + obsScore;\n\n if (totalRelevance === 0) continue;\n\n // Get trust score from owning agent\n const ownerAgent = memory.agentId ? this.agents.get(memory.agentId) : undefined;\n const trustScore = ownerAgent?.trustLevel ?? 0.5;\n\n // Calculate combined score\n let combinedScore = totalRelevance;\n if (useTrustWeighting) {\n combinedScore =\n totalRelevance * (1 - trustWeight) + trustScore * trustWeight;\n }\n\n results.push({\n memory,\n relevanceScore: totalRelevance,\n trustScore,\n combinedScore,\n });\n }\n\n // Sort by combined score (descending)\n results.sort((a, b) => b.combinedScore - a.combinedScore);\n\n // Emit search event\n this.emit('memory:cross_agent_search', requestingAgentId, query, results.length);\n\n return results;\n }\n\n /**\n * Copy a shared memory to an agent's private store.\n *\n * Creates a new entity owned by the requesting agent with source tracking.\n *\n * @param memoryName - Memory to copy\n * @param requestingAgentId - Agent making the copy\n * @param options - Copy options\n * @returns New copied memory or null if not accessible\n */\n async copyMemory(\n memoryName: string,\n requestingAgentId: string,\n options?: {\n newName?: string;\n annotation?: string;\n visibility?: MemoryVisibility;\n }\n ): Promise<AgentEntity | null> {\n // Check if memory is visible to requesting agent\n if (!this.isMemoryVisible(memoryName, requestingAgentId)) {\n return null;\n }\n\n const entity = this.storage.getEntityByName(memoryName);\n if (!entity || !isAgentEntity(entity)) {\n return null;\n }\n\n const sourceMemory = entity as AgentEntity;\n\n // Create a copy with new ownership\n const now = new Date().toISOString();\n const newName =\n options?.newName ??\n `copy_${memoryName}_${requestingAgentId}_${Date.now()}`;\n\n const copiedMemory: AgentEntity = {\n // Base entity fields\n name: newName,\n entityType: sourceMemory.entityType,\n observations: [...(sourceMemory.observations ?? [])],\n createdAt: now,\n lastModified: now,\n importance: sourceMemory.importance ?? 5,\n\n // Agent memory fields\n agentId: requestingAgentId,\n visibility: options?.visibility ?? 'private',\n memoryType: sourceMemory.memoryType ?? 'working',\n accessCount: 0,\n lastAccessedAt: now,\n confidence: sourceMemory.confidence ?? 0.5,\n confirmationCount: 0,\n\n // Source tracking\n source: {\n agentId: sourceMemory.agentId ?? requestingAgentId,\n timestamp: now,\n method: 'consolidated',\n reliability: sourceMemory.source?.reliability ?? 0.8,\n originalEntityId: memoryName,\n },\n };\n\n // Add annotation if provided\n if (options?.annotation) {\n copiedMemory.observations = [\n ...(copiedMemory.observations ?? []),\n `[Annotation] ${options.annotation}`,\n ];\n }\n\n await this.storage.appendEntity(copiedMemory as Entity);\n\n // Update requesting agent's last active\n this.updateLastActive(requestingAgentId);\n\n // Emit copy event\n this.emit(\n 'memory:copied',\n memoryName,\n sourceMemory.agentId,\n requestingAgentId,\n newName\n );\n\n return copiedMemory;\n }\n\n /**\n * Record that an agent accessed another agent's memory.\n *\n * Used for audit trail when cross-agent access occurs.\n *\n * @param memoryName - Memory that was accessed\n * @param requestingAgentId - Agent that accessed the memory\n * @param accessType - Type of access (view, search, copy)\n */\n recordCrossAgentAccess(\n memoryName: string,\n requestingAgentId: string,\n accessType: 'view' | 'search' | 'copy'\n ): void {\n const entity = this.storage.getEntityByName(memoryName);\n if (!entity || !isAgentEntity(entity)) return;\n\n const memory = entity as AgentEntity;\n\n // Only record if accessing another agent's memory\n if (memory.agentId === requestingAgentId) return;\n\n this.emit('memory:cross_agent_access', {\n memoryName,\n ownerAgentId: memory.agentId,\n requestingAgentId,\n accessType,\n timestamp: new Date().toISOString(),\n });\n }\n\n /**\n * Get collaboration statistics between agents.\n *\n * @param agentId - Agent to get stats for\n * @returns Collaboration statistics\n */\n async getCollaborationStats(agentId: string): Promise<{\n sharedMemoryCount: number;\n publicMemoryCount: number;\n accessibleFromOthers: number;\n }> {\n const ownMemories = await this.getAgentMemories(agentId);\n\n const sharedCount = ownMemories.filter((m) => m.visibility === 'shared')\n .length;\n const publicCount = ownMemories.filter((m) => m.visibility === 'public')\n .length;\n\n const visibleMemories = await this.getVisibleMemories(agentId);\n const fromOthers = visibleMemories.filter((m) => m.agentId !== agentId)\n .length;\n\n return {\n sharedMemoryCount: sharedCount,\n publicMemoryCount: publicCount,\n accessibleFromOthers: fromOthers,\n };\n }\n\n // ==================== Conflict Resolution ====================\n\n private _conflictResolver?: ConflictResolver;\n\n /**\n * Get or create the conflict resolver instance.\n */\n private getConflictResolver(): ConflictResolver {\n if (!this._conflictResolver) {\n this._conflictResolver = new ConflictResolver();\n\n // Forward conflict events\n this._conflictResolver.on('memory:conflict', (conflict: ConflictInfo) => {\n this.emit('memory:conflict', conflict);\n });\n\n this._conflictResolver.on(\n 'memory:conflict_resolved',\n (data: { conflict: ConflictInfo; strategy: ConflictStrategy; resolvedMemory: string }) => {\n this.emit('memory:conflict_resolved', data);\n }\n );\n }\n return this._conflictResolver;\n }\n\n /**\n * Detect conflicts among a set of memories.\n *\n * @param memories - Memories to check for conflicts (defaults to all visible to default agent)\n * @returns Array of detected conflicts\n */\n async detectConflicts(memories?: AgentEntity[]): Promise<ConflictInfo[]> {\n const resolver = this.getConflictResolver();\n\n if (!memories) {\n memories = await this.getVisibleMemories(this.config.defaultAgentId);\n }\n\n return resolver.detectConflicts(memories);\n }\n\n /**\n * Resolve a conflict using the specified strategy.\n *\n * @param conflict - Conflict information\n * @param strategy - Resolution strategy (uses suggested if not specified)\n * @returns Resolution result\n */\n async resolveConflict(\n conflict: ConflictInfo,\n strategy?: ConflictStrategy\n ): Promise<ResolutionResult> {\n const resolver = this.getConflictResolver();\n\n // Get all memories involved in the conflict\n const allNames = [conflict.primaryMemory, ...conflict.conflictingMemories];\n const memories: AgentEntity[] = [];\n\n for (const name of allNames) {\n const entity = this.storage.getEntityByName(name);\n if (entity && isAgentEntity(entity)) {\n memories.push(entity as AgentEntity);\n }\n }\n\n return resolver.resolveConflict(conflict, memories, this.agents, strategy);\n }\n\n /**\n * Merge memories from multiple agents with trust weighting.\n *\n * Creates a new merged memory preserving provenance from all sources.\n *\n * @param memoryNames - Names of memories to merge\n * @param targetAgentId - Agent that will own the merged memory\n * @param options - Merge options\n * @returns Merged memory\n */\n async mergeCrossAgent(\n memoryNames: string[],\n targetAgentId: string,\n options?: {\n newName?: string;\n resolveConflicts?: boolean;\n conflictStrategy?: ConflictStrategy;\n }\n ): Promise<AgentEntity | null> {\n if (memoryNames.length < 2) {\n return null;\n }\n\n // Gather memories\n const memories: AgentEntity[] = [];\n for (const name of memoryNames) {\n const entity = this.storage.getEntityByName(name);\n if (entity && isAgentEntity(entity)) {\n memories.push(entity as AgentEntity);\n }\n }\n\n if (memories.length < 2) {\n return null;\n }\n\n // Check for conflicts if requested\n if (options?.resolveConflicts) {\n const resolver = this.getConflictResolver();\n const conflicts = resolver.detectConflicts(memories);\n\n if (conflicts.length > 0) {\n // Resolve the first conflict (which may include all memories)\n const resolution = resolver.resolveConflict(\n conflicts[0],\n memories,\n this.agents,\n options.conflictStrategy\n );\n\n // Use the resolved memory as the merge result\n const now = new Date().toISOString();\n const newName =\n options.newName ??\n `merged_${targetAgentId}_${Date.now()}`;\n\n const mergedMemory: AgentEntity = {\n ...resolution.resolvedMemory,\n name: newName,\n agentId: targetAgentId,\n visibility: 'private',\n lastModified: now,\n source: {\n agentId: targetAgentId,\n timestamp: now,\n method: 'consolidated',\n reliability: resolution.resolvedMemory.confidence ?? 0.7,\n originalEntityId: resolution.sourceMemories.join(','),\n },\n };\n\n await this.storage.appendEntity(mergedMemory as Entity);\n\n // Emit merge event\n this.emit('memory:merged', {\n newMemory: newName,\n sourceMemories: memoryNames,\n targetAgent: targetAgentId,\n hadConflicts: true,\n });\n\n return mergedMemory;\n }\n }\n\n // No conflicts or not resolving - simple merge\n const now = new Date().toISOString();\n const newName = options?.newName ?? `merged_${targetAgentId}_${Date.now()}`;\n\n // Combine observations\n const allObservations = new Set<string>();\n for (const m of memories) {\n if (m.observations) {\n for (const o of m.observations) {\n allObservations.add(o);\n }\n }\n }\n\n // Calculate weighted confidence based on agent trust\n let totalWeightedConfidence = 0;\n let totalWeight = 0;\n for (const m of memories) {\n const agentMeta = m.agentId ? this.agents.get(m.agentId) : undefined;\n const trust = agentMeta?.trustLevel ?? 0.5;\n const conf = m.confidence ?? 0.5;\n totalWeightedConfidence += conf * trust;\n totalWeight += trust;\n }\n const avgConfidence = totalWeight > 0 ? totalWeightedConfidence / totalWeight : 0.5;\n\n // Sum confirmations\n const totalConfirmations = memories.reduce(\n (sum, m) => sum + (m.confirmationCount ?? 0),\n 0\n );\n\n // Use first memory as base for entity type\n const baseMemory = memories[0];\n\n const mergedMemory: AgentEntity = {\n name: newName,\n entityType: baseMemory.entityType ?? 'memory',\n observations: Array.from(allObservations),\n createdAt: now,\n lastModified: now,\n importance: Math.max(...memories.map((m) => m.importance ?? 5)),\n agentId: targetAgentId,\n visibility: 'private',\n memoryType: baseMemory.memoryType ?? 'semantic',\n accessCount: 0,\n lastAccessedAt: now,\n confidence: avgConfidence,\n confirmationCount: totalConfirmations,\n source: {\n agentId: targetAgentId,\n timestamp: now,\n method: 'consolidated',\n reliability: avgConfidence,\n originalEntityId: memoryNames.join(','),\n },\n };\n\n await this.storage.appendEntity(mergedMemory as Entity);\n\n // Update target agent's last active\n this.updateLastActive(targetAgentId);\n\n // Emit merge event\n this.emit('memory:merged', {\n newMemory: newName,\n sourceMemories: memoryNames,\n targetAgent: targetAgentId,\n hadConflicts: false,\n });\n\n return mergedMemory;\n }\n\n /**\n * Get the conflict resolver instance for advanced operations.\n */\n getConflictResolverInstance(): ConflictResolver {\n return this.getConflictResolver();\n }\n\n // ==================== Helper Methods ====================\n\n /**\n * Update agent's last active timestamp.\n * @internal\n */\n private updateLastActive(agentId: string): void {\n const agent = this.agents.get(agentId);\n if (agent) {\n agent.lastActiveAt = new Date().toISOString();\n }\n }\n\n /**\n * Get current configuration.\n */\n getConfig(): Readonly<Required<MultiAgentConfig>> {\n return { ...this.config };\n }\n}\n","/**\n * Agent Memory Configuration\n *\n * Unified configuration interface for all agent memory components.\n * Supports both environment variable and programmatic configuration.\n *\n * @module agent/AgentMemoryConfig\n */\n\nimport type { WorkingMemoryConfig } from './WorkingMemoryManager.js';\nimport type { SessionConfig } from './SessionManager.js';\nimport type { EpisodicMemoryConfig } from './EpisodicMemoryManager.js';\nimport type { ConsolidationPipelineConfig } from './ConsolidationPipeline.js';\nimport type { SummarizationConfig } from './SummarizationService.js';\nimport type { DecayEngineConfig } from './DecayEngine.js';\nimport type { DecaySchedulerConfig } from './DecayScheduler.js';\nimport type { SalienceEngineConfig } from './SalienceEngine.js';\nimport type { ContextWindowManagerConfig } from './ContextWindowManager.js';\nimport type { MemoryFormatterConfig } from './MemoryFormatter.js';\nimport type { MultiAgentConfig } from './MultiAgentMemoryManager.js';\nimport type { ConflictResolverConfig } from './ConflictResolver.js';\n\n/**\n * Configuration for the AgentMemoryManager facade.\n *\n * All sub-configurations are optional - defaults will be applied.\n * Can be loaded from environment variables via loadFromEnv().\n */\nexport interface AgentMemoryConfig {\n /** Working memory configuration */\n workingMemory?: WorkingMemoryConfig;\n\n /** Session management configuration */\n session?: SessionConfig;\n\n /** Episodic memory configuration */\n episodic?: EpisodicMemoryConfig;\n\n /** Consolidation pipeline configuration */\n consolidation?: ConsolidationPipelineConfig;\n\n /** Summarization service configuration */\n summarization?: SummarizationConfig;\n\n /** Decay engine configuration */\n decay?: DecayEngineConfig;\n\n /** Decay scheduler configuration (optional auto-decay) */\n decayScheduler?: DecaySchedulerConfig;\n\n /** Salience engine configuration */\n salience?: SalienceEngineConfig;\n\n /** Context window configuration */\n contextWindow?: ContextWindowManagerConfig;\n\n /** Memory formatter configuration */\n formatter?: MemoryFormatterConfig;\n\n /** Multi-agent configuration */\n multiAgent?: MultiAgentConfig;\n\n /** Conflict resolver configuration */\n conflictResolver?: ConflictResolverConfig;\n\n /** Enable automatic decay scheduling */\n enableAutoDecay?: boolean;\n\n /** Enable multi-agent support */\n enableMultiAgent?: boolean;\n\n /** Default agent ID for single-agent mode */\n defaultAgentId?: string;\n}\n\n/**\n * Environment variable prefix for agent memory configuration.\n */\nconst ENV_PREFIX = 'AGENT_MEMORY_';\n\n/**\n * Load configuration from environment variables.\n *\n * Environment variables follow the pattern:\n * - AGENT_MEMORY_DECAY_HALF_LIFE_HOURS\n * - AGENT_MEMORY_CONTEXT_MAX_TOKENS\n * etc.\n */\nexport function loadConfigFromEnv(): AgentMemoryConfig {\n return {\n workingMemory: {\n defaultTTLHours: getEnvNumber(`${ENV_PREFIX}DEFAULT_TTL_HOURS`),\n maxPerSession: getEnvNumber(`${ENV_PREFIX}MAX_PER_SESSION`),\n autoPromote: getEnvBool(`${ENV_PREFIX}AUTO_PROMOTE`),\n autoPromoteConfidenceThreshold: getEnvNumber(`${ENV_PREFIX}AUTO_PROMOTE_CONFIDENCE`),\n },\n session: {\n consolidateOnEnd: getEnvBool(`${ENV_PREFIX}SESSION_CONSOLIDATE_ON_END`),\n cleanupOnEnd: getEnvBool(`${ENV_PREFIX}SESSION_CLEANUP_ON_END`),\n promoteOnEnd: getEnvBool(`${ENV_PREFIX}SESSION_PROMOTE_ON_END`),\n },\n episodic: {\n autoLinkTemporal: getEnvBool(`${ENV_PREFIX}EPISODIC_AUTO_LINK_TEMPORAL`),\n maxSequenceLength: getEnvNumber(`${ENV_PREFIX}EPISODIC_MAX_SEQUENCE`),\n },\n consolidation: {\n summarizationEnabled: getEnvBool(`${ENV_PREFIX}CONSOLIDATION_SUMMARIZATION`),\n patternExtractionEnabled: getEnvBool(`${ENV_PREFIX}CONSOLIDATION_PATTERN_EXTRACTION`),\n minPromotionConfidence: getEnvNumber(`${ENV_PREFIX}CONSOLIDATION_MIN_CONFIDENCE`),\n },\n summarization: {\n provider: getEnvString(`${ENV_PREFIX}SUMMARIZATION_PROVIDER`),\n model: getEnvString(`${ENV_PREFIX}SUMMARIZATION_MODEL`),\n },\n decay: {\n halfLifeHours: getEnvNumber(`${ENV_PREFIX}DECAY_HALF_LIFE_HOURS`),\n minImportance: getEnvNumber(`${ENV_PREFIX}DECAY_MIN_IMPORTANCE`),\n importanceModulation: getEnvBool(`${ENV_PREFIX}DECAY_IMPORTANCE_MOD`),\n accessModulation: getEnvBool(`${ENV_PREFIX}DECAY_ACCESS_MOD`),\n },\n salience: {\n importanceWeight: getEnvNumber(`${ENV_PREFIX}SALIENCE_IMPORTANCE_WEIGHT`),\n recencyWeight: getEnvNumber(`${ENV_PREFIX}SALIENCE_RECENCY_WEIGHT`),\n frequencyWeight: getEnvNumber(`${ENV_PREFIX}SALIENCE_FREQUENCY_WEIGHT`),\n contextWeight: getEnvNumber(`${ENV_PREFIX}SALIENCE_CONTEXT_WEIGHT`),\n noveltyWeight: getEnvNumber(`${ENV_PREFIX}SALIENCE_NOVELTY_WEIGHT`),\n },\n contextWindow: {\n defaultMaxTokens: getEnvNumber(`${ENV_PREFIX}CONTEXT_MAX_TOKENS`),\n tokenMultiplier: getEnvNumber(`${ENV_PREFIX}CONTEXT_TOKEN_MULTIPLIER`),\n reserveBuffer: getEnvNumber(`${ENV_PREFIX}CONTEXT_RESERVE_BUFFER`),\n diversityThreshold: getEnvNumber(`${ENV_PREFIX}CONTEXT_DIVERSITY_THRESHOLD`),\n enforceDiversity: getEnvBool(`${ENV_PREFIX}CONTEXT_ENFORCE_DIVERSITY`),\n },\n formatter: {\n includeTimestamps: getEnvBool(`${ENV_PREFIX}FORMAT_TIMESTAMPS`),\n includeMemoryType: getEnvBool(`${ENV_PREFIX}FORMAT_MEMORY_TYPE`),\n },\n multiAgent: {\n defaultAgentId: getEnvString(`${ENV_PREFIX}MULTI_AGENT_DEFAULT_AGENT_ID`),\n defaultVisibility: getEnvString(`${ENV_PREFIX}MULTI_AGENT_DEFAULT_VISIBILITY`) as\n | 'private'\n | 'shared'\n | 'public'\n | undefined,\n },\n conflictResolver: {\n similarityThreshold: getEnvNumber(`${ENV_PREFIX}CONFLICT_SIMILARITY_THRESHOLD`),\n defaultStrategy: getEnvString(`${ENV_PREFIX}CONFLICT_DEFAULT_STRATEGY`) as\n | 'most_recent'\n | 'highest_confidence'\n | 'most_confirmations'\n | 'trusted_agent'\n | 'merge_all'\n | undefined,\n detectNegations: getEnvBool(`${ENV_PREFIX}CONFLICT_DETECT_NEGATIONS`),\n },\n enableAutoDecay: getEnvBool(`${ENV_PREFIX}ENABLE_AUTO_DECAY`),\n enableMultiAgent: getEnvBool(`${ENV_PREFIX}ENABLE_MULTI_AGENT`),\n defaultAgentId: getEnvString(`${ENV_PREFIX}DEFAULT_AGENT_ID`),\n };\n}\n\n/**\n * Merge user config with defaults.\n * Removes undefined values from env-loaded config.\n */\nexport function mergeConfig(\n userConfig: AgentMemoryConfig,\n envConfig: AgentMemoryConfig\n): AgentMemoryConfig {\n return {\n workingMemory: mergeSubConfig(userConfig.workingMemory, envConfig.workingMemory),\n session: mergeSubConfig(userConfig.session, envConfig.session),\n episodic: mergeSubConfig(userConfig.episodic, envConfig.episodic),\n consolidation: mergeSubConfig(userConfig.consolidation, envConfig.consolidation),\n summarization: mergeSubConfig(userConfig.summarization, envConfig.summarization),\n decay: mergeSubConfig(userConfig.decay, envConfig.decay),\n decayScheduler: mergeSubConfig(userConfig.decayScheduler, envConfig.decayScheduler),\n salience: mergeSubConfig(userConfig.salience, envConfig.salience),\n contextWindow: mergeSubConfig(userConfig.contextWindow, envConfig.contextWindow),\n formatter: mergeSubConfig(userConfig.formatter, envConfig.formatter),\n multiAgent: mergeSubConfig(userConfig.multiAgent, envConfig.multiAgent),\n conflictResolver: mergeSubConfig(userConfig.conflictResolver, envConfig.conflictResolver),\n enableAutoDecay: userConfig.enableAutoDecay ?? envConfig.enableAutoDecay,\n enableMultiAgent: userConfig.enableMultiAgent ?? envConfig.enableMultiAgent,\n defaultAgentId: userConfig.defaultAgentId ?? envConfig.defaultAgentId,\n };\n}\n\n/**\n * Validate configuration values.\n * @throws Error if configuration is invalid\n */\nexport function validateConfig(config: AgentMemoryConfig): void {\n // Validate decay settings\n if (config.decay?.halfLifeHours !== undefined && config.decay.halfLifeHours <= 0) {\n throw new Error('decay.halfLifeHours must be positive');\n }\n if (\n config.decay?.minImportance !== undefined &&\n (config.decay.minImportance < 0 || config.decay.minImportance > 10)\n ) {\n throw new Error('decay.minImportance must be between 0 and 10');\n }\n\n // Validate salience weights\n const salienceWeights = [\n config.salience?.importanceWeight,\n config.salience?.recencyWeight,\n config.salience?.frequencyWeight,\n config.salience?.contextWeight,\n config.salience?.noveltyWeight,\n ].filter((w) => w !== undefined);\n\n if (salienceWeights.some((w) => w! < 0 || w! > 1)) {\n throw new Error('Salience weights must be between 0 and 1');\n }\n\n // Validate context window\n if (\n config.contextWindow?.defaultMaxTokens !== undefined &&\n config.contextWindow.defaultMaxTokens <= 0\n ) {\n throw new Error('contextWindow.defaultMaxTokens must be positive');\n }\n\n // Validate conflict resolver\n if (\n config.conflictResolver?.similarityThreshold !== undefined &&\n (config.conflictResolver.similarityThreshold < 0 ||\n config.conflictResolver.similarityThreshold > 1)\n ) {\n throw new Error('conflictResolver.similarityThreshold must be between 0 and 1');\n }\n}\n\n// ==================== Helper Functions ====================\n\nfunction getEnvNumber(key: string): number | undefined {\n const value = process.env[key];\n if (value === undefined) return undefined;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? undefined : parsed;\n}\n\nfunction getEnvBool(key: string): boolean | undefined {\n const value = process.env[key];\n if (value === undefined) return undefined;\n return value.toLowerCase() === 'true';\n}\n\nfunction getEnvString(key: string): string | undefined {\n return process.env[key];\n}\n\nfunction mergeSubConfig<T extends object>(\n user: T | undefined,\n env: T | undefined\n): T | undefined {\n if (!user && !env) return undefined;\n if (!user) return removeUndefined(env!) as T;\n if (!env) return user;\n\n const envClean = removeUndefined(env);\n return { ...envClean, ...user };\n}\n\nfunction removeUndefined<T extends object>(obj: T): Partial<T> {\n const result: Partial<T> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (value !== undefined) {\n (result as Record<string, unknown>)[key] = value;\n }\n }\n return result;\n}\n","/**\n * Agent Memory Manager - Unified Facade\n *\n * High-level API for AI agent memory management, wrapping all agent memory\n * components into a cohesive interface.\n *\n * @module agent/AgentMemoryManager\n */\n\nimport { EventEmitter } from 'events';\nimport type { IGraphStorage } from '../types/types.js';\nimport type {\n AgentEntity,\n AgentMetadata,\n MemoryVisibility,\n ConflictStrategy,\n ConflictInfo,\n SessionEntity,\n ForgetResult,\n ConsolidationResult,\n ContextRetrievalOptions,\n ContextPackage,\n} from '../types/agent-memory.js';\nimport { AccessTracker, type AccessContext } from './AccessTracker.js';\nimport { DecayEngine, type ForgetOptions } from './DecayEngine.js';\nimport { DecayScheduler, type DecayCycleResult } from './DecayScheduler.js';\nimport { WorkingMemoryManager, type PromotionResult, type ConfirmationResult } from './WorkingMemoryManager.js';\nimport { SessionManager, type StartSessionOptions, type EndSessionResult } from './SessionManager.js';\nimport { EpisodicMemoryManager, type CreateEpisodeOptions, type TimelineOptions } from './EpisodicMemoryManager.js';\nimport { ConsolidationPipeline } from './ConsolidationPipeline.js';\nimport { SummarizationService, type ISummarizationProvider } from './SummarizationService.js';\nimport { PatternDetector } from './PatternDetector.js';\nimport { RuleEvaluator } from './RuleEvaluator.js';\nimport { SalienceEngine } from './SalienceEngine.js';\nimport { ContextWindowManager } from './ContextWindowManager.js';\nimport { MemoryFormatter } from './MemoryFormatter.js';\nimport { MultiAgentMemoryManager } from './MultiAgentMemoryManager.js';\nimport { ConflictResolver, type ResolutionResult } from './ConflictResolver.js';\nimport {\n type AgentMemoryConfig,\n loadConfigFromEnv,\n mergeConfig,\n validateConfig,\n} from './AgentMemoryConfig.js';\n\n/**\n * Options for creating working memory.\n */\nexport interface CreateMemoryOptions {\n /** Session ID to associate memory with */\n sessionId: string;\n /** Content/observation for the memory */\n content: string;\n /** Initial importance (0-10) */\n importance?: number;\n /** Task ID context */\n taskId?: string;\n /** Time-to-live in hours */\n ttlHours?: number;\n /** Agent ID (for multi-agent mode) */\n agentId?: string;\n /** Visibility level (for multi-agent mode) */\n visibility?: MemoryVisibility;\n}\n\n/**\n * Options for retrieving context.\n */\nexport interface RetrieveContextOptions {\n /** Current session ID */\n sessionId?: string;\n /** Current task ID */\n taskId?: string;\n /** Keywords for relevance scoring */\n keywords?: string[];\n /** Maximum tokens in context */\n maxTokens?: number;\n /** Include working memory (default: true) */\n includeWorkingMemory?: boolean;\n /** Include episodic memories (default: true) */\n includeEpisodicRecent?: boolean;\n /** Include semantic memories (default: true) */\n includeSemanticRelevant?: boolean;\n /** Specific memory names to always include */\n mustInclude?: string[];\n}\n\n/**\n * Agent Memory Manager - Unified API for agent memory operations.\n *\n * Provides a high-level interface for:\n * - Session lifecycle management\n * - Working memory creation and management\n * - Memory consolidation and promotion\n * - Context retrieval for LLM consumption\n * - Multi-agent memory coordination\n *\n * @example\n * ```typescript\n * const manager = new AgentMemoryManager(storage);\n *\n * // Start a session\n * const session = await manager.startSession({ agentId: 'assistant' });\n *\n * // Add working memory\n * const memory = await manager.addWorkingMemory({\n * sessionId: session.name,\n * content: 'User prefers dark mode',\n * });\n *\n * // Retrieve context for LLM\n * const context = await manager.retrieveForContext({\n * sessionId: session.name,\n * keywords: ['user preferences'],\n * maxTokens: 2000,\n * });\n *\n * // End session\n * await manager.endSession(session.name);\n * ```\n */\nexport class AgentMemoryManager extends EventEmitter {\n private readonly storage: IGraphStorage;\n private readonly config: AgentMemoryConfig;\n\n // Core components (lazy initialized)\n private _accessTracker?: AccessTracker;\n private _decayEngine?: DecayEngine;\n private _decayScheduler?: DecayScheduler;\n private _workingMemory?: WorkingMemoryManager;\n private _sessionManager?: SessionManager;\n private _episodicMemory?: EpisodicMemoryManager;\n private _consolidationPipeline?: ConsolidationPipeline;\n private _summarizationService?: SummarizationService;\n private _patternDetector?: PatternDetector;\n private _ruleEvaluator?: RuleEvaluator;\n private _salienceEngine?: SalienceEngine;\n private _contextWindowManager?: ContextWindowManager;\n private _memoryFormatter?: MemoryFormatter;\n private _multiAgentManager?: MultiAgentMemoryManager;\n private _conflictResolver?: ConflictResolver;\n\n constructor(storage: IGraphStorage, config: AgentMemoryConfig = {}) {\n super();\n this.storage = storage;\n\n // Load and merge configuration\n const envConfig = loadConfigFromEnv();\n this.config = mergeConfig(config, envConfig);\n\n // Validate configuration\n validateConfig(this.config);\n\n // Initialize auto-decay if enabled\n if (this.config.enableAutoDecay && this.config.decayScheduler) {\n this.decayScheduler.start();\n }\n }\n\n // ==================== Component Accessors ====================\n\n /** Access tracker for memory usage patterns */\n get accessTracker(): AccessTracker {\n return (this._accessTracker ??= new AccessTracker(this.storage));\n }\n\n /** Decay engine for memory importance decay */\n get decayEngine(): DecayEngine {\n return (this._decayEngine ??= new DecayEngine(\n this.storage,\n this.accessTracker,\n this.config.decay\n ));\n }\n\n /** Decay scheduler for automatic decay operations */\n get decayScheduler(): DecayScheduler {\n if (!this._decayScheduler) {\n this._decayScheduler = new DecayScheduler(\n this.decayEngine,\n this.config.decayScheduler ?? { decayIntervalMs: 3600000 }\n );\n }\n return this._decayScheduler;\n }\n\n /** Working memory manager for short-term memory */\n get workingMemory(): WorkingMemoryManager {\n return (this._workingMemory ??= new WorkingMemoryManager(\n this.storage,\n this.config.workingMemory\n ));\n }\n\n /** Episodic memory manager for experience tracking */\n get episodicMemory(): EpisodicMemoryManager {\n return (this._episodicMemory ??= new EpisodicMemoryManager(\n this.storage,\n this.config.episodic\n ));\n }\n\n /** Session manager for session lifecycle */\n get sessionManager(): SessionManager {\n return (this._sessionManager ??= new SessionManager(\n this.storage,\n this.workingMemory,\n this.config.session,\n this.episodicMemory\n ));\n }\n\n /** Consolidation pipeline for memory promotion */\n get consolidationPipeline(): ConsolidationPipeline {\n return (this._consolidationPipeline ??= new ConsolidationPipeline(\n this.storage,\n this.workingMemory,\n this.decayEngine,\n this.config.consolidation\n ));\n }\n\n /** Summarization service for memory grouping */\n get summarizationService(): SummarizationService {\n return (this._summarizationService ??= new SummarizationService(this.config.summarization));\n }\n\n /** Pattern detector for observation patterns */\n get patternDetector(): PatternDetector {\n return (this._patternDetector ??= new PatternDetector());\n }\n\n /** Rule evaluator for promotion rules */\n get ruleEvaluator(): RuleEvaluator {\n return (this._ruleEvaluator ??= new RuleEvaluator());\n }\n\n /** Salience engine for relevance scoring */\n get salienceEngine(): SalienceEngine {\n return (this._salienceEngine ??= new SalienceEngine(\n this.storage,\n this.accessTracker,\n this.decayEngine,\n this.config.salience\n ));\n }\n\n /** Context window manager for token-budgeted retrieval */\n get contextWindowManager(): ContextWindowManager {\n return (this._contextWindowManager ??= new ContextWindowManager(\n this.storage,\n this.salienceEngine,\n this.config.contextWindow\n ));\n }\n\n /** Memory formatter for LLM consumption */\n get memoryFormatter(): MemoryFormatter {\n return (this._memoryFormatter ??= new MemoryFormatter(this.config.formatter));\n }\n\n /** Multi-agent memory manager */\n get multiAgentManager(): MultiAgentMemoryManager {\n return (this._multiAgentManager ??= new MultiAgentMemoryManager(\n this.storage,\n this.config.multiAgent\n ));\n }\n\n /** Conflict resolver for memory conflicts */\n get conflictResolver(): ConflictResolver {\n return (this._conflictResolver ??= new ConflictResolver(this.config.conflictResolver));\n }\n\n // ==================== Session Lifecycle ====================\n\n /**\n * Start a new agent session.\n *\n * @param options - Session options\n * @returns Created session entity\n */\n async startSession(options: StartSessionOptions = {}): Promise<SessionEntity> {\n const session = await this.sessionManager.startSession(options);\n this.emit('session:started', { sessionId: session.name });\n return session;\n }\n\n /**\n * End an agent session.\n *\n * @param sessionId - Session to end\n * @param status - Session end status ('completed' or 'abandoned')\n * @returns End session result\n */\n async endSession(\n sessionId: string,\n status: 'completed' | 'abandoned' = 'completed'\n ): Promise<EndSessionResult> {\n const result = await this.sessionManager.endSession(sessionId, status);\n this.emit('session:ended', { sessionId, result });\n return result;\n }\n\n /**\n * Get the current active session or undefined.\n */\n async getActiveSession(): Promise<SessionEntity | undefined> {\n return this.sessionManager.getActiveSession();\n }\n\n // ==================== Working Memory Operations ====================\n\n /**\n * Add a new working memory entry.\n *\n * @param options - Memory creation options\n * @returns Created memory entity\n */\n async addWorkingMemory(options: CreateMemoryOptions): Promise<AgentEntity> {\n const memory = await this.workingMemory.createWorkingMemory(\n options.sessionId,\n options.content,\n {\n importance: options.importance,\n taskId: options.taskId,\n ttlHours: options.ttlHours,\n }\n );\n\n // Apply agent/visibility if multi-agent enabled\n if (this.config.enableMultiAgent && options.agentId) {\n const graph = await this.storage.getGraphForMutation();\n const entity = graph.entities.find((e) => e.name === memory.name) as AgentEntity | undefined;\n if (entity) {\n entity.agentId = options.agentId;\n entity.visibility = options.visibility ?? 'private';\n await this.storage.saveGraph(graph);\n }\n }\n\n this.emit('memory:created', { memoryId: memory.name, sessionId: options.sessionId });\n return memory;\n }\n\n /**\n * Get working memories for a session.\n *\n * @param sessionId - Session to query\n * @param filter - Optional filter criteria\n */\n async getSessionMemories(\n sessionId: string,\n filter?: { entityType?: string; taskId?: string; minImportance?: number }\n ): Promise<AgentEntity[]> {\n return this.workingMemory.getSessionMemories(sessionId, filter);\n }\n\n /**\n * Clear expired working memories.\n *\n * @returns Number of memories cleared\n */\n async clearExpiredMemories(): Promise<number> {\n const count = await this.workingMemory.clearExpired();\n if (count > 0) {\n this.emit('memory:expired', { count });\n }\n return count;\n }\n\n /**\n * Confirm a memory to strengthen it.\n *\n * @param memoryName - Memory to confirm\n * @param confidenceBoost - Optional confidence increase\n */\n async confirmMemory(memoryName: string, confidenceBoost?: number): Promise<ConfirmationResult> {\n return this.workingMemory.confirmMemory(memoryName, confidenceBoost);\n }\n\n /**\n * Promote a working memory to long-term storage.\n *\n * @param memoryName - Memory to promote\n * @param targetType - Target memory type (episodic or semantic)\n */\n async promoteMemory(\n memoryName: string,\n targetType: 'episodic' | 'semantic' = 'episodic'\n ): Promise<PromotionResult> {\n return this.workingMemory.promoteMemory(memoryName, targetType);\n }\n\n // ==================== Episodic Memory Operations ====================\n\n /**\n * Create an episodic memory (experience/event).\n *\n * @param content - Episode content/description\n * @param options - Episode creation options\n */\n async createEpisode(content: string, options?: CreateEpisodeOptions): Promise<AgentEntity> {\n return this.episodicMemory.createEpisode(content, options);\n }\n\n /**\n * Get timeline of episodes for a session.\n *\n * @param sessionId - Session to get timeline for\n * @param options - Timeline query options\n */\n async getTimeline(sessionId: string, options?: TimelineOptions): Promise<AgentEntity[]> {\n return this.episodicMemory.getTimeline(sessionId, options);\n }\n\n // ==================== Memory Consolidation ====================\n\n /**\n * Run consolidation for a session.\n *\n * @param sessionId - Session to consolidate\n * @returns Consolidation result\n */\n async consolidateSession(sessionId: string): Promise<ConsolidationResult> {\n const result = await this.consolidationPipeline.consolidateSession(sessionId);\n this.emit('consolidation:complete', { sessionId, result });\n return result;\n }\n\n // ==================== Context Retrieval ====================\n\n /**\n * Retrieve memories formatted for LLM context.\n *\n * @param options - Retrieval options\n * @returns Context package with formatted memories\n */\n async retrieveForContext(options: RetrieveContextOptions = {}): Promise<ContextPackage> {\n const retrievalOptions: ContextRetrievalOptions = {\n maxTokens: options.maxTokens ?? 4000,\n context: {\n currentSession: options.sessionId,\n currentTask: options.taskId,\n queryText: options.keywords?.join(' '),\n },\n includeWorkingMemory: options.includeWorkingMemory,\n includeEpisodicRecent: options.includeEpisodicRecent,\n includeSemanticRelevant: options.includeSemanticRelevant,\n mustInclude: options.mustInclude,\n };\n\n return this.contextWindowManager.retrieveForContext(retrievalOptions);\n }\n\n /**\n * Format memories for LLM prompt.\n *\n * @param memories - Memories to format\n * @param options - Format options\n */\n formatForPrompt(\n memories: AgentEntity[],\n options?: { maxTokens?: number; header?: string; separator?: string }\n ): string {\n return this.memoryFormatter.formatForPrompt(memories, options ?? {});\n }\n\n /**\n * Record memory access for tracking.\n *\n * @param memoryName - Memory accessed\n * @param context - Access context\n */\n recordAccess(memoryName: string, context: AccessContext): void {\n this.accessTracker.recordAccess(memoryName, context);\n }\n\n // ==================== Decay Operations ====================\n\n /**\n * Get memories that have decayed below threshold.\n *\n * @param threshold - Effective importance threshold\n */\n async getDecayedMemories(threshold: number = 0.1): Promise<AgentEntity[]> {\n return this.decayEngine.getDecayedMemories(threshold) as Promise<AgentEntity[]>;\n }\n\n /**\n * Forget weak memories (delete or archive).\n *\n * @param options - Forget options\n */\n async forgetWeakMemories(options: ForgetOptions): Promise<ForgetResult> {\n const result = await this.decayEngine.forgetWeakMemories(options);\n if (result.memoriesForgotten > 0) {\n this.emit('memory:forgotten', result);\n }\n return result;\n }\n\n /**\n * Reinforce a memory against decay.\n *\n * @param memoryName - Memory to reinforce\n * @param options - Reinforcement options\n */\n async reinforceMemory(\n memoryName: string,\n options?: { confirmationBoost?: number; confidenceBoost?: number }\n ): Promise<void> {\n await this.decayEngine.reinforceMemory(memoryName, options);\n }\n\n /**\n * Manually run a decay cycle.\n */\n async runDecayCycle(): Promise<DecayCycleResult> {\n return this.decayScheduler.runNow();\n }\n\n // ==================== Multi-Agent Operations ====================\n\n /**\n * Register an agent for multi-agent memory management.\n *\n * @param agentId - Unique agent identifier\n * @param metadata - Agent metadata\n */\n registerAgent(agentId: string, metadata: Omit<AgentMetadata, 'createdAt' | 'lastActiveAt'>): void {\n this.multiAgentManager.registerAgent(agentId, metadata);\n this.emit('agent:registered', { agentId });\n }\n\n /**\n * Get memories visible to specific agents.\n *\n * @param agentIds - Agents to check visibility for\n */\n async getSharedMemories(agentIds: string[]): Promise<AgentEntity[]> {\n return this.multiAgentManager.getSharedMemories(agentIds);\n }\n\n /**\n * Search memories across agents with trust weighting.\n *\n * @param requestingAgentId - Agent making the request\n * @param query - Search query\n * @param options - Search options\n */\n async searchCrossAgent(\n requestingAgentId: string,\n query: string,\n options?: { agentIds?: string[]; useTrustWeighting?: boolean; trustWeight?: number; entityType?: string }\n ): Promise<Array<{ memory: AgentEntity; relevanceScore: number; trustScore: number; combinedScore: number }>> {\n return this.multiAgentManager.searchCrossAgent(requestingAgentId, query, options);\n }\n\n /**\n * Copy a shared memory to another agent.\n *\n * @param sourceMemoryName - Memory to copy\n * @param targetAgentId - Target agent\n */\n async copyMemory(sourceMemoryName: string, targetAgentId: string): Promise<AgentEntity | null> {\n return this.multiAgentManager.copyMemory(sourceMemoryName, targetAgentId);\n }\n\n /**\n * Detect conflicts between agent memories.\n *\n * @param memories - Memories to check\n */\n async detectConflicts(memories: AgentEntity[]): Promise<ConflictInfo[]> {\n return this.multiAgentManager.detectConflicts(memories);\n }\n\n /**\n * Resolve a memory conflict using specified strategy.\n *\n * @param conflict - Conflict to resolve\n * @param strategy - Resolution strategy\n */\n async resolveConflict(\n conflict: ConflictInfo,\n strategy?: ConflictStrategy\n ): Promise<ResolutionResult> {\n const graph = await this.storage.loadGraph();\n const memories = graph.entities.filter((e): e is AgentEntity =>\n [conflict.primaryMemory, ...conflict.conflictingMemories].includes(e.name)\n );\n // Get agents map - use getAgent for each unique agentId in memories\n const agentIds = new Set(memories.map((m) => m.agentId).filter((id): id is string => !!id));\n const agents = new Map<string, AgentMetadata>();\n for (const id of agentIds) {\n const agent = this.multiAgentManager.getAgent(id);\n if (agent) {\n agents.set(id, agent);\n }\n }\n return this.conflictResolver.resolveConflict(conflict, memories, agents, strategy);\n }\n\n /**\n * Merge memories from multiple agents.\n *\n * @param memoryNames - Memories to merge\n * @param targetAgentId - Target agent for merged memory\n * @param options - Merge options\n */\n async mergeCrossAgent(\n memoryNames: string[],\n targetAgentId: string,\n options?: { resolveConflicts?: boolean; conflictStrategy?: ConflictStrategy }\n ): Promise<AgentEntity | null> {\n return this.multiAgentManager.mergeCrossAgent(memoryNames, targetAgentId, options);\n }\n\n // ==================== Configuration ====================\n\n /**\n * Set summarization provider for consolidation.\n *\n * @param provider - Provider implementation\n */\n setSummarizationProvider(provider: ISummarizationProvider): void {\n this.summarizationService.registerProvider(provider);\n }\n\n /**\n * Get the current configuration.\n */\n getConfig(): AgentMemoryConfig {\n return { ...this.config };\n }\n\n // ==================== Lifecycle ====================\n\n /**\n * Stop all background operations (decay scheduler).\n */\n stop(): void {\n if (this._decayScheduler) {\n this._decayScheduler.stop();\n }\n this.emit('manager:stopped');\n }\n}\n","/**\r\n * Manager Context\r\n *\r\n * Central context holding all manager instances with lazy initialization.\r\n * Provides direct manager access for toolHandlers.\r\n * Phase 4: Removed convenience methods - use managers directly.\r\n *\r\n * @module core/ManagerContext\r\n */\r\n\r\nimport path from 'path';\r\nimport { GraphStorage } from './GraphStorage.js';\r\nimport { createStorageFromPath } from './StorageFactory.js';\r\nimport { EntityManager } from './EntityManager.js';\r\nimport { RelationManager } from './RelationManager.js';\r\nimport { ObservationManager } from './ObservationManager.js';\r\nimport { HierarchyManager } from './HierarchyManager.js';\r\nimport { GraphTraversal } from './GraphTraversal.js';\r\nimport { SearchManager } from '../search/SearchManager.js';\r\nimport { RankedSearch } from '../search/RankedSearch.js';\r\nimport { SemanticSearch, createEmbeddingService, createVectorStore } from '../search/index.js';\r\nimport { IOManager } from '../features/IOManager.js';\r\nimport { TagManager } from '../features/TagManager.js';\r\nimport { AnalyticsManager } from '../features/AnalyticsManager.js';\r\nimport { CompressionManager } from '../features/CompressionManager.js';\r\nimport { ArchiveManager } from '../features/ArchiveManager.js';\r\nimport { AccessTracker } from '../agent/AccessTracker.js';\r\nimport { DecayEngine } from '../agent/DecayEngine.js';\r\nimport { DecayScheduler } from '../agent/DecayScheduler.js';\r\nimport { SalienceEngine } from '../agent/SalienceEngine.js';\r\nimport { ContextWindowManager } from '../agent/ContextWindowManager.js';\r\nimport { MemoryFormatter } from '../agent/MemoryFormatter.js';\r\nimport { AgentMemoryManager } from '../agent/AgentMemoryManager.js';\r\nimport type { AgentMemoryConfig } from '../agent/AgentMemoryConfig.js';\r\nimport { getEmbeddingConfig } from '../utils/constants.js';\r\nimport { validateFilePath } from '../utils/index.js';\r\n\r\n/**\r\n * Context holding all manager instances with lazy initialization.\r\n * Provides direct manager access for toolHandlers.\r\n */\r\nexport class ManagerContext {\r\n // Type as GraphStorage for manager compatibility; actual instance may be SQLiteStorage\r\n // which implements the same interface via duck typing\r\n readonly storage: GraphStorage;\r\n private readonly savedSearchesFilePath: string;\r\n private readonly tagAliasesFilePath: string;\r\n\r\n // Lazy-initialized managers\r\n private _entityManager?: EntityManager;\r\n private _relationManager?: RelationManager;\r\n private _observationManager?: ObservationManager;\r\n private _hierarchyManager?: HierarchyManager;\r\n private _graphTraversal?: GraphTraversal;\r\n private _searchManager?: SearchManager;\r\n private _semanticSearch?: SemanticSearch | null;\r\n private _rankedSearch?: RankedSearch;\r\n private _ioManager?: IOManager;\r\n private _tagManager?: TagManager;\r\n private _analyticsManager?: AnalyticsManager;\r\n private _compressionManager?: CompressionManager;\r\n private _archiveManager?: ArchiveManager;\r\n private _accessTracker?: AccessTracker;\r\n private _decayEngine?: DecayEngine;\r\n private _decayScheduler?: DecayScheduler;\r\n private _salienceEngine?: SalienceEngine;\r\n private _contextWindowManager?: ContextWindowManager;\r\n private _memoryFormatter?: MemoryFormatter;\r\n private _agentMemory?: AgentMemoryManager;\r\n\r\n constructor(memoryFilePath: string) {\r\n // Security: Validate path to prevent path traversal attacks\r\n const validatedPath = validateFilePath(memoryFilePath);\r\n\r\n // Derive paths for saved searches and tag aliases\r\n const dir = path.dirname(validatedPath);\r\n const basename = path.basename(validatedPath, path.extname(validatedPath));\r\n this.savedSearchesFilePath = path.join(dir, `${basename}-saved-searches.jsonl`);\r\n this.tagAliasesFilePath = path.join(dir, `${basename}-tag-aliases.jsonl`);\r\n // Use StorageFactory to respect MEMORY_STORAGE_TYPE environment variable\r\n // Type assertion: SQLiteStorage implements same interface as GraphStorage\r\n this.storage = createStorageFromPath(validatedPath) as GraphStorage;\r\n }\r\n\r\n // ==================== MANAGER ACCESSORS ====================\r\n // Use these for direct manager access in toolHandlers\r\n\r\n /** EntityManager - Entity CRUD and tag operations */\r\n get entityManager(): EntityManager {\r\n return (this._entityManager ??= new EntityManager(this.storage));\r\n }\r\n\r\n /** RelationManager - Relation CRUD */\r\n get relationManager(): RelationManager {\r\n return (this._relationManager ??= new RelationManager(this.storage));\r\n }\r\n\r\n /** ObservationManager - Observation CRUD */\r\n get observationManager(): ObservationManager {\r\n return (this._observationManager ??= new ObservationManager(this.storage));\r\n }\r\n\r\n /** HierarchyManager - Entity hierarchy operations */\r\n get hierarchyManager(): HierarchyManager {\r\n return (this._hierarchyManager ??= new HierarchyManager(this.storage));\r\n }\r\n\r\n /** GraphTraversal - Phase 4 Sprint 6-8: Graph traversal algorithms */\r\n get graphTraversal(): GraphTraversal {\r\n return (this._graphTraversal ??= new GraphTraversal(this.storage));\r\n }\r\n\r\n /** SearchManager - All search operations */\r\n get searchManager(): SearchManager {\r\n return (this._searchManager ??= new SearchManager(this.storage, this.savedSearchesFilePath));\r\n }\r\n\r\n /**\r\n * SemanticSearch - Phase 4 Sprint 12: Semantic similarity search.\r\n * Returns null if no embedding provider is configured.\r\n */\r\n get semanticSearch(): SemanticSearch | null {\r\n if (this._semanticSearch === undefined) {\r\n const config = getEmbeddingConfig();\r\n const embeddingService = createEmbeddingService(config);\r\n\r\n if (embeddingService) {\r\n const vectorStore = createVectorStore('jsonl'); // Use in-memory for now\r\n this._semanticSearch = new SemanticSearch(embeddingService, vectorStore);\r\n } else {\r\n this._semanticSearch = null;\r\n }\r\n }\r\n return this._semanticSearch;\r\n }\r\n\r\n /** RankedSearch - Phase 11: TF-IDF/BM25 ranked search for hybrid search */\r\n get rankedSearch(): RankedSearch {\r\n return (this._rankedSearch ??= new RankedSearch(this.storage));\r\n }\r\n\r\n /** IOManager - Import, export, and backup operations */\r\n get ioManager(): IOManager {\r\n return (this._ioManager ??= new IOManager(this.storage));\r\n }\r\n\r\n /** TagManager - Tag alias management */\r\n get tagManager(): TagManager {\r\n return (this._tagManager ??= new TagManager(this.tagAliasesFilePath));\r\n }\r\n\r\n /** AnalyticsManager - Graph statistics and validation */\r\n get analyticsManager(): AnalyticsManager {\r\n return (this._analyticsManager ??= new AnalyticsManager(this.storage));\r\n }\r\n\r\n /** CompressionManager - Duplicate detection and entity merging */\r\n get compressionManager(): CompressionManager {\r\n return (this._compressionManager ??= new CompressionManager(this.storage));\r\n }\r\n\r\n /** ArchiveManager - Entity archival operations */\r\n get archiveManager(): ArchiveManager {\r\n return (this._archiveManager ??= new ArchiveManager(this.storage));\r\n }\r\n\r\n /**\r\n * AccessTracker - Phase 1 Agent Memory: Access pattern tracking.\r\n * Automatically wired to EntityManager, SearchManager, and GraphTraversal.\r\n */\r\n get accessTracker(): AccessTracker {\r\n if (!this._accessTracker) {\r\n this._accessTracker = new AccessTracker(this.storage);\r\n this.wireAccessTracker();\r\n }\r\n return this._accessTracker;\r\n }\r\n\r\n /**\r\n * Wire AccessTracker to managers that support access tracking.\r\n * @internal\r\n */\r\n private wireAccessTracker(): void {\r\n if (this._accessTracker) {\r\n // Wire to EntityManager\r\n this.entityManager.setAccessTracker(this._accessTracker);\r\n // Wire to SearchManager\r\n this.searchManager.setAccessTracker(this._accessTracker);\r\n // Wire to GraphTraversal\r\n this.graphTraversal.setAccessTracker(this._accessTracker);\r\n }\r\n }\r\n\r\n /**\r\n * DecayEngine - Phase 1 Agent Memory: Memory importance decay calculations.\r\n *\r\n * Configurable via environment variables:\r\n * - MEMORY_DECAY_HALF_LIFE_HOURS (default: 168 = 1 week)\r\n * - MEMORY_DECAY_MIN_IMPORTANCE (default: 0.1)\r\n * - MEMORY_DECAY_IMPORTANCE_MOD (default: true)\r\n * - MEMORY_DECAY_ACCESS_MOD (default: true)\r\n */\r\n get decayEngine(): DecayEngine {\r\n if (!this._decayEngine) {\r\n this._decayEngine = new DecayEngine(this.storage, this.accessTracker, {\r\n halfLifeHours: this.getEnvNumber('MEMORY_DECAY_HALF_LIFE_HOURS', 168),\r\n minImportance: this.getEnvNumber('MEMORY_DECAY_MIN_IMPORTANCE', 0.1),\r\n importanceModulation: this.getEnvBool('MEMORY_DECAY_IMPORTANCE_MOD', true),\r\n accessModulation: this.getEnvBool('MEMORY_DECAY_ACCESS_MOD', true),\r\n });\r\n }\r\n return this._decayEngine;\r\n }\r\n\r\n /**\r\n * DecayScheduler - Phase 1 Agent Memory: Scheduled decay and forget operations.\r\n *\r\n * Returns undefined if auto-decay is not enabled.\r\n *\r\n * Configurable via environment variables:\r\n * - MEMORY_AUTO_DECAY (default: false) - Enable to create scheduler\r\n * - MEMORY_DECAY_INTERVAL_MS (default: 3600000 = 1 hour)\r\n * - MEMORY_AUTO_FORGET (default: false)\r\n * - MEMORY_FORGET_THRESHOLD (default: 0.05)\r\n */\r\n get decayScheduler(): DecayScheduler | undefined {\r\n if (this._decayScheduler) return this._decayScheduler;\r\n\r\n if (this.getEnvBool('MEMORY_AUTO_DECAY', false)) {\r\n this._decayScheduler = new DecayScheduler(this.decayEngine, {\r\n decayIntervalMs: this.getEnvNumber('MEMORY_DECAY_INTERVAL_MS', 3600000),\r\n autoForget: this.getEnvBool('MEMORY_AUTO_FORGET', false),\r\n forgetOptions: {\r\n effectiveImportanceThreshold: this.getEnvNumber(\r\n 'MEMORY_FORGET_THRESHOLD',\r\n 0.05\r\n ),\r\n },\r\n });\r\n }\r\n\r\n return this._decayScheduler;\r\n }\r\n\r\n /**\r\n * SalienceEngine - Phase 4 Agent Memory: Context-aware relevance scoring.\r\n *\r\n * Configurable via environment variables:\r\n * - MEMORY_SALIENCE_IMPORTANCE_WEIGHT (default: 0.25)\r\n * - MEMORY_SALIENCE_RECENCY_WEIGHT (default: 0.25)\r\n * - MEMORY_SALIENCE_FREQUENCY_WEIGHT (default: 0.2)\r\n * - MEMORY_SALIENCE_CONTEXT_WEIGHT (default: 0.2)\r\n * - MEMORY_SALIENCE_NOVELTY_WEIGHT (default: 0.1)\r\n */\r\n get salienceEngine(): SalienceEngine {\r\n if (!this._salienceEngine) {\r\n this._salienceEngine = new SalienceEngine(\r\n this.storage,\r\n this.accessTracker,\r\n this.decayEngine,\r\n {\r\n importanceWeight: this.getEnvNumber('MEMORY_SALIENCE_IMPORTANCE_WEIGHT', 0.25),\r\n recencyWeight: this.getEnvNumber('MEMORY_SALIENCE_RECENCY_WEIGHT', 0.25),\r\n frequencyWeight: this.getEnvNumber('MEMORY_SALIENCE_FREQUENCY_WEIGHT', 0.2),\r\n contextWeight: this.getEnvNumber('MEMORY_SALIENCE_CONTEXT_WEIGHT', 0.2),\r\n noveltyWeight: this.getEnvNumber('MEMORY_SALIENCE_NOVELTY_WEIGHT', 0.1),\r\n }\r\n );\r\n }\r\n return this._salienceEngine;\r\n }\r\n\r\n /**\r\n * ContextWindowManager - Phase 4 Agent Memory: Token-budgeted memory retrieval.\r\n *\r\n * Configurable via environment variables:\r\n * - MEMORY_CONTEXT_MAX_TOKENS (default: 4000)\r\n * - MEMORY_CONTEXT_TOKEN_MULTIPLIER (default: 1.3)\r\n * - MEMORY_CONTEXT_RESERVE_BUFFER (default: 100)\r\n * - MEMORY_CONTEXT_DIVERSITY_THRESHOLD (default: 0.8)\r\n */\r\n get contextWindowManager(): ContextWindowManager {\r\n if (!this._contextWindowManager) {\r\n this._contextWindowManager = new ContextWindowManager(\r\n this.storage,\r\n this.salienceEngine,\r\n {\r\n defaultMaxTokens: this.getEnvNumber('MEMORY_CONTEXT_MAX_TOKENS', 4000),\r\n tokenMultiplier: this.getEnvNumber('MEMORY_CONTEXT_TOKEN_MULTIPLIER', 1.3),\r\n reserveBuffer: this.getEnvNumber('MEMORY_CONTEXT_RESERVE_BUFFER', 100),\r\n diversityThreshold: this.getEnvNumber('MEMORY_CONTEXT_DIVERSITY_THRESHOLD', 0.8),\r\n enforceDiversity: this.getEnvBool('MEMORY_CONTEXT_ENFORCE_DIVERSITY', true),\r\n }\r\n );\r\n }\r\n return this._contextWindowManager;\r\n }\r\n\r\n /**\r\n * MemoryFormatter - Phase 4 Agent Memory: Format memories for LLM consumption.\r\n */\r\n get memoryFormatter(): MemoryFormatter {\r\n if (!this._memoryFormatter) {\r\n this._memoryFormatter = new MemoryFormatter({\r\n includeTimestamps: this.getEnvBool('MEMORY_FORMAT_TIMESTAMPS', true),\r\n includeMemoryType: this.getEnvBool('MEMORY_FORMAT_MEMORY_TYPE', true),\r\n });\r\n }\r\n return this._memoryFormatter;\r\n }\r\n\r\n /**\r\n * AgentMemoryManager - Phase 5 Agent Memory: Unified facade for all agent memory operations.\r\n *\r\n * Provides high-level API for:\r\n * - Session lifecycle management\r\n * - Working memory creation and management\r\n * - Memory consolidation and promotion\r\n * - Context retrieval for LLM consumption\r\n * - Multi-agent memory coordination\r\n *\r\n * @param config - Optional configuration override (default: loaded from env vars)\r\n */\r\n agentMemory(config?: AgentMemoryConfig): AgentMemoryManager {\r\n if (!this._agentMemory || config) {\r\n this._agentMemory = new AgentMemoryManager(this.storage, config);\r\n }\r\n return this._agentMemory;\r\n }\r\n\r\n // ==================== Environment Variable Helpers ====================\r\n\r\n /**\r\n * Get a number from environment variable with default.\r\n * @internal\r\n */\r\n private getEnvNumber(key: string, defaultValue: number): number {\r\n const value = process.env[key];\r\n if (value === undefined) return defaultValue;\r\n const parsed = parseFloat(value);\r\n return isNaN(parsed) ? defaultValue : parsed;\r\n }\r\n\r\n /**\r\n * Get a boolean from environment variable with default.\r\n * @internal\r\n */\r\n private getEnvBool(key: string, defaultValue: boolean): boolean {\r\n const value = process.env[key];\r\n if (value === undefined) return defaultValue;\r\n return value.toLowerCase() === 'true';\r\n }\r\n}\r\n","/**\n * Interactive CLI Mode (REPL)\n *\n * Provides an interactive shell for exploring the knowledge graph.\n *\n * @module cli/interactive\n */\n\nimport * as readline from 'readline';\nimport { ManagerContext } from '../core/ManagerContext.js';\nimport type { GlobalOptions } from './options.js';\nimport chalk from 'chalk';\n\ninterface InteractiveContext {\n ctx: ManagerContext;\n options: GlobalOptions;\n history: string[];\n}\n\nexport async function startInteractiveMode(options: GlobalOptions): Promise<void> {\n const ctx = new ManagerContext(options.storage);\n\n const interactiveCtx: InteractiveContext = {\n ctx,\n options,\n history: [],\n };\n\n // Get entity names for completion\n const graph = await ctx.storage.loadGraph();\n const entityNames = graph.entities.map(e => e.name);\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n prompt: chalk.cyan('memory> '),\n completer: (line: string) => {\n const completions = [\n 'entities', 'relations', 'search', 'get', 'stats',\n 'help', 'exit', 'clear', 'history',\n ...entityNames,\n ];\n const hits = completions.filter(c => c.toLowerCase().startsWith(line.toLowerCase()));\n return [hits.length ? hits : completions, line];\n },\n });\n\n console.log(chalk.green('MemoryJS Interactive Mode'));\n console.log(chalk.gray('Type \"help\" for commands, \"exit\" to quit.\\n'));\n\n rl.prompt();\n\n rl.on('line', async (line: string) => {\n const trimmed = line.trim();\n\n if (!trimmed) {\n rl.prompt();\n return;\n }\n\n interactiveCtx.history.push(trimmed);\n\n try {\n await processCommand(trimmed, interactiveCtx, rl);\n } catch (error) {\n console.error(chalk.red(`Error: ${(error as Error).message}`));\n }\n\n rl.prompt();\n });\n\n rl.on('close', () => {\n console.log(chalk.gray('\\nGoodbye!'));\n process.exit(0);\n });\n}\n\nasync function processCommand(\n input: string,\n ctx: InteractiveContext,\n rl: readline.Interface\n): Promise<void> {\n const [command, ...args] = input.split(/\\s+/);\n\n switch (command.toLowerCase()) {\n case 'help':\n case '.help':\n showHelp();\n break;\n\n case 'exit':\n case '.exit':\n case 'quit':\n rl.close();\n break;\n\n case 'clear':\n case '.clear':\n console.clear();\n break;\n\n case 'entities':\n case 'ls': {\n const graph = await ctx.ctx.storage.loadGraph();\n const entities = graph.entities;\n console.log(`\\nEntities (${entities.length}):`);\n for (const e of entities.slice(0, 20)) {\n console.log(` ${chalk.cyan(e.name)} [${e.entityType}]`);\n }\n if (entities.length > 20) {\n console.log(` ... and ${entities.length - 20} more`);\n }\n break;\n }\n\n case 'get': {\n const name = args.join(' ');\n if (!name) {\n console.log(chalk.yellow('Usage: get <entity-name>'));\n break;\n }\n const entity = await ctx.ctx.entityManager.getEntity(name);\n if (entity) {\n console.log(JSON.stringify(entity, null, 2));\n } else {\n console.log(chalk.yellow(`Entity not found: ${name}`));\n }\n break;\n }\n\n case 'search': {\n const query = args.join(' ');\n if (!query) {\n console.log(chalk.yellow('Usage: search <query>'));\n break;\n }\n const result = await ctx.ctx.searchManager.searchNodes(query);\n console.log(`\\nSearch results for \"${query}\":`);\n for (const entity of result.entities.slice(0, 10)) {\n console.log(` ${chalk.cyan(entity.name)} [${entity.entityType}]`);\n if (entity.observations && entity.observations.length > 0) {\n const preview = entity.observations[0].substring(0, 60);\n console.log(` ${chalk.gray(preview)}${entity.observations[0].length > 60 ? '...' : ''}`);\n }\n }\n if (result.entities.length > 10) {\n console.log(` ... and ${result.entities.length - 10} more`);\n }\n break;\n }\n\n case 'relations': {\n const name = args.join(' ');\n if (!name) {\n console.log(chalk.yellow('Usage: relations <entity-name>'));\n break;\n }\n const relations = await ctx.ctx.relationManager.getRelations(name);\n if (relations.length === 0) {\n console.log(chalk.yellow(`No relations found for: ${name}`));\n break;\n }\n console.log(`\\nRelations for \"${name}\":`);\n for (const rel of relations) {\n if (rel.from === name) {\n console.log(` ${chalk.cyan(name)} --[${rel.relationType}]--> ${rel.to}`);\n } else {\n console.log(` ${rel.from} --[${rel.relationType}]--> ${chalk.cyan(name)}`);\n }\n }\n break;\n }\n\n case 'stats': {\n const stats = await ctx.ctx.analyticsManager.getGraphStats();\n console.log(`\\nKnowledge Graph Statistics:`);\n console.log(` Entities: ${stats.totalEntities}`);\n console.log(` Relations: ${stats.totalRelations}`);\n console.log(` Entity Types: ${Object.keys(stats.entityTypesCounts).length}`);\n console.log(` Relation Types: ${Object.keys(stats.relationTypesCounts).length}`);\n break;\n }\n\n case 'history':\n console.log('\\nCommand history:');\n ctx.history.slice(-20).forEach((cmd, i) => {\n console.log(` ${i + 1}. ${cmd}`);\n });\n break;\n\n default:\n console.log(chalk.yellow(`Unknown command: ${command}. Type \"help\" for available commands.`));\n }\n}\n\nfunction showHelp(): void {\n console.log(`\n${chalk.green('Available Commands:')}\n\n ${chalk.cyan('entities')} / ${chalk.cyan('ls')} List all entities\n ${chalk.cyan('get <name>')} Get entity details\n ${chalk.cyan('search <query>')} Search entities\n ${chalk.cyan('relations <name>')} Show relations for entity\n ${chalk.cyan('stats')} Show graph statistics\n ${chalk.cyan('history')} Show command history\n ${chalk.cyan('clear')} Clear screen\n ${chalk.cyan('help')} Show this help\n ${chalk.cyan('exit')} Exit interactive mode\n\n${chalk.gray('Tab completion available for entity names.')}\n`);\n}\n","/**\n * MemoryJS CLI\n *\n * Command-line interface for MemoryJS knowledge graph operations.\n * Supports entity/relation CRUD, search, import/export, and interactive mode.\n *\n * @module cli\n */\n\nimport { Command } from 'commander';\nimport { registerCommands } from './commands/index.js';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\n// Get package version\nfunction getVersion(): string {\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // Try to find package.json relative to the CLI file\n const pkgPath = join(__dirname, '..', '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n return pkg.version;\n } catch {\n return '0.0.0';\n }\n}\n\nconst program = new Command();\n\nprogram\n .name('memory')\n .description('MemoryJS - Knowledge Graph CLI')\n .version(getVersion(), '-v, --version', 'Output the current version');\n\n// Global options\nprogram\n .option('-s, --storage <path>', 'Path to storage file', './memory.jsonl')\n .option('-f, --format <type>', 'Output format (json|table|csv)', 'json')\n .option('-q, --quiet', 'Suppress non-essential output')\n .option('--verbose', 'Enable verbose/debug output');\n\n// Register all commands\nregisterCommands(program);\n\n// Parse and execute\nprogram.parse();\n","/**\n * CLI Command Registry\n *\n * Registers all command categories with the main program.\n *\n * @module cli/commands\n */\n\nimport { Command } from 'commander';\nimport { ManagerContext } from '../../core/ManagerContext.js';\nimport { parseGlobalOptions, createLogger, type GlobalOptions } from '../options.js';\nimport { findConfigFile, loadConfig, mergeConfig } from '../config.js';\nimport {\n formatEntities,\n formatRelations,\n formatEntityDetail,\n formatSearchResults,\n formatSuccess,\n formatError,\n} from '../formatters.js';\n\n/**\n * Get merged options from config file and CLI.\n */\nfunction getOptions(program: Command): GlobalOptions {\n const cliOpts = program.opts();\n const configPath = findConfigFile();\n const fileConfig = configPath ? loadConfig(configPath) : {};\n return mergeConfig(fileConfig, parseGlobalOptions(cliOpts));\n}\n\n/**\n * Create a ManagerContext with the specified storage path.\n */\nfunction createContext(options: GlobalOptions): ManagerContext {\n return new ManagerContext(options.storage);\n}\n\nexport function registerCommands(program: Command): void {\n // ==================== Entity Commands ====================\n const entity = program\n .command('entity')\n .description('Manage entities (create, read, update, delete)');\n\n entity\n .command('create <name>')\n .description('Create a new entity')\n .option('-t, --type <type>', 'Entity type', 'generic')\n .option('-o, --observation <obs...>', 'Observations to add')\n .option('--tags <tags...>', 'Tags to add')\n .option('-i, --importance <n>', 'Importance score (0-10)', parseFloat)\n .action(async (name: string, opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const [entity] = await ctx.entityManager.createEntities([{\n name,\n entityType: (opts.type as string) || 'generic',\n observations: (opts.observation as string[]) || [],\n tags: (opts.tags as string[]) || [],\n importance: opts.importance as number | undefined,\n }]);\n\n logger.info(formatSuccess(`Created entity: ${entity.name}`));\n if (!options.quiet) {\n console.log(formatEntityDetail(entity, options.format));\n }\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n entity\n .command('get <name>')\n .description('Get an entity by name')\n .action(async (name: string) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const entity = await ctx.entityManager.getEntity(name);\n if (!entity) {\n logger.error(formatError(`Entity \"${name}\" not found`));\n process.exit(1);\n }\n console.log(formatEntityDetail(entity, options.format));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n entity\n .command('list')\n .description('List entities')\n .option('-t, --type <type>', 'Filter by entity type')\n .option('--tags <tags...>', 'Filter by tags')\n .option('-l, --limit <n>', 'Limit results', parseInt)\n .action(async (opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const graph = await ctx.storage.loadGraph();\n let entities = [...graph.entities];\n\n // Apply filters\n if (opts.type) {\n entities = entities.filter(e => e.entityType === opts.type);\n }\n if (opts.tags) {\n const tags = opts.tags as string[];\n entities = entities.filter(e =>\n tags.some(tag => (e.tags || []).includes(tag))\n );\n }\n if (opts.limit) {\n entities = entities.slice(0, opts.limit as number);\n }\n\n console.log(formatEntities(entities, options.format));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n entity\n .command('update <name>')\n .description('Update an entity')\n .option('-t, --type <type>', 'New entity type')\n .option('-o, --observation <obs...>', 'Add observations')\n .option('--tags <tags...>', 'Set tags')\n .option('-i, --importance <n>', 'Set importance (0-10)', parseFloat)\n .action(async (name: string, opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const existing = await ctx.entityManager.getEntity(name);\n if (!existing) {\n throw new Error(`Entity \"${name}\" not found`);\n }\n\n const updates: Record<string, unknown> = {};\n if (opts.type) updates.entityType = opts.type;\n if (opts.observation) {\n updates.observations = [...(existing.observations || []), ...(opts.observation as string[])];\n }\n if (opts.tags) updates.tags = opts.tags;\n if (opts.importance !== undefined) updates.importance = opts.importance;\n\n const entity = await ctx.entityManager.updateEntity(name, updates);\n\n logger.info(formatSuccess(`Updated entity: ${entity.name}`));\n if (!options.quiet) {\n console.log(formatEntityDetail(entity, options.format));\n }\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n entity\n .command('delete <name>')\n .description('Delete an entity')\n .option('-f, --force', 'Skip confirmation')\n .action(async (name: string, opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n await ctx.entityManager.deleteEntities([name]);\n logger.info(formatSuccess(`Deleted entity: ${name}`));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n // ==================== Relation Commands ====================\n const relation = program\n .command('relation')\n .description('Manage relations between entities');\n\n relation\n .command('create <from> <type> <to>')\n .description('Create a new relation')\n .action(async (from: string, relationType: string, to: string) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n await ctx.relationManager.createRelations([{\n from,\n to,\n relationType,\n }]);\n\n logger.info(formatSuccess(`Created relation: ${from} --[${relationType}]--> ${to}`));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n relation\n .command('list')\n .description('List relations')\n .option('--from <entity>', 'Filter by source entity')\n .option('--to <entity>', 'Filter by target entity')\n .option('-t, --type <type>', 'Filter by relation type')\n .action(async (opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const graph = await ctx.storage.loadGraph();\n let relations = [...graph.relations];\n\n // Apply filters\n if (opts.from) {\n relations = relations.filter(r => r.from === opts.from);\n }\n if (opts.to) {\n relations = relations.filter(r => r.to === opts.to);\n }\n if (opts.type) {\n relations = relations.filter(r => r.relationType === opts.type);\n }\n\n console.log(formatRelations(relations, options.format));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n relation\n .command('delete <from> <type> <to>')\n .description('Delete a relation')\n .action(async (from: string, relationType: string, to: string) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n await ctx.relationManager.deleteRelations([{ from, to, relationType }]);\n logger.info(formatSuccess(`Deleted relation: ${from} --[${relationType}]--> ${to}`));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n // ==================== Search Command ====================\n program\n .command('search <query>')\n .description('Search entities and observations')\n .option('-l, --limit <n>', 'Limit results', parseInt, 10)\n .option('-t, --type <type>', 'Filter by entity type')\n .action(async (query: string, opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n logger.debug(`Searching for: ${query}`);\n const result = await ctx.searchManager.searchNodes(query);\n\n let entities = result.entities.map((entity, idx) => ({\n entity,\n score: 1.0 - idx * 0.01, // Simple ranking by position\n }));\n\n if (opts.type) {\n entities = entities.filter(r => r.entity.entityType === opts.type);\n }\n if (opts.limit) {\n entities = entities.slice(0, opts.limit as number);\n }\n\n console.log(formatSearchResults(entities, options.format));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n // ==================== Import Command ====================\n program\n .command('import <file>')\n .description('Import data from file')\n .option('-f, --format <format>', 'File format (json|csv|graphml)', 'json')\n .option('--merge <strategy>', 'Merge strategy (replace|skip|merge|fail)', 'skip')\n .action(async (file: string, opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const fs = await import('fs');\n const data = fs.readFileSync(file, 'utf-8');\n\n const result = await ctx.ioManager.importGraph(\n opts.format as 'json' | 'csv' | 'graphml',\n data,\n opts.merge as 'replace' | 'skip' | 'merge' | 'fail'\n );\n\n logger.info(formatSuccess(\n `Imported ${result.entitiesImported} entities and ${result.relationsImported} relations`\n ));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n // ==================== Export Command ====================\n program\n .command('export <file>')\n .description('Export data to file')\n .option('-f, --format <format>', 'Output format (json|csv|graphml|markdown|mermaid)', 'json')\n .action(async (file: string, opts: Record<string, unknown>) => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const graph = await ctx.storage.loadGraph();\n const data = ctx.ioManager.exportGraph(\n graph,\n opts.format as 'json' | 'csv' | 'graphml' | 'markdown' | 'mermaid'\n );\n\n const fs = await import('fs');\n fs.writeFileSync(file, data, 'utf-8');\n\n logger.info(formatSuccess(`Exported to ${file}`));\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n // ==================== Stats Command ====================\n program\n .command('stats')\n .description('Show knowledge graph statistics')\n .action(async () => {\n const options = getOptions(program);\n const logger = createLogger(options);\n const ctx = createContext(options);\n\n try {\n const stats = await ctx.analyticsManager.getGraphStats();\n\n if (options.format === 'json') {\n console.log(JSON.stringify(stats, null, 2));\n } else {\n console.log(`\nKnowledge Graph Statistics\n==========================\nEntities: ${stats.entityCount}\nRelations: ${stats.relationCount}\nEntity Types: ${stats.entityTypeCount}\nRelation Types: ${stats.relationTypeCount}\nObservations: ${stats.observationCount}\nTags Used: ${stats.uniqueTagCount}\n`);\n }\n } catch (error) {\n logger.error(formatError((error as Error).message));\n process.exit(1);\n }\n });\n\n // ==================== Interactive Mode ====================\n program\n .command('interactive')\n .alias('i')\n .description('Start interactive REPL mode')\n .action(async () => {\n const options = getOptions(program);\n const { startInteractiveMode } = await import('../interactive.js');\n await startInteractiveMode(options);\n });\n}\n","/**\n * CLI Global Options\n *\n * Shared options and context for all CLI commands.\n *\n * @module cli/options\n */\n\nexport interface GlobalOptions {\n storage: string;\n format: 'json' | 'table' | 'csv';\n quiet: boolean;\n verbose: boolean;\n}\n\nexport const defaultOptions: GlobalOptions = {\n storage: process.env.MEMORYJS_STORAGE_PATH || './memory.jsonl',\n format: (process.env.MEMORYJS_OUTPUT_FORMAT as GlobalOptions['format']) || 'json',\n quiet: false,\n verbose: false,\n};\n\n/**\n * Parse and validate global options from commander.\n */\nexport function parseGlobalOptions(opts: Record<string, unknown>): GlobalOptions {\n const format = opts.format as string;\n if (format && !['json', 'table', 'csv'].includes(format)) {\n console.error(`Invalid format: ${format}. Use json, table, or csv.`);\n process.exit(1);\n }\n\n return {\n storage: (opts.storage as string) || defaultOptions.storage,\n format: (format as GlobalOptions['format']) || defaultOptions.format,\n quiet: Boolean(opts.quiet),\n verbose: Boolean(opts.verbose),\n };\n}\n\n/**\n * Logging utilities that respect quiet/verbose flags.\n */\nexport function createLogger(options: GlobalOptions) {\n return {\n info: (msg: string) => !options.quiet && console.log(msg),\n debug: (msg: string) => options.verbose && console.log(`[DEBUG] ${msg}`),\n error: (msg: string) => console.error(`[ERROR] ${msg}`),\n warn: (msg: string) => !options.quiet && console.warn(`[WARN] ${msg}`),\n };\n}\n","/**\n * CLI Configuration File Support\n *\n * Load configuration from .memoryjsrc or memoryjs.config.json.\n *\n * @module cli/config\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { resolve, dirname } from 'path';\nimport type { GlobalOptions } from './options.js';\n\nconst CONFIG_FILES = [\n '.memoryjsrc',\n '.memoryjsrc.json',\n 'memoryjs.config.json',\n];\n\n/**\n * Search for config file starting from cwd and moving up.\n */\nexport function findConfigFile(startDir: string = process.cwd()): string | null {\n let currentDir = startDir;\n const root = resolve('/');\n\n while (currentDir !== root) {\n for (const filename of CONFIG_FILES) {\n const configPath = resolve(currentDir, filename);\n if (existsSync(configPath)) {\n return configPath;\n }\n }\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) break; // Reached root\n currentDir = parentDir;\n }\n\n return null;\n}\n\n/**\n * Load configuration from file.\n */\nexport function loadConfig(configPath: string): Partial<GlobalOptions> {\n try {\n const content = readFileSync(configPath, 'utf-8');\n const config = JSON.parse(content);\n return validateConfig(config);\n } catch (error) {\n console.warn(`Warning: Failed to load config from ${configPath}`);\n return {};\n }\n}\n\n/**\n * Validate and sanitize config values.\n */\nfunction validateConfig(config: Record<string, unknown>): Partial<GlobalOptions> {\n const validated: Partial<GlobalOptions> = {};\n\n if (typeof config.storage === 'string') {\n validated.storage = config.storage;\n }\n\n if (config.format && ['json', 'table', 'csv'].includes(config.format as string)) {\n validated.format = config.format as GlobalOptions['format'];\n }\n\n if (typeof config.quiet === 'boolean') {\n validated.quiet = config.quiet;\n }\n\n if (typeof config.verbose === 'boolean') {\n validated.verbose = config.verbose;\n }\n\n return validated;\n}\n\n/**\n * Merge config file with CLI options. CLI takes precedence.\n */\nexport function mergeConfig(\n fileConfig: Partial<GlobalOptions>,\n cliOptions: Partial<GlobalOptions>\n): GlobalOptions {\n return {\n storage: cliOptions.storage ?? fileConfig.storage ?? './memory.jsonl',\n format: cliOptions.format ?? fileConfig.format ?? 'json',\n quiet: cliOptions.quiet ?? fileConfig.quiet ?? false,\n verbose: cliOptions.verbose ?? fileConfig.verbose ?? false,\n };\n}\n","/**\n * CLI Output Formatters\n *\n * Format data for JSON, table, or CSV output.\n *\n * @module cli/formatters\n */\n\nimport Table from 'cli-table3';\nimport chalk from 'chalk';\nimport type { Entity, Relation } from '../types/types.js';\n\nexport type OutputFormat = 'json' | 'table' | 'csv';\n\n/**\n * Get terminal width, with fallback for non-TTY.\n */\nfunction getTerminalWidth(): number {\n return process.stdout.columns || 80;\n}\n\n/**\n * Format entities for output.\n */\nexport function formatEntities(\n entities: Entity[],\n format: OutputFormat\n): string {\n if (entities.length === 0) {\n return format === 'json' ? '[]' : 'No entities found.';\n }\n\n switch (format) {\n case 'json':\n return JSON.stringify(entities, null, 2);\n\n case 'table': {\n const table = new Table({\n head: [chalk.cyan('Name'), chalk.cyan('Type'), chalk.cyan('Observations'), chalk.cyan('Tags')],\n colWidths: calculateColWidths(getTerminalWidth(), [0.25, 0.15, 0.4, 0.2]),\n wordWrap: true,\n });\n\n for (const entity of entities) {\n table.push([\n entity.name,\n entity.entityType,\n (entity.observations || []).slice(0, 3).join('; ') +\n (entity.observations && entity.observations.length > 3 ? '...' : ''),\n (entity.tags || []).join(', '),\n ]);\n }\n\n return table.toString();\n }\n\n case 'csv': {\n const header = 'name,entityType,observations,tags';\n const rows = entities.map(e => [\n escapeCSV(e.name),\n escapeCSV(e.entityType),\n escapeCSV((e.observations || []).join('; ')),\n escapeCSV((e.tags || []).join(', ')),\n ].join(','));\n return [header, ...rows].join('\\n');\n }\n }\n}\n\n/**\n * Format relations for output.\n */\nexport function formatRelations(\n relations: Relation[],\n format: OutputFormat\n): string {\n if (relations.length === 0) {\n return format === 'json' ? '[]' : 'No relations found.';\n }\n\n switch (format) {\n case 'json':\n return JSON.stringify(relations, null, 2);\n\n case 'table': {\n const table = new Table({\n head: [chalk.cyan('From'), chalk.cyan('Relation'), chalk.cyan('To')],\n colWidths: calculateColWidths(getTerminalWidth(), [0.35, 0.3, 0.35]),\n });\n\n for (const rel of relations) {\n table.push([rel.from, rel.relationType, rel.to]);\n }\n\n return table.toString();\n }\n\n case 'csv': {\n const header = 'from,relationType,to';\n const rows = relations.map(r => [\n escapeCSV(r.from),\n escapeCSV(r.relationType),\n escapeCSV(r.to),\n ].join(','));\n return [header, ...rows].join('\\n');\n }\n }\n}\n\n/**\n * Format search results for output.\n */\nexport function formatSearchResults(\n results: Array<{ entity: Entity; score?: number }>,\n format: OutputFormat\n): string {\n if (results.length === 0) {\n return format === 'json' ? '[]' : 'No results found.';\n }\n\n switch (format) {\n case 'json':\n return JSON.stringify(results, null, 2);\n\n case 'table': {\n const table = new Table({\n head: [chalk.cyan('Name'), chalk.cyan('Type'), chalk.cyan('Score'), chalk.cyan('Observations')],\n colWidths: calculateColWidths(getTerminalWidth(), [0.25, 0.15, 0.1, 0.5]),\n wordWrap: true,\n });\n\n for (const result of results) {\n table.push([\n result.entity.name,\n result.entity.entityType,\n result.score !== undefined ? result.score.toFixed(3) : '-',\n (result.entity.observations || []).slice(0, 2).join('; ') +\n (result.entity.observations && result.entity.observations.length > 2 ? '...' : ''),\n ]);\n }\n\n return table.toString();\n }\n\n case 'csv': {\n const header = 'name,entityType,score,observations';\n const rows = results.map(r => [\n escapeCSV(r.entity.name),\n escapeCSV(r.entity.entityType),\n r.score !== undefined ? r.score.toFixed(3) : '',\n escapeCSV((r.entity.observations || []).join('; ')),\n ].join(','));\n return [header, ...rows].join('\\n');\n }\n }\n}\n\n/**\n * Format a single entity for detailed output.\n */\nexport function formatEntityDetail(\n entity: Entity | null,\n format: OutputFormat\n): string {\n if (!entity) {\n return format === 'json' ? 'null' : 'Entity not found.';\n }\n\n switch (format) {\n case 'json':\n return JSON.stringify(entity, null, 2);\n\n case 'table': {\n const lines = [\n `${chalk.bold('Name:')} ${entity.name}`,\n `${chalk.bold('Type:')} ${entity.entityType}`,\n `${chalk.bold('Importance:')} ${entity.importance ?? 'N/A'}`,\n `${chalk.bold('Tags:')} ${(entity.tags || []).join(', ') || 'None'}`,\n `${chalk.bold('Parent:')} ${entity.parentId || 'None'}`,\n `${chalk.bold('Created:')} ${entity.createdAt || 'N/A'}`,\n `${chalk.bold('Modified:')} ${entity.lastModified || 'N/A'}`,\n '',\n chalk.bold('Observations:'),\n ...(entity.observations || []).map((o, i) => ` ${i + 1}. ${o}`),\n ];\n return lines.join('\\n');\n }\n\n case 'csv': {\n const header = 'field,value';\n const rows = [\n `name,${escapeCSV(entity.name)}`,\n `entityType,${escapeCSV(entity.entityType)}`,\n `importance,${entity.importance ?? ''}`,\n `tags,${escapeCSV((entity.tags || []).join('; '))}`,\n `parentId,${escapeCSV(entity.parentId || '')}`,\n `observations,${escapeCSV((entity.observations || []).join('; '))}`,\n ];\n return [header, ...rows].join('\\n');\n }\n }\n}\n\n/**\n * Format a success message.\n */\nexport function formatSuccess(message: string): string {\n return chalk.green('✓') + ' ' + message;\n}\n\n/**\n * Format an error message.\n */\nexport function formatError(message: string): string {\n return chalk.red('✗') + ' ' + message;\n}\n\nfunction calculateColWidths(totalWidth: number, ratios: number[]): number[] {\n const padding = 4; // Account for table borders\n const available = totalWidth - padding;\n return ratios.map(r => Math.max(10, Math.floor(available * r)));\n}\n\nfunction escapeCSV(value: string): string {\n if (value.includes(',') || value.includes('\"') || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n"],"mappings":"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","names":["min","max","required","path","workerpool","workerpool","resolve","fs","Mutex","pattern","path","MIN_IMPORTANCE","MAX_IMPORTANCE","entityExists","path","resolve","fs","path","workerpool","fileURLToPath","dirname","join","maxLength","maxLength","fs","DEFAULT_OPTIONS","resolve","DEFAULT_OPTIONS","fs","fs","dirname","join","hasMatchingTag","max","min","required","pattern","pattern","EventEmitter","now","newName","mergedMemory","EventEmitter","path","chalk","dirname","validateConfig","mergeConfig","program","mergeConfig","logger","entity","fs","startInteractiveMode","readFileSync","fileURLToPath","dirname","join","__filename","__dirname"]}