UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

282 lines 11.9 kB
/** * 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