UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

848 lines 36.9 kB
/** * Memory Entity Manager * * Core manager for the memory-centric architecture that handles storage, retrieval, * relationship management, and intelligence operations for memory entities. */ import * as fs from 'fs/promises'; import * as path from 'path'; import crypto from 'crypto'; import { MemoryEntitySchema, } from '../types/memory-entities.js'; import { McpAdrError } from '../types/index.js'; import { loadConfig } from './config.js'; import { EnhancedLogger } from './enhanced-logging.js'; export class MemoryEntityManager { fsOverride; memoryDir; entitiesFile; relationshipsFile; intelligenceFile; evolutionLogFile; config; logger; isTestMode; // In-memory caches for performance entitiesCache = new Map(); relationshipsCache = new Map(); intelligence = null; lastSnapshotTime = 0; constructor(config, testMode = false, fsOverride) { this.fsOverride = fsOverride; this.isTestMode = testMode; const projectConfig = loadConfig(); this.logger = new EnhancedLogger(); this.memoryDir = path.join(projectConfig.projectPath, '.mcp-adr-memory'); this.entitiesFile = path.join(this.memoryDir, 'entities.json'); this.relationshipsFile = path.join(this.memoryDir, 'relationships.json'); this.intelligenceFile = path.join(this.memoryDir, 'intelligence.json'); this.evolutionLogFile = path.join(this.memoryDir, 'evolution-log.json'); this.config = { storageType: 'file', snapshotFrequency: testMode ? 0 : 30, // No automatic persistence in test mode compressionEnabled: true, backupRetention: 30, // 30 days syncEnabled: !testMode, // Disable sync in test mode conflictResolution: 'merge', ...config, }; } /** * Initialize the memory system */ async initialize() { try { await this.ensureMemoryDirectory(); await this.loadFromPersistence(); await this.initializeIntelligence(); this.logger.info('Memory Entity Manager initialized successfully', 'MemoryEntityManager', { entityCount: this.entitiesCache.size, relationshipCount: this.relationshipsCache.size, }); } catch (error) { throw new McpAdrError(`Failed to initialize memory system: ${error instanceof Error ? error.message : String(error)}`, 'MEMORY_INIT_ERROR'); } } /** * Create or update a memory entity */ async upsertEntity(entity) { try { const now = new Date().toISOString(); const id = entity.id || crypto.randomUUID(); // Get existing entity if updating const existing = entity.id ? this.entitiesCache.get(entity.id) : null; // Create full entity with defaults const fullEntity = { id, created: existing?.created || now, lastModified: now, version: (existing?.version || 0) + 1, confidence: entity.confidence || 0.85, relevance: entity.relevance || 0.7, tags: entity.tags || [], relationships: entity.relationships || existing?.relationships || [], context: { projectPhase: entity.context?.projectPhase, businessDomain: entity.context?.businessDomain, technicalStack: entity.context?.technicalStack || [], environmentalFactors: entity.context?.environmentalFactors || [], stakeholders: entity.context?.stakeholders || [], }, accessPattern: { lastAccessed: now, accessCount: (existing?.accessPattern?.accessCount || 0) + 1, accessContext: entity.accessPattern?.accessContext || [], }, evolution: { origin: entity.evolution?.origin || (existing ? existing.evolution.origin : 'created'), transformations: [ ...(existing?.evolution?.transformations || []), { timestamp: now, type: existing ? 'updated' : 'created', description: existing ? `Updated ${entity.type} entity` : `Created ${entity.type} entity`, agent: 'MemoryEntityManager', }, ], }, validation: { isVerified: entity.validation?.isVerified || false, verificationMethod: entity.validation?.verificationMethod, verificationTimestamp: entity.validation?.verificationTimestamp, conflictResolution: entity.validation?.conflictResolution, }, ...entity, }; // Validate the entity try { MemoryEntitySchema.parse(fullEntity); } catch (validationError) { throw new McpAdrError(`Entity validation failed: ${validationError instanceof Error ? validationError.message : String(validationError)}`, 'ENTITY_VALIDATION_ERROR'); } // Store in cache this.entitiesCache.set(id, fullEntity); // Log evolution event await this.logEvolutionEvent({ id: crypto.randomUUID(), entityId: id, timestamp: now, eventType: existing ? 'modified' : 'created', agent: 'MemoryEntityManager', changes: existing ? this.calculateChanges(existing, fullEntity) : {}, }); // Update intelligence await this.updateIntelligence('entity_updated', { entityId: id, entityType: fullEntity.type, }); // Persist if needed await this.maybePersist(); this.logger.debug(`${existing ? 'Updated' : 'Created'} entity`, 'MemoryEntityManager', { entityId: id, type: fullEntity.type, title: fullEntity.title.substring(0, 50), }); return fullEntity; } catch (error) { throw new McpAdrError(`Failed to upsert entity: ${error instanceof Error ? error.message : String(error)}`, 'ENTITY_UPSERT_ERROR'); } } /** * Get an entity by ID */ async getEntity(id) { const entity = this.entitiesCache.get(id); if (entity) { // Update access pattern entity.accessPattern.lastAccessed = new Date().toISOString(); entity.accessPattern.accessCount += 1; await this.logEvolutionEvent({ id: crypto.randomUUID(), entityId: id, timestamp: new Date().toISOString(), eventType: 'accessed', agent: 'MemoryEntityManager', }); } return entity || null; } /** * Delete an entity */ async deleteEntity(id) { const entity = this.entitiesCache.get(id); if (!entity) return false; // Remove entity this.entitiesCache.delete(id); // Remove all relationships involving this entity const relationshipsToRemove = Array.from(this.relationshipsCache.values()).filter(rel => rel.sourceId === id || rel.targetId === id); relationshipsToRemove.forEach(rel => this.relationshipsCache.delete(rel.id)); // Log evolution event await this.logEvolutionEvent({ id: crypto.randomUUID(), entityId: id, timestamp: new Date().toISOString(), eventType: 'deprecated', agent: 'MemoryEntityManager', context: 'Entity deleted by user request', }); await this.maybePersist(); return true; } /** * Create or update a relationship between entities */ async upsertRelationship(relationship) { try { const now = new Date().toISOString(); const id = relationship.id || crypto.randomUUID(); // Validate that both entities exist const sourceEntity = this.entitiesCache.get(relationship.sourceId); const targetEntity = this.entitiesCache.get(relationship.targetId); if (!sourceEntity || !targetEntity) { throw new McpAdrError('Source or target entity not found', 'RELATIONSHIP_INVALID_ENTITIES'); } const fullRelationship = { id, sourceId: relationship.sourceId, targetId: relationship.targetId, type: relationship.type, strength: relationship.strength || 0.7, context: relationship.context || '', evidence: relationship.evidence || [], created: relationship.created || now, lastValidated: relationship.lastValidated || now, confidence: relationship.confidence || 0.85, }; // Store in cache this.relationshipsCache.set(id, fullRelationship); // Update entity relationships for both source and target this.updateEntityRelationships(sourceEntity, fullRelationship); this.updateEntityRelationships(targetEntity, fullRelationship); // Log evolution events for both entities await Promise.all([ this.logEvolutionEvent({ id: crypto.randomUUID(), entityId: relationship.sourceId, timestamp: now, eventType: 'related', agent: 'MemoryEntityManager', context: `Relationship ${relationship.type} to ${targetEntity.title}`, }), this.logEvolutionEvent({ id: crypto.randomUUID(), entityId: relationship.targetId, timestamp: now, eventType: 'related', agent: 'MemoryEntityManager', context: `Relationship ${relationship.type} from ${sourceEntity.title}`, }), ]); await this.maybePersist(); return fullRelationship; } catch (error) { throw new McpAdrError(`Failed to create relationship: ${error instanceof Error ? error.message : String(error)}`, 'RELATIONSHIP_CREATE_ERROR'); } } /** * Query entities with advanced filtering */ async queryEntities(query) { const startTime = Date.now(); try { let entities = Array.from(this.entitiesCache.values()); const relationships = Array.from(this.relationshipsCache.values()); // Apply filters if (query.entityTypes?.length) { entities = entities.filter(e => query.entityTypes.includes(e.type)); } if (query.tags?.length) { entities = entities.filter(e => query.tags.some(tag => e.tags.includes(tag))); } if (query.textQuery) { const searchTerm = query.textQuery.toLowerCase(); entities = entities.filter(e => e.title.toLowerCase().includes(searchTerm) || e.description.toLowerCase().includes(searchTerm) || e.tags.some(tag => tag.toLowerCase().includes(searchTerm))); } if (query.confidenceThreshold !== undefined) { entities = entities.filter(e => e.confidence >= query.confidenceThreshold); } if (query.relevanceThreshold !== undefined) { entities = entities.filter(e => e.relevance >= query.relevanceThreshold); } if (query.timeRange) { const fromTime = new Date(query.timeRange.from).getTime(); const toTime = new Date(query.timeRange.to).getTime(); entities = entities.filter(e => { const entityTime = new Date(e.lastModified).getTime(); return entityTime >= fromTime && entityTime <= toTime; }); } // Apply context filters if (query.contextFilters) { if (query.contextFilters.projectPhase) { entities = entities.filter(e => e.context.projectPhase === query.contextFilters.projectPhase); } if (query.contextFilters.businessDomain) { entities = entities.filter(e => e.context.businessDomain === query.contextFilters.businessDomain); } if (query.contextFilters.technicalStack?.length) { entities = entities.filter(e => query.contextFilters.technicalStack.some(tech => e.context.technicalStack.includes(tech))); } } // Sort entities const sortBy = query.sortBy || 'relevance'; entities.sort((a, b) => { switch (sortBy) { case 'confidence': return b.confidence - a.confidence; case 'lastModified': return new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime(); case 'created': return new Date(b.created).getTime() - new Date(a.created).getTime(); case 'accessCount': return b.accessPattern.accessCount - a.accessPattern.accessCount; case 'relevance': default: return b.relevance - a.relevance; } }); const totalCount = entities.length; // Generate aggregations on ALL matching entities before applying limit const aggregations = { byType: {}, byTag: {}, byConfidence: {}, }; entities.forEach(entity => { // By type aggregations.byType[entity.type] = (aggregations.byType[entity.type] || 0) + 1; // By tags entity.tags.forEach(tag => { aggregations.byTag[tag] = (aggregations.byTag[tag] || 0) + 1; }); // By confidence range const confidenceRange = Math.floor(entity.confidence * 10) / 10; aggregations.byConfidence[`${confidenceRange}-${confidenceRange + 0.1}`] = (aggregations.byConfidence[`${confidenceRange}-${confidenceRange + 0.1}`] || 0) + 1; }); // Apply limit AFTER generating aggregations if (query.limit && query.limit > 0) { entities = entities.slice(0, query.limit); } // Include related entities if requested let resultRelationships = relationships; if (query.includeRelated) { const entityIds = new Set(entities.map(e => e.id)); resultRelationships = relationships.filter(r => entityIds.has(r.sourceId) || entityIds.has(r.targetId)); } const queryTime = Date.now() - startTime; this.logger.debug('Entity query executed', 'MemoryEntityManager', { queryTime, totalCount, returnedCount: entities.length, filters: Object.keys(query).filter(key => query[key] !== undefined), }); return { entities, relationships: resultRelationships, totalCount, queryTime, aggregations, }; } catch (error) { throw new McpAdrError(`Failed to query entities: ${error instanceof Error ? error.message : String(error)}`, 'QUERY_ERROR'); } } /** * Find related entities through relationships */ async findRelatedEntities(entityId, maxDepth = 2, relationshipTypes) { const visited = new Set(); const paths = []; const relatedEntities = new Map(); const traverse = (currentId, currentPath, currentRelationships, depth) => { if (depth > maxDepth || visited.has(currentId)) return; visited.add(currentId); const currentEntity = this.entitiesCache.get(currentId); if (!currentEntity) return; if (depth > 0) { relatedEntities.set(currentId, currentEntity); paths.push({ path: [...currentPath, currentId], relationships: [...currentRelationships], depth, }); } // Find both outgoing and incoming relationships const allRels = Array.from(this.relationshipsCache.values()).filter(rel => { const isOutgoing = rel.sourceId === currentId; const isIncoming = rel.targetId === currentId; if (!isOutgoing && !isIncoming) return false; if (relationshipTypes?.length && !relationshipTypes.includes(rel.type)) return false; return true; }); for (const rel of allRels) { const targetId = rel.sourceId === currentId ? rel.targetId : rel.sourceId; if (!visited.has(targetId)) { traverse(targetId, [...currentPath, currentId], [...currentRelationships, rel], depth + 1); } } }; traverse(entityId, [], [], 0); return { entities: Array.from(relatedEntities.values()), relationshipPaths: paths, }; } /** * Get memory intelligence data */ async getIntelligence() { if (!this.intelligence) { await this.initializeIntelligence(); } return this.intelligence; } /** * Create a memory snapshot */ async createSnapshot() { const now = new Date().toISOString(); const intelligence = await this.getIntelligence(); const entities = Array.from(this.entitiesCache.values()); const relationships = Array.from(this.relationshipsCache.values()); const totalConfidence = entities.reduce((sum, e) => sum + e.confidence, 0); const averageConfidence = entities.length > 0 ? totalConfidence / entities.length : 0; return { id: crypto.randomUUID(), timestamp: now, version: '1.0.0', entities, relationships, intelligence, metadata: { totalEntities: entities.length, totalRelationships: relationships.length, averageConfidence, lastOptimization: now, }, }; } // Private helper methods async ensureMemoryDirectory() { try { const fsInstance = this.fsOverride || fs; await fsInstance.access(this.memoryDir); } catch (error) { // Directory doesn't exist, create it const fsInstance = this.fsOverride || fs; await fsInstance.mkdir(this.memoryDir, { recursive: true }); } } async loadFromPersistence() { // Skip loading from persistence in test mode to avoid conflicts if (this.isTestMode) { return; } try { // Load entities try { const fsInstance = this.fsOverride || fs; const entitiesData = await fsInstance.readFile(this.entitiesFile, 'utf-8'); const entities = JSON.parse(entitiesData); entities.forEach(entity => { this.entitiesCache.set(entity.id, entity); }); } catch { // No entities file exists yet } // Load relationships try { const fsInstance = this.fsOverride || fs; const relationshipsData = await fsInstance.readFile(this.relationshipsFile, 'utf-8'); const relationships = JSON.parse(relationshipsData); relationships.forEach(relationship => { this.relationshipsCache.set(relationship.id, relationship); }); } catch { // No relationships file exists yet } // Load intelligence try { const fsInstance = this.fsOverride || fs; const intelligenceData = await fsInstance.readFile(this.intelligenceFile, 'utf-8'); this.intelligence = JSON.parse(intelligenceData); } catch { // Will initialize default intelligence } } catch (error) { this.logger.warn('Failed to load some memory data from persistence', 'MemoryEntityManager', { error, }); } } async maybePersist() { // In test mode, persist when there are enough changes for testing purposes if (this.isTestMode) { // Check if we have enough entities to test persistence (2+ entities) const now = Date.now(); const timeSinceLastSnapshot = now - this.lastSnapshotTime; // In test mode, persist after 30 minutes of simulated time OR when we have 2+ entities if (this.entitiesCache.size >= 2 && timeSinceLastSnapshot >= 30 * 60 * 1000) { await this.persistToStorage(); this.lastSnapshotTime = now; } return; } // Skip auto-persistence if disabled if (this.config.snapshotFrequency === 0) { return; } const now = Date.now(); const timeSinceLastSnapshot = now - this.lastSnapshotTime; const snapshotIntervalMs = this.config.snapshotFrequency * 60 * 1000; if (timeSinceLastSnapshot >= snapshotIntervalMs) { await this.persistToStorage(); this.lastSnapshotTime = now; } } async persistToStorage() { try { const fsInstance = this.fsOverride || fs; await this.ensureMemoryDirectory(); // Save entities const entities = Array.from(this.entitiesCache.values()); await fsInstance.writeFile(this.entitiesFile, JSON.stringify(entities, null, 2)); // Save relationships const relationships = Array.from(this.relationshipsCache.values()); await fsInstance.writeFile(this.relationshipsFile, JSON.stringify(relationships, null, 2)); // Save intelligence if (this.intelligence) { await fsInstance.writeFile(this.intelligenceFile, JSON.stringify(this.intelligence, null, 2)); } this.logger.debug('Memory data persisted to storage', 'MemoryEntityManager', { entityCount: entities.length, relationshipCount: relationships.length, }); } catch (error) { this.logger.error('Failed to persist memory data', 'MemoryEntityManager', error instanceof Error ? error : undefined, { error }); } } async initializeIntelligence() { if (!this.intelligence) { this.intelligence = { contextAwareness: { currentContext: {}, contextHistory: [], }, patternRecognition: { discoveredPatterns: [], patternConfidence: {}, emergentBehaviors: [], }, relationshipInference: { suggestedRelationships: [], weakConnections: [], conflictDetection: [], }, adaptiveRecommendations: { nextActions: [], knowledgeGaps: [], optimizationOpportunities: [], }, }; // Initialize with some default knowledge gaps and recommendations await this.updateRecommendations(); } } async updateIntelligence(eventType, context) { if (!this.intelligence) await this.initializeIntelligence(); // Update context awareness this.intelligence.contextAwareness.currentContext = { ...this.intelligence.contextAwareness.currentContext, lastEvent: eventType, lastEventTime: new Date().toISOString(), ...context, }; // Basic pattern recognition - could be enhanced with ML if (eventType === 'entity_updated') { const pattern = `${context.entityType}_update`; this.intelligence.patternRecognition.patternConfidence[pattern] = (this.intelligence.patternRecognition.patternConfidence[pattern] || 0) + 1; } // Update recommendations based on current state await this.updateRecommendations(); } async updateRecommendations() { if (!this.intelligence) return; const entities = Array.from(this.entitiesCache.values()); const relationships = Array.from(this.relationshipsCache.values()); // Identify knowledge gaps (entities with low confidence OR system lacking entities) const knowledgeGaps = []; if (entities.length === 0) { knowledgeGaps.push('No architectural decisions documented in memory system'); } else { // Add gaps for low confidence entities const lowConfidenceGaps = entities .filter(e => e.confidence < 0.5) .map(e => `Low confidence in ${e.type}: ${e.title}`) .slice(0, 3); knowledgeGaps.push(...lowConfidenceGaps); // Add gaps for missing entity types if (entities.length === 1) { knowledgeGaps.push('Limited diversity in architectural entities - consider documenting more types'); } // Add gaps for entities without tags const untaggedEntities = entities.filter(e => !e.tags || e.tags.length === 0); if (untaggedEntities.length > 0) { knowledgeGaps.push(`${untaggedEntities.length} entities lack proper categorization tags`); } } // Suggest relationships for entities without many connections const suggestedRelationships = []; for (const entity of entities) { const entityRelationships = relationships.filter(r => r.sourceId === entity.id || r.targetId === entity.id); if (entityRelationships.length < 2) { // Find potential relationships based on tags or type similarity const similar = entities.filter(other => other.id !== entity.id && (other.type === entity.type || other.tags.some(tag => entity.tags.includes(tag)))); for (const similarEntity of similar.slice(0, 2)) { suggestedRelationships.push({ sourceId: entity.id, targetId: similarEntity.id, type: 'relates_to', confidence: 0.6, reasoning: `Similar ${entity.type} entities with overlapping tags`, }); } } } this.intelligence.adaptiveRecommendations = { nextActions: [ { action: 'Validate low-confidence entities', priority: 1, reasoning: 'Several entities have confidence below 50%', requiredEntities: entities .filter(e => e.confidence < 0.5) .map(e => e.id) .slice(0, 3), }, ], knowledgeGaps, optimizationOpportunities: [ 'Consider consolidating similar entities', 'Review and strengthen weak relationships', 'Add more context to entities with generic descriptions', ], }; this.intelligence.relationshipInference.suggestedRelationships = suggestedRelationships.slice(0, 10); } updateEntityRelationships(entity, relationship) { // Determine the target entity ID based on which entity this is const isSourceEntity = entity.id === relationship.sourceId; const targetEntityId = isSourceEntity ? relationship.targetId : relationship.sourceId; // Add relationship to entity's relationship list if not already present const existingRelIndex = entity.relationships.findIndex(r => r.targetId === targetEntityId && r.type === relationship.type); const entityRel = { targetId: targetEntityId, type: relationship.type, strength: relationship.strength, context: relationship.context, created: relationship.created, }; if (existingRelIndex >= 0) { entity.relationships[existingRelIndex] = entityRel; } else { entity.relationships.push(entityRel); } } calculateChanges(oldEntity, newEntity) { const changes = {}; // Convert to any to avoid index signature access issues const oldAny = oldEntity; const newAny = newEntity; if (oldAny['title'] !== newAny['title']) changes['title'] = { old: oldAny['title'], new: newAny['title'] }; if (oldAny['description'] !== newAny['description']) changes['description'] = { old: oldAny['description'], new: newAny['description'] }; if (oldEntity['confidence'] !== newEntity['confidence']) changes['confidence'] = { old: oldEntity['confidence'], new: newEntity['confidence'] }; if (oldEntity['relevance'] !== newEntity['relevance']) changes['relevance'] = { old: oldEntity['relevance'], new: newEntity['relevance'] }; return changes; } async logEvolutionEvent(event) { try { let existingLog = []; try { const fsInstance = this.fsOverride || fs; const logData = await fsInstance.readFile(this.evolutionLogFile, 'utf-8'); existingLog = JSON.parse(logData); } catch { // Log file doesn't exist yet } existingLog.push(event); // Keep only the last 1000 events to manage file size if (existingLog.length > 1000) { existingLog = existingLog.slice(-1000); } const fsInstance = this.fsOverride || fs; await fsInstance.writeFile(this.evolutionLogFile, JSON.stringify(existingLog, null, 2)); } catch (error) { this.logger.warn('Failed to log evolution event', 'MemoryEntityManager', { event, error }); } } /** * Clear all cached data (useful for testing) */ clearCache() { this.entitiesCache.clear(); this.relationshipsCache.clear(); this.intelligence = null; this.lastSnapshotTime = 0; } /** * Force persistence (useful for testing) */ async forcePersist() { return this.persistToStorage(); } /** * Create cross-tool relationships using the MemoryRelationshipMapper */ async createCrossToolRelationships() { const { MemoryRelationshipMapper } = await import('./memory-relationship-mapper.js'); const mapper = new MemoryRelationshipMapper(this); this.logger.info('Starting cross-tool relationship creation', 'MemoryEntityManager'); const result = await mapper.createCrossToolRelationships(); const autoCreatedCount = result.suggestedRelationships.filter(rel => rel.confidence >= 0.85).length; this.logger.info('Cross-tool relationships created', 'MemoryEntityManager', { totalSuggested: result.suggestedRelationships.length, autoCreated: autoCreatedCount, conflicts: result.conflicts.length, }); return { ...result, autoCreatedCount, }; } /** * Infer and suggest new relationships based on existing entity patterns */ async inferRelationships(entityTypes, minConfidence = 0.7) { const { MemoryRelationshipMapper } = await import('./memory-relationship-mapper.js'); const mapper = new MemoryRelationshipMapper(this, { confidenceThreshold: minConfidence }); const result = await mapper.createCrossToolRelationships(); // Filter by entity types if specified let filteredSuggestions = result.suggestedRelationships; if (entityTypes && entityTypes.length > 0) { const entityCache = this.entitiesCache; filteredSuggestions = result.suggestedRelationships.filter(rel => { const sourceEntity = entityCache.get(rel.sourceId); const targetEntity = entityCache.get(rel.targetId); return (sourceEntity && targetEntity && (entityTypes.includes(sourceEntity.type) || entityTypes.includes(targetEntity.type))); }); } return { suggestedRelationships: filteredSuggestions.filter(rel => rel.confidence >= minConfidence), }; } /** * Validate existing relationships and detect conflicts */ async validateRelationships() { const validRelationships = []; const invalidRelationships = []; const suggestedActions = []; for (const [relationshipId, relationship] of this.relationshipsCache) { // Check if both entities still exist const sourceExists = this.entitiesCache.has(relationship.sourceId); const targetExists = this.entitiesCache.has(relationship.targetId); if (!sourceExists || !targetExists) { invalidRelationships.push({ relationshipId, reason: `${!sourceExists ? 'Source' : 'Target'} entity no longer exists`, severity: 'high', }); suggestedActions.push({ action: 'remove', relationshipId, reason: 'Orphaned relationship - entity deleted', }); continue; } // Check relationship age and confidence const createdDate = new Date(relationship.created); const now = new Date(); const ageInDays = (now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24); if (ageInDays > 90 && relationship.confidence < 0.6) { invalidRelationships.push({ relationshipId, reason: 'Old relationship with low confidence', severity: 'medium', }); suggestedActions.push({ action: 'investigate', relationshipId, reason: 'Requires validation - old and low confidence', }); } else if (relationship.strength < 0.3) { invalidRelationships.push({ relationshipId, reason: 'Very low relationship strength', severity: 'low', }); suggestedActions.push({ action: 'update', relationshipId, reason: 'Consider strengthening or removing weak relationship', }); } else { validRelationships.push(relationshipId); } } this.logger.info('Relationship validation completed', 'MemoryEntityManager', { total: this.relationshipsCache.size, valid: validRelationships.length, invalid: invalidRelationships.length, suggestedActions: suggestedActions.length, }); return { validRelationships, invalidRelationships, suggestedActions, }; } } //# sourceMappingURL=memory-entity-manager.js.map