mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
848 lines • 36.9 kB
JavaScript
/**
* 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