mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
481 lines • 23.6 kB
JavaScript
/**
* Memory Relationship Mapper
*
* Intelligent relationship mapping utility for creating connections between
* memory entities across different tools. Enables troubleshooting sessions
* to reference environment snapshots, deployment assessments to validate
* against ADRs, and builds comprehensive project knowledge graph.
*/
import { EnhancedLogger } from './enhanced-logging.js';
import { McpAdrError } from '../types/index.js';
export class MemoryRelationshipMapper {
memoryManager;
logger;
config;
constructor(memoryManager, config) {
this.memoryManager = memoryManager;
this.logger = new EnhancedLogger();
this.config = {
temporalThreshold: 24, // 24 hours
contextSimilarityThreshold: 0.7,
confidenceThreshold: 0.8,
enableInferenceLearning: true,
...config,
};
}
/**
* Create all cross-tool relationships across the knowledge graph
*/
async createCrossToolRelationships() {
this.logger.info('Starting cross-tool relationship mapping', 'MemoryRelationshipMapper');
const allResults = {
suggestedRelationships: [],
conflicts: [],
};
try {
// 1. Link troubleshooting sessions to environment snapshots
const troubleshootingEnvResult = await this.linkTroubleshootingToEnvironment();
allResults.suggestedRelationships.push(...troubleshootingEnvResult.suggestedRelationships);
allResults.conflicts.push(...troubleshootingEnvResult.conflicts);
// 2. Link deployment assessments to ADR compliance
const deploymentAdrResult = await this.linkDeploymentToADRs();
allResults.suggestedRelationships.push(...deploymentAdrResult.suggestedRelationships);
allResults.conflicts.push(...deploymentAdrResult.conflicts);
// 3. Link security patterns to architectural decisions
const securityArchResult = await this.linkSecurityToArchitecture();
allResults.suggestedRelationships.push(...securityArchResult.suggestedRelationships);
allResults.conflicts.push(...securityArchResult.conflicts);
// 4. Link failure patterns to troubleshooting sessions
const failureTroubleshootingResult = await this.linkFailuresToTroubleshooting();
allResults.suggestedRelationships.push(...failureTroubleshootingResult.suggestedRelationships);
allResults.conflicts.push(...failureTroubleshootingResult.conflicts);
// 5. Link environment snapshots to deployment assessments
const envDeploymentResult = await this.linkEnvironmentToDeployment();
allResults.suggestedRelationships.push(...envDeploymentResult.suggestedRelationships);
allResults.conflicts.push(...envDeploymentResult.conflicts);
// 6. Auto-create high-confidence relationships
await this.autoCreateHighConfidenceRelationships(allResults.suggestedRelationships);
this.logger.info('Cross-tool relationship mapping completed', 'MemoryRelationshipMapper', {
suggestedCount: allResults.suggestedRelationships.length,
conflictCount: allResults.conflicts.length,
autoCreatedCount: allResults.suggestedRelationships.filter(r => r.confidence >= this.config.confidenceThreshold).length,
});
return allResults;
}
catch (error) {
throw new McpAdrError(`Cross-tool relationship mapping failed: ${error instanceof Error ? error.message : String(error)}`, 'RELATIONSHIP_MAPPING_ERROR');
}
}
/**
* Link troubleshooting sessions to environment snapshots
*/
async linkTroubleshootingToEnvironment() {
const troubleshootingSessions = (await this.queryEntitiesByType('troubleshooting_session'));
const environmentSnapshots = (await this.queryEntitiesByType('environment_snapshot'));
const result = {
suggestedRelationships: [],
conflicts: [],
};
for (const session of troubleshootingSessions) {
const relatedEnvs = await this.findTemporallyRelatedEnvironments(session, environmentSnapshots);
for (const env of relatedEnvs) {
const confidence = this.calculateEnvironmentSessionConfidence(session, env);
if (confidence >= 0.6) {
result.suggestedRelationships.push({
sourceId: session.id,
targetId: env.id,
type: 'occurred_in',
confidence,
reasoning: `Troubleshooting session occurred during environment snapshot timeframe with matching context`,
evidence: [
`Temporal overlap: ${this.getTemporalOverlap(session.created, env.created)}`,
`Environment match: ${this.getEnvironmentMatch(session, env)}`,
`Context similarity: ${this.getContextSimilarity(session.context, env.context)}`,
],
});
}
}
}
return result;
}
/**
* Link deployment assessments to ADR compliance
*/
async linkDeploymentToADRs() {
const deploymentAssessments = (await this.queryEntitiesByType('deployment_assessment'));
const adrs = (await this.queryEntitiesByType('architectural_decision'));
this.logger.debug(`Found ${deploymentAssessments.length} deployments and ${adrs.length} ADRs`, 'MemoryRelationshipMapper');
const result = {
suggestedRelationships: [],
conflicts: [],
};
for (const deployment of deploymentAssessments) {
for (const adr of adrs) {
const compliance = this.calculateAdrComplianceScore(deployment, adr);
if (compliance.isCompliant) {
result.suggestedRelationships.push({
sourceId: deployment.id,
targetId: adr.id,
type: 'complies_with',
confidence: compliance.confidence,
reasoning: `Deployment assessment demonstrates compliance with architectural decision`,
evidence: compliance.evidence,
});
}
else if (compliance.hasConflict) {
result.conflicts.push({
description: `Deployment assessment conflicts with architectural decision`,
entityIds: [deployment.id, adr.id],
severity: compliance.conflictSeverity,
recommendation: compliance.recommendation,
});
}
}
}
return result;
}
/**
* Link security patterns to architectural decisions
*/
async linkSecurityToArchitecture() {
const securityPatterns = (await this.queryEntitiesByType('security_pattern'));
const adrs = (await this.queryEntitiesByType('architectural_decision'));
const result = {
suggestedRelationships: [],
conflicts: [],
};
for (const security of securityPatterns) {
for (const adr of adrs) {
if (this.hasSecurityArchitectureRelation(security, adr)) {
const confidence = this.calculateSecurityArchitectureConfidence(security, adr);
result.suggestedRelationships.push({
sourceId: security.id,
targetId: adr.id,
type: 'detects',
confidence,
reasoning: 'Security pattern provides protection for architectural decision',
evidence: [
`Security type: ${security.securityData?.contentType || 'unknown'}`,
`Risk level: ${security.securityData?.riskAssessment?.overallRisk || 'unknown'}`,
`Architecture domain: ${this.extractArchitectureDomain(adr)}`,
],
});
}
}
}
return result;
}
/**
* Link failure patterns to troubleshooting sessions
*/
async linkFailuresToTroubleshooting() {
const failurePatterns = (await this.queryEntitiesByType('failure_pattern'));
const troubleshootingSessions = (await this.queryEntitiesByType('troubleshooting_session'));
const result = {
suggestedRelationships: [],
conflicts: [],
};
for (const failure of failurePatterns) {
for (const session of troubleshootingSessions) {
if (this.hasFailureSessionMatch(failure, session)) {
const confidence = this.calculateFailureSessionConfidence(failure, session);
result.suggestedRelationships.push({
sourceId: failure.id,
targetId: session.id,
type: 'addresses',
confidence,
reasoning: `Failure pattern matches troubleshooting session failure signature`,
evidence: [
`Failure type match: ${failure.patternData?.failureType || 'unknown'} === ${session.sessionData?.failurePattern?.failureType || 'unknown'}`,
`Error signature similarity: ${this.getErrorSignatureSimilarity(failure, session)}`,
`Environment overlap: ${this.getEnvironmentOverlap(failure, session)}`,
],
});
}
}
}
return result;
}
/**
* Link environment snapshots to deployment assessments
*/
async linkEnvironmentToDeployment() {
const environmentSnapshots = (await this.queryEntitiesByType('environment_snapshot'));
const deploymentAssessments = (await this.queryEntitiesByType('deployment_assessment'));
const result = {
suggestedRelationships: [],
conflicts: [],
};
for (const env of environmentSnapshots) {
for (const deployment of deploymentAssessments) {
if (this.hasEnvironmentDeploymentRelation(env, deployment)) {
const confidence = this.calculateEnvironmentDeploymentConfidence(env, deployment);
result.suggestedRelationships.push({
sourceId: env.id,
targetId: deployment.id,
type: 'validates',
confidence,
reasoning: `Environment snapshot validates deployment assessment configuration`,
evidence: [
`Environment type: ${env.environmentData?.environmentType || 'unknown'}`,
`Deployment environment: ${deployment.assessmentData?.environment || 'unknown'}`,
`Configuration overlap: ${this.getConfigurationOverlap(env, deployment)}`,
],
});
}
}
}
return result;
}
/**
* Auto-create relationships with high confidence scores
*/
async autoCreateHighConfidenceRelationships(suggestedRelationships) {
const highConfidenceRelationships = suggestedRelationships.filter(rel => rel.confidence >= this.config.confidenceThreshold);
for (const suggestion of highConfidenceRelationships) {
try {
await this.memoryManager.upsertRelationship({
sourceId: suggestion.sourceId,
targetId: suggestion.targetId,
type: suggestion.type,
strength: suggestion.confidence,
confidence: suggestion.confidence,
context: suggestion.reasoning,
evidence: suggestion.evidence,
});
this.logger.debug('Auto-created high-confidence relationship', 'MemoryRelationshipMapper', {
sourceId: suggestion.sourceId,
targetId: suggestion.targetId,
type: suggestion.type,
confidence: suggestion.confidence,
});
}
catch (error) {
this.logger.warn('Failed to auto-create relationship', 'MemoryRelationshipMapper', {
error: error instanceof Error ? error.message : String(error),
sourceId: suggestion.sourceId,
targetId: suggestion.targetId,
});
}
}
}
// Helper methods for relationship inference
async queryEntitiesByType(type) {
const result = await this.memoryManager.queryEntities({
entityTypes: [type],
});
return result.entities;
}
async findTemporallyRelatedEnvironments(session, environments) {
const sessionTime = new Date(session.created).getTime();
const thresholdMs = this.config.temporalThreshold * 60 * 60 * 1000; // Convert hours to ms
return environments.filter(env => {
const envTime = new Date(env.created).getTime();
const timeDiff = Math.abs(sessionTime - envTime);
return timeDiff <= thresholdMs;
});
}
calculateEnvironmentSessionConfidence(session, env) {
let confidence = 0.5; // Base confidence
// Temporal proximity (0.0 - 0.3)
const temporalScore = this.getTemporalProximityScore(session.created, env.created);
confidence += temporalScore * 0.3;
// Environment type match (0.0 - 0.2)
const sessionEnv = session.sessionData?.environmentContext?.['environment'] || 'unknown';
if (sessionEnv === env.environmentData?.environmentType) {
confidence += 0.2;
}
// Context similarity (0.0 - 0.3)
const contextScore = this.getContextSimilarity(session.context, env.context);
confidence += contextScore * 0.3;
// Technical stack overlap (0.0 - 0.2)
const stackOverlap = this.getTechnicalStackOverlap(session.context.technicalStack, env.context.technicalStack);
confidence += stackOverlap * 0.2;
return Math.min(1.0, confidence);
}
calculateAdrComplianceScore(deployment, adr) {
const evidence = [];
let complianceScore = 0;
let hasConflict = false;
let conflictSeverity = 'low';
// Check if deployment follows ADR decisions
const deploymentTech = deployment.context.technicalStack;
// Extract technical stack for comparison
// Technology alignment
const techAlignment = this.getTechnicalStackOverlap(deploymentTech, adr.context.technicalStack);
complianceScore += techAlignment * 0.4;
evidence.push(`Technology alignment: ${(techAlignment * 100).toFixed(1)}%`);
// Decision implementation status
if (adr.decisionData?.implementationStatus === 'completed') {
complianceScore += 0.3;
evidence.push('ADR implementation is complete');
}
else if (adr.decisionData?.implementationStatus === 'in_progress') {
complianceScore += 0.2;
evidence.push('ADR implementation in progress');
}
// Check for conflicts
this.logger.debug(`Checking conflict: readiness=${deployment.assessmentData?.readinessScore || 'unknown'}, status=${adr.decisionData?.status || 'unknown'}`, 'MemoryRelationshipMapper');
if (deployment.assessmentData?.readinessScore != null &&
deployment.assessmentData.readinessScore < 0.7 &&
adr.decisionData?.status === 'accepted') {
hasConflict = true;
conflictSeverity = deployment.assessmentData.readinessScore < 0.5 ? 'high' : 'medium';
evidence.push('Low deployment readiness conflicts with accepted ADR');
this.logger.debug(`Conflict detected: severity=${conflictSeverity}`, 'MemoryRelationshipMapper');
}
return {
isCompliant: complianceScore >= 0.6,
hasConflict,
confidence: complianceScore,
conflictSeverity,
evidence,
recommendation: hasConflict
? 'Review deployment readiness issues and ADR implementation status'
: 'Deployment demonstrates good ADR compliance',
};
}
calculateSecurityArchitectureConfidence(security, adr) {
let confidence = 0.3; // Base confidence
// Risk level alignment
const adrRiskMentioned = (adr.decisionData?.consequences?.risks?.length || 0) > 0;
if (adrRiskMentioned && security.securityData?.riskAssessment?.overallRisk === 'high') {
confidence += 0.3;
}
// Context overlap
const contextOverlap = this.getContextSimilarity(security.context, adr.context);
confidence += contextOverlap * 0.4;
return Math.min(1.0, confidence);
}
hasSecurityArchitectureRelation(security, adr) {
// Check if security pattern is relevant to the architectural decision domain
const securityTags = security.tags || [];
const adrTags = adr.tags || [];
const hasCommonTags = securityTags.some(tag => adrTags.includes(tag));
const hasSecurityRisks = (adr.decisionData?.consequences?.risks?.length || 0) > 0;
return hasCommonTags || hasSecurityRisks;
}
hasFailureSessionMatch(failure, session) {
return (failure.patternData?.failureType === session.sessionData?.failurePattern?.failureType &&
this.getErrorSignatureSimilarity(failure, session) > 0.7);
}
calculateFailureSessionConfidence(failure, session) {
let confidence = 0.3;
// Failure type exact match
if (failure.patternData?.failureType === session.sessionData?.failurePattern?.failureType) {
confidence += 0.4;
}
// Error signature similarity
const errorSimilarity = this.getErrorSignatureSimilarity(failure, session);
confidence += errorSimilarity * 0.3;
return Math.min(1.0, confidence);
}
hasEnvironmentDeploymentRelation(env, deployment) {
return env.environmentData?.environmentType === deployment.assessmentData?.environment;
}
calculateEnvironmentDeploymentConfidence(env, deployment) {
let confidence = 0.4; // Base confidence
// Environment type exact match
if (env.environmentData?.environmentType === deployment.assessmentData?.environment) {
confidence += 0.3;
}
// Configuration overlap
const configOverlap = this.getConfigurationOverlap(env, deployment);
confidence += configOverlap * 0.3;
return Math.min(1.0, confidence);
}
// Utility methods for similarity calculations
getTemporalOverlap(time1, time2) {
const diff = Math.abs(new Date(time1).getTime() - new Date(time2).getTime());
const hours = diff / (1000 * 60 * 60);
return `${hours.toFixed(1)} hours apart`;
}
getEnvironmentMatch(session, env) {
const sessionEnv = session.sessionData?.environmentContext?.['environment'] || 'unknown';
const envType = env.environmentData?.environmentType || 'unknown';
return sessionEnv === envType ? 'exact match' : 'different environments';
}
getContextSimilarity(context1, context2) {
// Simple context similarity based on shared technical stack and environmental factors
const stack1 = context1.technicalStack || [];
const stack2 = context2.technicalStack || [];
const env1 = context1.environmentalFactors || [];
const env2 = context2.environmentalFactors || [];
const stackOverlap = this.getTechnicalStackOverlap(stack1, stack2);
const envOverlap = this.getArrayOverlap(env1, env2);
return (stackOverlap + envOverlap) / 2;
}
getTechnicalStackOverlap(stack1, stack2) {
if (stack1.length === 0 && stack2.length === 0)
return 1.0;
if (stack1.length === 0 || stack2.length === 0)
return 0.0;
const commonTech = stack1.filter(tech => stack2.includes(tech));
const totalUniqueTech = new Set([...stack1, ...stack2]).size;
return commonTech.length / totalUniqueTech;
}
getArrayOverlap(arr1, arr2) {
if (arr1.length === 0 && arr2.length === 0)
return 1.0;
if (arr1.length === 0 || arr2.length === 0)
return 0.0;
const common = arr1.filter(item => arr2.includes(item));
const totalUnique = new Set([...arr1, ...arr2]).size;
return common.length / totalUnique;
}
getTemporalProximityScore(time1, time2) {
const diff = Math.abs(new Date(time1).getTime() - new Date(time2).getTime());
const hours = diff / (1000 * 60 * 60);
if (hours <= 1)
return 1.0;
if (hours <= 6)
return 0.8;
if (hours <= 24)
return 0.6;
if (hours <= 168)
return 0.4; // 1 week
return 0.0;
}
getErrorSignatureSimilarity(failure, session) {
const failureSignature = failure.patternData.pattern;
const sessionSignature = session.sessionData.failurePattern.errorSignature;
// Simple string similarity (in a real implementation, you might use Levenshtein distance)
if (failureSignature === sessionSignature)
return 1.0;
if (failureSignature.includes(sessionSignature) ||
sessionSignature.includes(failureSignature)) {
return 0.8;
}
const commonWords = failureSignature
.split(' ')
.filter((word) => sessionSignature.includes(word) && word.length > 3);
const totalWords = new Set([...failureSignature.split(' '), ...sessionSignature.split(' ')])
.size;
return commonWords.length / totalWords;
}
getEnvironmentOverlap(failure, session) {
const failureEnvs = failure.patternData.environments || [];
const sessionEnv = session.sessionData?.environmentContext?.['environment'] || 'unknown';
return failureEnvs.includes(sessionEnv) ? 'environment match' : 'different environments';
}
getConfigurationOverlap(env, _deployment) {
// Compare configuration elements between environment snapshot and deployment
const envConfig = env.environmentData.configuration || {};
// This is a simplified comparison - in practice, you'd compare specific config elements
if (typeof envConfig === 'object' && Object.keys(envConfig).length > 0) {
return 0.7; // Assume moderate overlap when config data is present
}
return 0.3; // Low overlap when limited config data
}
extractArchitectureDomain(adr) {
const tags = adr.tags || [];
const commonDomains = ['database', 'frontend', 'backend', 'security', 'infrastructure', 'api'];
for (const domain of commonDomains) {
if (tags.some(tag => tag.toLowerCase().includes(domain))) {
return domain;
}
}
return 'general';
}
}
//# sourceMappingURL=memory-relationship-mapper.js.map