mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
370 lines (365 loc) • 15.3 kB
JavaScript
/**
* Dynamic Project Health Scoring System
* Aggregates scores from multiple MCP tools to provide real-time project health assessment
*/
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
export class ProjectHealthScoring {
scoringCachePath;
weights;
constructor(projectPath, weights) {
this.scoringCachePath = join(projectPath, '.mcp-adr-cache', 'project-health-scores.json');
this.weights = {
taskCompletion: 0.25,
deploymentReadiness: 0.30,
architectureCompliance: 0.20,
securityPosture: 0.15,
codeQuality: 0.10,
...weights
};
}
/**
* Get current project health score
*/
async getProjectHealthScore() {
let currentScore = this.loadCachedScore();
if (!currentScore) {
currentScore = this.initializeScore();
}
// Recalculate overall score with current weights
currentScore.overall = this.calculateOverallScore(currentScore.breakdown);
currentScore.confidence = this.calculateConfidence(currentScore.breakdown);
currentScore.lastUpdated = new Date().toISOString();
this.saveCachedScore(currentScore);
return currentScore;
}
/**
* Update task completion score (called by TODO management tools)
*/
async updateTaskCompletionScore(taskData) {
const currentScore = await this.getProjectHealthScore();
currentScore.breakdown.taskCompletion = {
completed: taskData.completed,
total: taskData.total,
percentage: taskData.total > 0 ? (taskData.completed / taskData.total) * 100 : 100,
priorityWeightedScore: taskData.priorityWeightedScore,
criticalTasksRemaining: taskData.criticalTasksRemaining,
lastUpdated: new Date().toISOString()
};
currentScore.taskCompletion = this.calculateTaskCompletionScore(currentScore.breakdown.taskCompletion);
this.addInfluencingTool(currentScore, 'manage_todo');
await this.updateOverallScore(currentScore);
}
/**
* Update deployment readiness score (called by smart_git_push)
*/
async updateDeploymentReadinessScore(deploymentData) {
const currentScore = await this.getProjectHealthScore();
currentScore.breakdown.deploymentReadiness = {
releaseScore: deploymentData.releaseScore,
milestoneCompletion: deploymentData.milestoneCompletion,
criticalBlockers: deploymentData.criticalBlockers,
warningBlockers: deploymentData.warningBlockers,
gitHealthScore: deploymentData.gitHealthScore,
lastUpdated: new Date().toISOString()
};
currentScore.deploymentReadiness = this.calculateDeploymentReadinessScore(currentScore.breakdown.deploymentReadiness);
this.addInfluencingTool(currentScore, 'smart_git_push');
await this.updateOverallScore(currentScore);
}
/**
* Update architecture compliance score (called by compare_adr_progress)
*/
async updateArchitectureComplianceScore(architectureData) {
const currentScore = await this.getProjectHealthScore();
currentScore.breakdown.architectureCompliance = {
adrImplementationScore: architectureData.adrImplementationScore,
mockVsProductionScore: architectureData.mockVsProductionScore,
environmentAlignmentScore: architectureData.environmentAlignmentScore,
lastUpdated: new Date().toISOString()
};
currentScore.architectureCompliance = this.calculateArchitectureComplianceScore(currentScore.breakdown.architectureCompliance);
this.addInfluencingTool(currentScore, 'compare_adr_progress');
await this.updateOverallScore(currentScore);
}
/**
* Update security posture score (called by content security tools)
*/
async updateSecurityPostureScore(securityData) {
const currentScore = await this.getProjectHealthScore();
currentScore.breakdown.securityPosture = {
secretExposureRisk: securityData.secretExposureRisk,
contentMaskingEffectiveness: securityData.contentMaskingEffectiveness,
vulnerabilityCount: securityData.vulnerabilityCount,
lastUpdated: new Date().toISOString()
};
currentScore.securityPosture = this.calculateSecurityPostureScore(currentScore.breakdown.securityPosture);
this.addInfluencingTool(currentScore, 'analyze_content_security');
await this.updateOverallScore(currentScore);
}
/**
* Update code quality score (called by rule validation tools)
*/
async updateCodeQualityScore(codeQualityData) {
const currentScore = await this.getProjectHealthScore();
currentScore.breakdown.codeQuality = {
ruleViolations: codeQualityData.ruleViolations,
patternAdherence: codeQualityData.patternAdherence,
technicalDebtScore: codeQualityData.technicalDebtScore,
lastUpdated: new Date().toISOString()
};
currentScore.codeQuality = this.calculateCodeQualityScore(currentScore.breakdown.codeQuality);
this.addInfluencingTool(currentScore, 'validate_rules');
await this.updateOverallScore(currentScore);
}
/**
* Calculate overall weighted score
*/
calculateOverallScore(breakdown) {
const scores = {
taskCompletion: this.calculateTaskCompletionScore(breakdown.taskCompletion),
deploymentReadiness: this.calculateDeploymentReadinessScore(breakdown.deploymentReadiness),
architectureCompliance: this.calculateArchitectureComplianceScore(breakdown.architectureCompliance),
securityPosture: this.calculateSecurityPostureScore(breakdown.securityPosture),
codeQuality: this.calculateCodeQualityScore(breakdown.codeQuality)
};
return Math.round(scores.taskCompletion * this.weights.taskCompletion +
scores.deploymentReadiness * this.weights.deploymentReadiness +
scores.architectureCompliance * this.weights.architectureCompliance +
scores.securityPosture * this.weights.securityPosture +
scores.codeQuality * this.weights.codeQuality);
}
/**
* Calculate task completion score with priority weighting
*/
calculateTaskCompletionScore(taskData) {
if (taskData.total === 0)
return 100;
// Base completion score
let score = taskData.percentage;
// Priority weighting boost
if (taskData.priorityWeightedScore > taskData.percentage) {
score = Math.min(100, score + (taskData.priorityWeightedScore - taskData.percentage) * 0.3);
}
// Critical tasks penalty
if (taskData.criticalTasksRemaining > 0) {
score = Math.max(0, score - (taskData.criticalTasksRemaining * 15));
}
return Math.round(score);
}
/**
* Calculate deployment readiness score
*/
calculateDeploymentReadinessScore(deploymentData) {
let score = deploymentData.releaseScore * 100;
// Milestone completion factor
score = (score + deploymentData.milestoneCompletion * 100) / 2;
// Critical blockers are severe penalties
score = Math.max(0, score - (deploymentData.criticalBlockers * 25));
// Warning blockers are minor penalties
score = Math.max(0, score - (deploymentData.warningBlockers * 5));
// Git health factor
score = (score + deploymentData.gitHealthScore) / 2;
return Math.round(score);
}
/**
* Calculate architecture compliance score
*/
calculateArchitectureComplianceScore(architectureData) {
return Math.round((architectureData.adrImplementationScore * 0.4 +
architectureData.mockVsProductionScore * 0.3 +
architectureData.environmentAlignmentScore * 0.3));
}
/**
* Calculate security posture score
*/
calculateSecurityPostureScore(securityData) {
let score = 100;
// Secret exposure risk (inverted - higher risk = lower score)
score -= securityData.secretExposureRisk;
// Content masking effectiveness
score = (score + securityData.contentMaskingEffectiveness) / 2;
// Vulnerability penalty
score = Math.max(0, score - (securityData.vulnerabilityCount * 10));
return Math.round(Math.max(0, score));
}
/**
* Calculate code quality score
*/
calculateCodeQualityScore(codeQualityData) {
let score = codeQualityData.patternAdherence;
// Rule violations penalty
score = Math.max(0, score - (codeQualityData.ruleViolations * 5));
// Technical debt factor
score = (score + codeQualityData.technicalDebtScore) / 2;
return Math.round(score);
}
/**
* Calculate confidence in overall score
*/
calculateConfidence(breakdown) {
const now = new Date();
const maxAgeMs = 24 * 60 * 60 * 1000; // 24 hours
let confidence = 100;
// Reduce confidence based on data age
Object.values(breakdown).forEach(scoreData => {
const age = now.getTime() - new Date(scoreData.lastUpdated).getTime();
if (age > maxAgeMs) {
confidence -= 10;
}
});
return Math.max(0, confidence);
}
/**
* Initialize default score structure
*/
initializeScore() {
const now = new Date().toISOString();
return {
overall: 50,
taskCompletion: 50,
deploymentReadiness: 50,
architectureCompliance: 50,
securityPosture: 50,
codeQuality: 50,
confidence: 0,
lastUpdated: now,
influencingTools: [],
breakdown: {
taskCompletion: {
completed: 0,
total: 0,
percentage: 0,
priorityWeightedScore: 0,
criticalTasksRemaining: 0,
lastUpdated: now
},
deploymentReadiness: {
releaseScore: 0.5,
milestoneCompletion: 0.5,
criticalBlockers: 0,
warningBlockers: 0,
gitHealthScore: 50,
lastUpdated: now
},
architectureCompliance: {
adrImplementationScore: 50,
mockVsProductionScore: 50,
environmentAlignmentScore: 50,
lastUpdated: now
},
securityPosture: {
secretExposureRisk: 0,
contentMaskingEffectiveness: 80,
vulnerabilityCount: 0,
lastUpdated: now
},
codeQuality: {
ruleViolations: 0,
patternAdherence: 70,
technicalDebtScore: 60,
lastUpdated: now
}
}
};
}
/**
* Update overall score and save
*/
async updateOverallScore(score) {
score.overall = this.calculateOverallScore(score.breakdown);
score.confidence = this.calculateConfidence(score.breakdown);
score.lastUpdated = new Date().toISOString();
this.saveCachedScore(score);
}
/**
* Add tool to influencing tools list
*/
addInfluencingTool(score, toolName) {
if (!score.influencingTools.includes(toolName)) {
score.influencingTools.push(toolName);
}
}
/**
* Load cached score from file
*/
loadCachedScore() {
try {
if (existsSync(this.scoringCachePath)) {
const cached = JSON.parse(readFileSync(this.scoringCachePath, 'utf-8'));
return cached;
}
}
catch (error) {
// Ignore cache errors
}
return null;
}
/**
* Save score to cache
*/
saveCachedScore(score) {
try {
// Ensure cache directory exists
mkdirSync(dirname(this.scoringCachePath), { recursive: true });
writeFileSync(this.scoringCachePath, JSON.stringify(score, null, 2));
console.log(`✅ ProjectHealthScoring: Successfully saved to ${this.scoringCachePath}`);
}
catch (error) {
// Log the error instead of silently ignoring it
console.error(`❌ ProjectHealthScoring: Failed to save to ${this.scoringCachePath}:`, error);
}
}
/**
* Generate formatted score display for TODO.md header
*/
async generateScoreDisplay() {
const score = await this.getProjectHealthScore();
const overallEmoji = this.getScoreEmoji(score.overall);
const confidenceEmoji = this.getConfidenceEmoji(score.confidence);
return `# Project Health Dashboard
## 🎯 Overall Project Health: ${overallEmoji} ${score.overall}% ${confidenceEmoji}
### 📊 Health Metrics
- 📋 **Task Completion**: ${this.getScoreEmoji(score.taskCompletion)} ${score.taskCompletion}%
- 🚀 **Deployment Readiness**: ${this.getScoreEmoji(score.deploymentReadiness)} ${score.deploymentReadiness}%
- 🏗️ **Architecture Compliance**: ${this.getScoreEmoji(score.architectureCompliance)} ${score.architectureCompliance}%
- 🔒 **Security Posture**: ${this.getScoreEmoji(score.securityPosture)} ${score.securityPosture}%
- 🛠️ **Code Quality**: ${this.getScoreEmoji(score.codeQuality)} ${score.codeQuality}%
### 🔄 Data Freshness
- **Last Updated**: ${new Date(score.lastUpdated).toLocaleString()}
- **Confidence**: ${score.confidence}%
- **Contributing Tools**: ${score.influencingTools.join(', ') || 'None'}
### 📈 Detailed Breakdown
- **Tasks**: ${score.breakdown.taskCompletion.completed}/${score.breakdown.taskCompletion.total} completed, ${score.breakdown.taskCompletion.criticalTasksRemaining} critical remaining
- **Deployment**: ${score.breakdown.deploymentReadiness.criticalBlockers} critical blockers, ${score.breakdown.deploymentReadiness.warningBlockers} warnings
- **Security**: ${score.breakdown.securityPosture.vulnerabilityCount} vulnerabilities, ${score.breakdown.securityPosture.contentMaskingEffectiveness}% masking effectiveness
- **Code Quality**: ${score.breakdown.codeQuality.ruleViolations} rule violations, ${score.breakdown.codeQuality.patternAdherence}% pattern adherence
---
`;
}
/**
* Get emoji for score level
*/
getScoreEmoji(score) {
if (score >= 90)
return '🟢';
if (score >= 75)
return '🟡';
if (score >= 60)
return '🟠';
return '🔴';
}
/**
* Get emoji for confidence level
*/
getConfidenceEmoji(confidence) {
if (confidence >= 90)
return '💎';
if (confidence >= 75)
return '✨';
if (confidence >= 60)
return '⚡';
return '⚠️';
}
}
//# sourceMappingURL=project-health-scoring.js.map