UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

277 lines 11.1 kB
/** * Memory-Centric Health Scoring System * * Evaluates system health based on memory quality, retrieval performance, * and entity relationship coherence rather than traditional task completion metrics. * * This replaces the deprecated ProjectHealthScoring with a memory-aware approach * that aligns with the memory-centric architecture. */ export class MemoryHealthScoring { weights; constructor(weights) { this.weights = { memoryQuality: 0.25, retrievalPerformance: 0.25, entityCoherence: 0.2, contextUtilization: 0.15, decisionAlignment: 0.15, ...weights, }; } /** * Calculate overall memory health score */ async calculateMemoryHealth(memories, retrievalMetrics, entityGraph) { const now = new Date().toISOString(); const breakdown = { memoryQuality: this.assessMemoryQuality(memories, now), retrievalPerformance: this.assessRetrievalPerformance(retrievalMetrics, now), entityCoherence: this.assessEntityCoherence(entityGraph, now), contextUtilization: this.assessContextUtilization(memories, now), decisionAlignment: this.assessDecisionAlignment(memories, entityGraph, now), }; const scores = { memoryQuality: this.calculateMemoryQualityScore(breakdown.memoryQuality), retrievalPerformance: this.calculateRetrievalScore(breakdown.retrievalPerformance), entityCoherence: this.calculateCoherenceScore(breakdown.entityCoherence), contextUtilization: this.calculateContextScore(breakdown.contextUtilization), decisionAlignment: this.calculateAlignmentScore(breakdown.decisionAlignment), }; const overall = Math.round(scores.memoryQuality * this.weights.memoryQuality + scores.retrievalPerformance * this.weights.retrievalPerformance + scores.entityCoherence * this.weights.entityCoherence + scores.contextUtilization * this.weights.contextUtilization + scores.decisionAlignment * this.weights.decisionAlignment); return { overall, ...scores, confidence: this.calculateConfidence(breakdown), lastUpdated: now, breakdown, }; } /** * Assess memory quality based on relevance and freshness */ assessMemoryQuality(memories, timestamp) { const now = new Date(); const staleThreshold = 7 * 24 * 60 * 60 * 1000; // 7 days let relevantCount = 0; let staleCount = 0; let totalRelevance = 0; memories.forEach(memory => { // Check relevance (simplified - would use embeddings in production) const relevance = memory.metadata?.relevanceScore || 0.5; totalRelevance += relevance; if (relevance > 0.7) relevantCount++; // Check staleness if (memory.timestamp) { const age = now.getTime() - new Date(memory.timestamp).getTime(); if (age > staleThreshold) staleCount++; } }); return { totalMemories: memories.length, relevantMemories: relevantCount, staleMemories: staleCount, averageRelevanceScore: memories.length > 0 ? totalRelevance / memories.length : 0, lastUpdated: timestamp, }; } /** * Assess retrieval performance metrics */ assessRetrievalPerformance(metrics, timestamp) { return { totalRetrievals: metrics?.totalRetrievals || 0, successfulRetrievals: metrics?.successfulRetrievals || 0, averageRetrievalTime: metrics?.averageRetrievalTime || 0, precisionScore: metrics?.precisionScore || 0.5, recallScore: metrics?.recallScore || 0.5, lastUpdated: timestamp, }; } /** * Assess entity relationship coherence */ assessEntityCoherence(entityGraph, timestamp) { const entities = entityGraph?.entities || []; const relationships = entityGraph?.relationships || []; // Count connected vs orphaned entities const connectedEntityIds = new Set(); relationships.forEach((rel) => { connectedEntityIds.add(rel.sourceId); connectedEntityIds.add(rel.targetId); }); const orphanedCount = entities.filter((e) => !connectedEntityIds.has(e.id)).length; // Calculate average relationship strength const avgStrength = relationships.length > 0 ? relationships.reduce((sum, rel) => sum + (rel.strength || 0.5), 0) / relationships.length : 0; return { totalEntities: entities.length, connectedEntities: connectedEntityIds.size, orphanedEntities: orphanedCount, relationshipStrength: avgStrength, lastUpdated: timestamp, }; } /** * Assess context utilization */ assessContextUtilization(memories, timestamp) { let totalContextSize = 0; let totalRelevance = 0; let totalCompleteness = 0; memories.forEach(memory => { const context = memory.context || {}; totalContextSize += Object.keys(context).length; totalRelevance += context.relevanceScore || 0.5; totalCompleteness += context.completeness || 0.5; }); const count = memories.length || 1; return { averageContextSize: totalContextSize / count, contextRelevance: totalRelevance / count, contextCompleteness: totalCompleteness / count, lastUpdated: timestamp, }; } /** * Assess alignment with architectural decisions */ assessDecisionAlignment(memories, entityGraph, timestamp) { const decisions = entityGraph?.decisions || []; let alignedCount = 0; let conflictingCount = 0; decisions.forEach((decision) => { // Check if memories support this decision const supportingMemories = memories.filter(m => m.relatedDecisions?.includes(decision.id)); if (supportingMemories.length > 0) { alignedCount++; } else if (decision.conflicts?.length > 0) { conflictingCount++; } }); return { totalDecisions: decisions.length, alignedDecisions: alignedCount, conflictingDecisions: conflictingCount, alignmentScore: decisions.length > 0 ? alignedCount / decisions.length : 0, lastUpdated: timestamp, }; } /** * Calculate memory quality score */ calculateMemoryQualityScore(quality) { if (quality.totalMemories === 0) return 50; // Neutral score for no memories let score = 100; // Penalize stale memories const staleRatio = quality.staleMemories / quality.totalMemories; score -= staleRatio * 30; // Reward relevant memories const relevantRatio = quality.relevantMemories / quality.totalMemories; score = (score + relevantRatio * 100) / 2; // Factor in average relevance score = (score + quality.averageRelevanceScore * 100) / 2; return Math.round(Math.max(0, Math.min(100, score))); } /** * Calculate retrieval performance score */ calculateRetrievalScore(perf) { if (perf.totalRetrievals === 0) return 50; // Neutral score for no retrievals const successRate = perf.successfulRetrievals / perf.totalRetrievals; const f1Score = (2 * (perf.precisionScore * perf.recallScore)) / (perf.precisionScore + perf.recallScore || 1); // Penalize slow retrieval (over 100ms is considered slow) const speedScore = Math.max(0, 1 - perf.averageRetrievalTime / 100); return Math.round((successRate * 40 + f1Score * 40 + speedScore * 20) * 100); } /** * Calculate entity coherence score */ calculateCoherenceScore(coherence) { if (coherence.totalEntities === 0) return 100; // Perfect score for no entities const connectedRatio = coherence.connectedEntities / coherence.totalEntities; const orphanPenalty = (coherence.orphanedEntities / coherence.totalEntities) * 30; let score = connectedRatio * 100 - orphanPenalty; score = (score + coherence.relationshipStrength * 100) / 2; return Math.round(Math.max(0, Math.min(100, score))); } /** * Calculate context utilization score */ calculateContextScore(context) { // Ideal context size is between 5-15 items const sizeScore = context.averageContextSize < 5 ? context.averageContextSize / 5 : context.averageContextSize > 15 ? Math.max(0, 1 - (context.averageContextSize - 15) / 15) : 1; return Math.round((sizeScore * 30 + context.contextRelevance * 35 + context.contextCompleteness * 35) * 100); } /** * Calculate decision alignment score */ calculateAlignmentScore(alignment) { if (alignment.totalDecisions === 0) return 100; // Perfect score for no decisions let score = alignment.alignmentScore * 100; // Heavily penalize conflicting decisions const conflictPenalty = (alignment.conflictingDecisions / alignment.totalDecisions) * 50; score -= conflictPenalty; return Math.round(Math.max(0, Math.min(100, score))); } /** * Calculate confidence in the overall score */ calculateConfidence(breakdown) { const now = new Date(); const maxAgeMs = 60 * 60 * 1000; // 1 hour for memory systems let confidence = 100; // Check data freshness Object.values(breakdown).forEach(component => { if (component.lastUpdated) { const age = now.getTime() - new Date(component.lastUpdated).getTime(); if (age > maxAgeMs) { confidence -= 20; } } }); // Reduce confidence if we have very little data if (breakdown.memoryQuality.totalMemories < 10) confidence -= 20; if (breakdown.retrievalPerformance.totalRetrievals < 5) confidence -= 20; if (breakdown.entityCoherence.totalEntities < 5) confidence -= 20; return Math.max(0, confidence); } /** * Get emoji representation of score */ getScoreEmoji(score) { if (score >= 90) return '🟢'; if (score >= 75) return '🟡'; if (score >= 60) return '🟠'; return '🔴'; } } /** * Singleton instance for easy access */ export const memoryHealthScoring = new MemoryHealthScoring(); //# sourceMappingURL=memory-health-scoring.js.map