mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
257 lines • 9.56 kB
JavaScript
/**
* Machine-readable rule format utilities
* Implements JSON/YAML rule serialization and validation
*/
import { McpAdrError } from '../types/index.js';
/**
* Create a new rule set with metadata
*/
export function createRuleSet(name, description, rules, author = 'MCP ADR Analysis Server') {
const now = new Date().toISOString();
// Group rules by category
const categoryMap = new Map();
rules.forEach(rule => {
const category = rule.category;
if (!categoryMap.has(category)) {
categoryMap.set(category, []);
}
categoryMap.get(category).push(rule);
});
// Create category summaries
const categories = Array.from(categoryMap.entries()).map(([name, categoryRules]) => ({
name,
description: getCategoryDescription(name),
priority: getCategoryPriority(name),
ruleCount: categoryRules.length,
}));
// Extract rule dependencies
const dependencies = extractRuleDependencies(rules);
return {
metadata: {
version: '1.0.0',
name,
description,
created: now,
lastModified: now,
author,
tags: extractRuleTags(rules),
},
rules,
categories,
dependencies,
};
}
/**
* Serialize rule set to JSON
*/
export function serializeRuleSetToJson(ruleSet) {
try {
return JSON.stringify(ruleSet, null, 2);
}
catch (error) {
throw new McpAdrError(`Failed to serialize rule set to JSON: ${error instanceof Error ? error.message : String(error)}`, 'SERIALIZATION_ERROR');
}
}
/**
* Serialize rule set to YAML
*/
export function serializeRuleSetToYaml(ruleSet) {
try {
// Simple YAML serialization (for complex cases, use a proper YAML library)
const yamlLines = [];
// Metadata section
yamlLines.push('metadata:');
yamlLines.push(` version: "${ruleSet.metadata.version}"`);
yamlLines.push(` name: "${ruleSet.metadata.name}"`);
yamlLines.push(` description: "${ruleSet.metadata.description}"`);
yamlLines.push(` created: "${ruleSet.metadata.created}"`);
yamlLines.push(` lastModified: "${ruleSet.metadata.lastModified}"`);
yamlLines.push(` author: "${ruleSet.metadata.author}"`);
yamlLines.push(' tags:');
ruleSet.metadata.tags.forEach(tag => {
yamlLines.push(` - "${tag}"`);
});
// Categories section
yamlLines.push('');
yamlLines.push('categories:');
ruleSet.categories.forEach(category => {
yamlLines.push(` - name: "${category.name}"`);
yamlLines.push(` description: "${category.description}"`);
yamlLines.push(` priority: "${category.priority}"`);
yamlLines.push(` ruleCount: ${category.ruleCount}`);
});
// Rules section
yamlLines.push('');
yamlLines.push('rules:');
ruleSet.rules.forEach(rule => {
yamlLines.push(` - id: "${rule.id}"`);
yamlLines.push(` name: "${rule.name}"`);
yamlLines.push(` description: "${rule.description}"`);
yamlLines.push(` category: "${rule.category}"`);
yamlLines.push(` type: "${rule.type}"`);
yamlLines.push(` severity: "${rule.severity}"`);
yamlLines.push(` scope: "${rule.scope}"`);
yamlLines.push(` pattern: "${rule.pattern}"`);
yamlLines.push(` message: "${rule.message}"`);
yamlLines.push(` automatable: ${rule.automatable}`);
yamlLines.push(` confidence: ${rule.confidence}`);
yamlLines.push(' examples:');
yamlLines.push(' valid:');
rule.examples.valid.forEach(example => {
yamlLines.push(` - "${example}"`);
});
yamlLines.push(' invalid:');
rule.examples.invalid.forEach(example => {
yamlLines.push(` - "${example}"`);
});
yamlLines.push(' sourceAdrs:');
rule.sourceAdrs.forEach(adr => {
yamlLines.push(` - "${adr}"`);
});
yamlLines.push(' tags:');
rule.tags.forEach(tag => {
yamlLines.push(` - "${tag}"`);
});
});
return yamlLines.join('\n');
}
catch (error) {
throw new McpAdrError(`Failed to serialize rule set to YAML: ${error instanceof Error ? error.message : String(error)}`, 'SERIALIZATION_ERROR');
}
}
/**
* Parse rule set from JSON
*/
export function parseRuleSetFromJson(jsonContent) {
try {
const ruleSet = JSON.parse(jsonContent);
validateRuleSet(ruleSet);
return ruleSet;
}
catch (error) {
throw new McpAdrError(`Failed to parse rule set from JSON: ${error instanceof Error ? error.message : String(error)}`, 'PARSING_ERROR');
}
}
/**
* Create compliance report
*/
export function createComplianceReport(projectName, ruleSetVersion, validationResults, scope = 'full_project') {
const now = new Date().toISOString();
const reportId = `compliance-${Date.now()}`;
// Calculate summary metrics
const totalFiles = validationResults.length;
const totalViolations = validationResults.reduce((sum, result) => sum + result.violations.length, 0);
const totalRules = validationResults.reduce((sum, result) => sum + result.totalRulesChecked, 0) / totalFiles;
const overallCompliance = validationResults.reduce((sum, result) => sum + result.overallCompliance, 0) / totalFiles;
// Determine risk level
const criticalViolations = validationResults.reduce((sum, result) => sum + result.violations.filter(v => v.severity === 'critical').length, 0);
const errorViolations = validationResults.reduce((sum, result) => sum + result.violations.filter(v => v.severity === 'error').length, 0);
let riskLevel = 'low';
if (criticalViolations > 0)
riskLevel = 'critical';
else if (errorViolations > 5)
riskLevel = 'high';
else if (overallCompliance < 0.8)
riskLevel = 'medium';
return {
metadata: {
reportId,
generatedAt: now,
projectName,
ruleSetVersion,
scope,
},
summary: {
overallCompliance,
totalFiles,
totalRules: Math.round(totalRules),
totalViolations,
riskLevel,
},
results: validationResults,
trends: [], // Would be populated with historical data
};
}
/**
* Serialize compliance report to JSON
*/
export function serializeComplianceReportToJson(report) {
try {
return JSON.stringify(report, null, 2);
}
catch (error) {
throw new McpAdrError(`Failed to serialize compliance report: ${error instanceof Error ? error.message : String(error)}`, 'SERIALIZATION_ERROR');
}
}
/**
* Validate rule set structure
*/
function validateRuleSet(ruleSet) {
if (!ruleSet.metadata || !ruleSet.rules || !Array.isArray(ruleSet.rules)) {
throw new Error('Invalid rule set structure: missing metadata or rules');
}
if (!ruleSet.metadata.version || !ruleSet.metadata.name) {
throw new Error('Invalid rule set metadata: missing version or name');
}
// Validate each rule
ruleSet.rules.forEach((rule, index) => {
if (!rule.id || !rule.name || !rule.description) {
throw new Error(`Invalid rule at index ${index}: missing required fields`);
}
if (!['must', 'should', 'may', 'must_not', 'should_not'].includes(rule.type)) {
throw new Error(`Invalid rule type at index ${index}: ${rule.type}`);
}
if (!['info', 'warning', 'error', 'critical'].includes(rule.severity)) {
throw new Error(`Invalid rule severity at index ${index}: ${rule.severity}`);
}
});
}
/**
* Get category description
*/
function getCategoryDescription(category) {
const descriptions = {
architectural: 'Rules governing overall system architecture and design patterns',
technology: 'Rules about technology choices, frameworks, and libraries',
coding: 'Rules for code style, organization, and implementation practices',
process: 'Rules for development processes and workflows',
security: 'Rules for security implementation and best practices',
performance: 'Rules for performance optimization and efficiency',
};
return descriptions[category] || 'General architectural rules';
}
/**
* Get category priority
*/
function getCategoryPriority(category) {
const priorities = {
security: 'critical',
architectural: 'high',
performance: 'high',
technology: 'medium',
coding: 'medium',
process: 'low',
};
return priorities[category] || 'medium';
}
/**
* Extract rule dependencies
*/
function extractRuleDependencies(rules) {
// Simple dependency extraction based on rule content
// In a real implementation, this would be more sophisticated
return rules.map(rule => ({
ruleId: rule.id,
dependsOn: [],
conflictsWith: [],
relationship: 'enhances',
}));
}
/**
* Extract tags from rules
*/
function extractRuleTags(rules) {
const allTags = rules.flatMap(rule => rule.tags);
return Array.from(new Set(allTags));
}
//# sourceMappingURL=rule-format.js.map