mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
282 lines • 11.9 kB
JavaScript
/**
* Rule Generation Resource - AI-powered rule generation from ADRs and patterns
* URI Pattern: adr://rule_generation
*
* Bridges to rule-generation-tool for comprehensive rule generation capabilities
*/
import { resourceCache, generateETag } from './resource-cache.js';
/**
* Extract structured data from rule-generation-tool output
*/
function extractRuleDataFromToolOutput(toolOutput, operation) {
const extracted = {};
// Extract generated rules
const rules = [];
// Extract rules from JSON blocks or structured output
const jsonBlockMatch = toolOutput.match(/```json\s*([\s\S]*?)\s*```/);
if (jsonBlockMatch && jsonBlockMatch[1]) {
try {
const parsed = JSON.parse(jsonBlockMatch[1]);
if (Array.isArray(parsed)) {
rules.push(...parsed);
}
else if (parsed.rules && Array.isArray(parsed.rules)) {
rules.push(...parsed.rules);
}
}
catch {
// JSON parsing failed, continue with text extraction
}
}
// Extract rules from text format
const ruleMatches = toolOutput.matchAll(/(?:Rule|rule)[:\s]+([^\n]+)/gi);
for (const match of ruleMatches) {
if (match[1] && rules.length < 20) { // Limit extraction
const ruleName = match[1].trim();
// Try to find description
const descPattern = new RegExp(`${ruleName}[\\s\\S]{0,200}?description[:\\s]+([^\\n]+)`, 'i');
const descMatch = toolOutput.match(descPattern);
rules.push({
id: `rule-${Date.now()}-${rules.length}`,
name: ruleName,
description: descMatch && descMatch[1] ? descMatch[1].trim() : `Rule: ${ruleName}`,
type: toolOutput.toLowerCase().includes('security') ? 'security' :
toolOutput.toLowerCase().includes('performance') ? 'performance' :
toolOutput.toLowerCase().includes('architect') ? 'architectural' : 'coding',
severity: toolOutput.toLowerCase().includes('critical') ? 'critical' :
toolOutput.toLowerCase().includes('error') ? 'error' :
toolOutput.toLowerCase().includes('warning') ? 'warning' : 'info',
message: `Follow rule: ${ruleName}`,
source: 'adr',
enabled: true,
});
}
}
if (rules.length > 0) {
extracted.rules = rules;
// Generate summary
const byType = {};
const bySeverity = {};
const bySource = {};
for (const rule of rules) {
byType[rule.type] = (byType[rule.type] || 0) + 1;
bySeverity[rule.severity] = (bySeverity[rule.severity] || 0) + 1;
bySource[rule.source] = (bySource[rule.source] || 0) + 1;
}
extracted.summary = {
totalRulesGenerated: rules.length,
byType,
bySeverity,
bySource,
};
}
// Extract validation results
if (operation === 'validate' && toolOutput.includes('valid')) {
const validMatch = toolOutput.match(/(\d+)\s+valid/i);
const invalidMatch = toolOutput.match(/(\d+)\s+invalid/i);
const totalMatch = toolOutput.match(/(\d+)\s+total/i);
const complianceMatch = toolOutput.match(/compliance[:\s]+(\d+)%/i);
const validRules = validMatch && validMatch[1] ? parseInt(validMatch[1]) : 0;
const invalidRules = invalidMatch && invalidMatch[1] ? parseInt(invalidMatch[1]) : 0;
const totalRules = totalMatch && totalMatch[1] ? parseInt(totalMatch[1]) : validRules + invalidRules;
extracted.validation = {
totalRules,
validRules,
invalidRules,
validationErrors: [],
complianceScore: complianceMatch && complianceMatch[1] ? parseInt(complianceMatch[1]) : totalRules > 0 ? Math.round((validRules / totalRules) * 100) : 100,
};
// Extract validation errors
const errorMatches = toolOutput.matchAll(/(?:error|invalid)[:\s]+(.*?)(?:\n|$)/gi);
for (const match of errorMatches) {
if (match[1] && extracted.validation) {
extracted.validation.validationErrors.push({
rule: 'unknown',
error: match[1].trim(),
severity: 'error',
});
}
}
}
// Extract rule set information
if (operation === 'create_set' && toolOutput.includes('rule set')) {
const setNameMatch = toolOutput.match(/(?:set|name)[:\s]+([^\n]+)/i);
const setDescMatch = toolOutput.match(/description[:\s]+([^\n]+)/i);
extracted.ruleSet = {
id: `ruleset-${Date.now()}`,
name: setNameMatch && setNameMatch[1] ? setNameMatch[1].trim() : 'Generated Rule Set',
description: setDescMatch && setDescMatch[1] ? setDescMatch[1].trim() : 'Automatically generated rule set',
rules: rules.map(r => r.id),
applicability: {
projectTypes: [],
technologies: [],
environments: [],
},
priority: 'medium',
};
}
// Extract extraction statistics
const adrsMatch = toolOutput.match(/(\d+)\s+ADRs?/i);
const patternsMatch = toolOutput.match(/(\d+)\s+patterns?/i);
const extractedMatch = toolOutput.match(/(\d+)\s+(?:rules?\s+)?extracted/i);
if (adrsMatch || patternsMatch || extractedMatch) {
extracted.extraction = {
adrsAnalyzed: adrsMatch && adrsMatch[1] ? parseInt(adrsMatch[1]) : 0,
patternsIdentified: patternsMatch && patternsMatch[1] ? parseInt(patternsMatch[1]) : 0,
rulesExtracted: extractedMatch && extractedMatch[1] ? parseInt(extractedMatch[1]) : rules.length,
confidenceScores: {},
};
}
// Extract knowledge enhancement info
if (toolOutput.includes('knowledge') || toolOutput.includes('governance')) {
const domains = [];
if (toolOutput.includes('api'))
domains.push('api-design');
if (toolOutput.includes('security'))
domains.push('security-patterns');
if (toolOutput.includes('architecture'))
domains.push('architectural-governance');
extracted.knowledgeEnhancement = {
enabled: true,
domains,
governanceKnowledge: [],
};
}
return extracted;
}
/**
* Generate basic rule generation result (fallback)
*/
async function generateBasicRuleGeneration(operation, source) {
return {
operation,
source,
timestamp: new Date().toISOString(),
status: 'partial',
rules: [],
summary: {
totalRulesGenerated: 0,
byType: {},
bySeverity: {},
bySource: {},
},
analysisMetadata: {
operation,
timestamp: new Date().toISOString(),
confidence: 0.5,
source: 'basic',
knowledgeEnhancement: false,
enhancedMode: false,
},
};
}
/**
* Generate comprehensive rule generation using rule-generation-tool
*/
async function generateComprehensiveRuleGeneration(operation, source, adrDirectory, projectPath, knowledgeEnhancement, enhancedMode, outputFormat) {
try {
let toolResult;
if (operation === 'generate') {
// Import and call the comprehensive tool
const { generateRules } = await import('../tools/rule-generation-tool.js');
toolResult = await generateRules({
source,
adrDirectory,
projectPath,
outputFormat,
knowledgeEnhancement,
enhancedMode,
});
}
else if (operation === 'validate') {
const { validateRules } = await import('../tools/rule-generation-tool.js');
// Load rules to validate (empty array for now - would need actual rules)
toolResult = await validateRules({
rules: [], // TODO: Load actual rules for validation
projectPath,
reportFormat: 'detailed',
});
}
else if (operation === 'create_set') {
const { createRuleSet } = await import('../tools/rule-generation-tool.js');
toolResult = await createRuleSet({
name: 'Generated Rule Set',
description: 'AI-generated architectural rules from ADRs and patterns',
rules: [], // TODO: Provide actual rules for the set
outputFormat,
});
}
// Extract text content from tool result
let toolOutputText = '';
if (toolResult && toolResult.content && Array.isArray(toolResult.content)) {
for (const item of toolResult.content) {
if (item.type === 'text' && item.text) {
toolOutputText += item.text + '\n';
}
}
}
// Get basic data
const basicResult = await generateBasicRuleGeneration(operation, source);
// Extract enhanced data from tool output
const enhancedData = extractRuleDataFromToolOutput(toolOutputText, operation);
// Merge basic and enhanced data
const comprehensiveResult = {
...basicResult,
...enhancedData,
status: enhancedData.rules && enhancedData.rules.length > 0 ? 'success' : 'partial',
analysisMetadata: {
operation,
timestamp: new Date().toISOString(),
confidence: 0.9,
source: 'comprehensive-tool',
knowledgeEnhancement,
enhancedMode,
},
};
return comprehensiveResult;
}
catch (error) {
console.error('[rule-generation-resource] Tool execution failed, falling back to basic generation:', error);
return generateBasicRuleGeneration(operation, source);
}
}
/**
* Generate rule generation resource
*/
export async function generateRuleGenerationResource(_params, searchParams) {
// Extract query parameters
const operation = (searchParams?.get('operation') || 'generate');
const source = (searchParams?.get('source') || 'both');
const adrDirectory = searchParams?.get('adr_directory') || process.env['ADR_DIRECTORY'] || 'docs/adrs';
const projectPath = searchParams?.get('project_path') || process.cwd();
const knowledgeEnhancement = searchParams?.get('knowledge') !== 'false';
const enhancedMode = searchParams?.get('enhanced') !== 'false';
const outputFormat = (searchParams?.get('format') || 'json');
const useComprehensive = searchParams?.get('comprehensive') !== 'false';
const cacheKey = `rule-generation:${operation}:${source}:${knowledgeEnhancement}:${enhancedMode}:${useComprehensive}`;
// Check cache
const cached = await resourceCache.get(cacheKey);
if (cached) {
return cached;
}
// Generate rule generation result (comprehensive or basic)
let ruleGenerationResult;
if (useComprehensive) {
ruleGenerationResult = await generateComprehensiveRuleGeneration(operation, source, adrDirectory, projectPath, knowledgeEnhancement, enhancedMode, outputFormat);
}
else {
ruleGenerationResult = await generateBasicRuleGeneration(operation, source);
}
const result = {
data: ruleGenerationResult,
contentType: 'application/json',
lastModified: new Date().toISOString(),
cacheKey,
ttl: 300, // 5 minutes cache
etag: generateETag(ruleGenerationResult),
};
// Cache result
resourceCache.set(cacheKey, result, result.ttl);
return result;
}
//# sourceMappingURL=rule-generation-resource.js.map