UNPKG

mcp-adr-analysis-server

Version:

MCP server for analyzing Architectural Decision Records and project architecture

738 lines (642 loc) 30 kB
/** * MCP Tool for ADR suggestions and implicit decision detection * Enhanced with Knowledge Generation and Reflexion capabilities * Implements prompt-driven ADR recommendation system with learning */ import { McpAdrError } from '../types/index.js'; import { generateArchitecturalKnowledge } from '../utils/knowledge-generation.js'; import { executeWithReflexion, retrieveRelevantMemories, createToolReflexionConfig } from '../utils/reflexion.js'; import { executeADRSuggestionPrompt, formatMCPResponse } from '../utils/prompt-execution.js'; /** * Suggest ADRs based on project analysis with advanced prompting techniques * Enhanced with Knowledge Generation and Reflexion learning capabilities */ export async function suggestAdrs(args) { const { projectPath = process.cwd(), analysisType = 'comprehensive', beforeCode, afterCode, changeDescription, commitMessages, existingAdrs, enhancedMode = true, // Default to enhanced mode learningEnabled = true, // Default to learning enabled knowledgeEnhancement = true, // Default to knowledge enhancement conversationContext, // Context from calling LLM } = args; try { const { analyzeImplicitDecisions, analyzeCodeChanges } = await import('../utils/adr-suggestions.js'); switch (analysisType) { case 'implicit_decisions': { let enhancedPrompt = ''; let enhancementInfo = ''; // Apply enhancements if enabled if (enhancedMode && (knowledgeEnhancement || learningEnabled)) { let knowledgeContext = ''; // Generate domain knowledge for implicit decision detection if (knowledgeEnhancement) { try { const knowledgeResult = await generateArchitecturalKnowledge({ projectPath, technologies: [], patterns: [], projectType: 'implicit-decision-detection' }, { domains: ['api-design', 'database-design'], depth: 'basic', cacheEnabled: true }); knowledgeContext = `\n## Knowledge Enhancement\n${knowledgeResult.prompt}\n`; } catch (error) { console.error('[WARNING] Knowledge generation failed:', error); } } // Apply learning if enabled if (learningEnabled) { try { const reflexionConfig = createToolReflexionConfig('suggest_adrs'); const baseResult = await analyzeImplicitDecisions(projectPath, existingAdrs, conversationContext); const reflexionResult = await executeWithReflexion({ prompt: baseResult.analysisPrompt + knowledgeContext, instructions: baseResult.instructions, context: { projectPath, analysisType: 'implicit_decisions', existingAdrs } }, reflexionConfig); enhancedPrompt = reflexionResult.prompt; enhancementInfo = ` ## Enhancement Status - **Knowledge Generation**: ${knowledgeEnhancement ? '✅ Applied' : '❌ Disabled'} - **Reflexion Learning**: ✅ Applied - **Learning from**: Past implicit decision detection tasks `; } catch (error) { console.error('[WARNING] Reflexion enhancement failed:', error); const result = await analyzeImplicitDecisions(projectPath, existingAdrs, conversationContext); enhancedPrompt = result.analysisPrompt + knowledgeContext; } } else { const result = await analyzeImplicitDecisions(projectPath, existingAdrs, conversationContext); enhancedPrompt = result.analysisPrompt + knowledgeContext; enhancementInfo = ` ## Enhancement Status - **Knowledge Generation**: ${knowledgeEnhancement ? '✅ Applied' : '❌ Disabled'} - **Reflexion Learning**: ❌ Disabled `; } } else { const result = await analyzeImplicitDecisions(projectPath, existingAdrs, conversationContext); enhancedPrompt = result.analysisPrompt; enhancementInfo = ` ## Enhancement Status - **Enhanced Mode**: ❌ Disabled - All advanced features are disabled for this analysis `; } const baseResult = await analyzeImplicitDecisions(projectPath, existingAdrs, conversationContext); return { content: [ { type: 'text', text: `# ADR Suggestions: Enhanced Implicit Decisions Analysis ${enhancementInfo} ${baseResult.instructions} ## Enhanced AI Analysis Prompt ${enhancedPrompt} ## Next Steps 1. **Submit the enhanced prompt** to an AI agent for comprehensive analysis 2. **Review the detected decisions** and prioritize based on impact and risk 3. **Use the \`generate_adr_from_decision\` tool** to create ADRs for high-priority decisions 4. **Integrate with existing ADR workflow** for review and approval ## Expected Output The enhanced AI analysis will identify implicit architectural decisions and provide: - Detailed decision analysis with evidence and domain knowledge - Priority and risk assessments informed by past experiences - Suggested ADR titles and content with improved quality - Recommendations for documentation strategy based on learning `, }, ], }; } case 'code_changes': { if (!beforeCode || !afterCode || !changeDescription) { throw new McpAdrError('Code change analysis requires beforeCode, afterCode, and changeDescription', 'INVALID_INPUT'); } let enhancedPrompt = ''; let enhancementInfo = ''; // Apply enhancements if enabled if (enhancedMode && (knowledgeEnhancement || learningEnabled)) { let knowledgeContext = ''; // Generate domain knowledge for code change analysis if (knowledgeEnhancement) { try { const knowledgeResult = await generateArchitecturalKnowledge({ projectPath: projectPath || process.cwd(), technologies: [], patterns: [], projectType: 'code-change-analysis' }, { domains: ['api-design', 'performance-optimization'], depth: 'basic', cacheEnabled: true }); knowledgeContext = `\n## Knowledge Enhancement\n${knowledgeResult.prompt}\n`; } catch (error) { console.error('[WARNING] Knowledge generation failed:', error); } } // Apply learning if enabled if (learningEnabled) { try { const reflexionConfig = createToolReflexionConfig('suggest_adrs', { evaluationCriteria: ['task-success', 'accuracy', 'relevance'] }); const baseResult = await analyzeCodeChanges(beforeCode, afterCode, changeDescription, commitMessages); const reflexionResult = await executeWithReflexion({ prompt: baseResult.analysisPrompt + knowledgeContext, instructions: baseResult.instructions, context: { analysisType: 'code_changes', changeDescription, hasCommitMessages: !!commitMessages?.length } }, reflexionConfig); enhancedPrompt = reflexionResult.prompt; enhancementInfo = ` ## Enhancement Status - **Knowledge Generation**: ${knowledgeEnhancement ? '✅ Applied' : '❌ Disabled'} - **Reflexion Learning**: ✅ Applied - **Learning from**: Past code change analysis tasks `; } catch (error) { console.error('[WARNING] Reflexion enhancement failed:', error); const result = await analyzeCodeChanges(beforeCode, afterCode, changeDescription, commitMessages); enhancedPrompt = result.analysisPrompt + knowledgeContext; } } else { const result = await analyzeCodeChanges(beforeCode, afterCode, changeDescription, commitMessages); enhancedPrompt = result.analysisPrompt + knowledgeContext; enhancementInfo = ` ## Enhancement Status - **Knowledge Generation**: ${knowledgeEnhancement ? '✅ Applied' : '❌ Disabled'} - **Reflexion Learning**: ❌ Disabled `; } } else { const result = await analyzeCodeChanges(beforeCode, afterCode, changeDescription, commitMessages); enhancedPrompt = result.analysisPrompt; enhancementInfo = ` ## Enhancement Status - **Enhanced Mode**: ❌ Disabled - All advanced features are disabled for this analysis `; } const baseResult = await analyzeCodeChanges(beforeCode, afterCode, changeDescription, commitMessages); return { content: [ { type: 'text', text: `# ADR Suggestions: Enhanced Code Change Analysis ${enhancementInfo} ${baseResult.instructions} ## Enhanced AI Analysis Prompt ${enhancedPrompt} ## Next Steps 1. **Submit the enhanced prompt** to an AI agent for change analysis 2. **Review the identified decisions** reflected in the code changes 3. **Document significant decisions** as ADRs using the generation tool 4. **Follow up with development team** for any clarification questions ## Expected Output The enhanced AI analysis will provide: - Architectural decisions reflected in the changes with domain context - Change motivation and context analysis informed by past experiences - Impact and risk assessment with improved accuracy - Recommendations for documentation based on learning patterns `, }, ], }; } case 'comprehensive': { let enhancedPrompt = ''; let knowledgeContext = ''; let reflexionContext = ''; // Step 1: Generate domain-specific knowledge if enabled if (knowledgeEnhancement) { try { const knowledgeResult = await generateArchitecturalKnowledge({ projectPath, technologies: [], // Will be auto-detected from project patterns: [], projectType: 'software-architecture', existingAdrs: existingAdrs || [] }, { domains: ['api-design', 'database-design', 'microservices'], depth: 'intermediate', cacheEnabled: true }); knowledgeContext = ` ## Domain-Specific Knowledge Enhancement The following architectural knowledge has been generated to enhance ADR suggestions: ${knowledgeResult.prompt} --- `; } catch (error) { console.error('[WARNING] Knowledge generation failed:', error); knowledgeContext = '<!-- Knowledge generation unavailable -->\n'; } } // Step 2: Apply Reflexion learning if enabled if (learningEnabled) { try { // Retrieve relevant memories from past ADR suggestion tasks const memoryResult = await retrieveRelevantMemories('adr-suggestion', { projectPath, analysisType: 'comprehensive', existingAdrs }, { maxResults: 5, relevanceThreshold: 0.6 }); reflexionContext = ` ## Learning from Past Experiences The following insights from past ADR suggestion tasks will inform this analysis: ${memoryResult.prompt} --- `; } catch (error) { console.error('[WARNING] Reflexion memory retrieval failed:', error); reflexionContext = '<!-- Learning context unavailable -->\n'; } } // Step 3: Get the base analysis const implicitResult = await analyzeImplicitDecisions(projectPath, existingAdrs, conversationContext); // Step 4: Apply Reflexion execution if learning is enabled if (learningEnabled) { try { const reflexionConfig = createToolReflexionConfig('suggest_adrs', { reflectionDepth: 'detailed', evaluationCriteria: ['task-success', 'relevance', 'clarity'], learningRate: 0.7 }); const reflexionResult = await executeWithReflexion({ prompt: implicitResult.analysisPrompt, instructions: implicitResult.instructions, context: { projectPath, analysisType: 'comprehensive', existingAdrs, knowledgeEnhanced: knowledgeEnhancement, learningEnabled: true } }, reflexionConfig); enhancedPrompt = ` ## Enhanced Analysis with Learning ${reflexionResult.prompt} --- `; } catch (error) { console.error('[WARNING] Reflexion execution failed:', error); enhancedPrompt = implicitResult.analysisPrompt; } } else { enhancedPrompt = implicitResult.analysisPrompt; } // Execute the analysis with AI if enabled, otherwise return prompt const executionResult = await executeADRSuggestionPrompt(enhancedPrompt, implicitResult.instructions, { temperature: 0.1, maxTokens: 4000, }); if (executionResult.isAIGenerated) { // AI execution successful - return actual analysis results return formatMCPResponse({ ...executionResult, content: `# ADR Suggestions: AI Analysis Results ## Enhancement Features - **Knowledge Generation**: ${knowledgeEnhancement ? '✅ Enabled' : '❌ Disabled'} - **Reflexion Learning**: ${learningEnabled ? '✅ Enabled' : '❌ Disabled'} - **Enhanced Mode**: ${enhancedMode ? '✅ Enabled' : '❌ Disabled'} ## Project Analysis - **Project Path**: ${projectPath} - **Existing ADRs**: ${existingAdrs?.length || 0} ADRs provided - **Analysis Type**: Comprehensive (AI-driven with enhancements) ${knowledgeContext} ${reflexionContext} ## AI Analysis Results ${executionResult.content} ## Next Steps Based on the analysis above: 1. **Review Suggested ADRs**: Examine each suggested architectural decision 2. **Prioritize by Impact**: Focus on high-impact decisions first 3. **Generate ADRs**: Use the \`generate_adr_from_decision\` tool for priority decisions 4. **Implement Changes**: Plan and execute the architectural changes 5. **Update Documentation**: Keep ADRs current as decisions evolve ## Integration Workflow For each suggested decision, use: \`\`\`json { "tool": "generate_adr_from_decision", "args": { "decisionData": { "title": "Decision title from analysis", "context": "Context from analysis", "decision": "Decision description", "consequences": "Consequences from analysis" } } } \`\`\` `, }); } else { // Fallback to prompt-only mode return { content: [ { type: 'text', text: `# ADR Suggestions: Enhanced Comprehensive Analysis This enhanced analysis uses advanced prompting techniques to provide superior ADR suggestions. ## Enhancement Features - **Knowledge Generation**: ${knowledgeEnhancement ? '✅ Enabled' : '❌ Disabled'} - **Reflexion Learning**: ${learningEnabled ? '✅ Enabled' : '❌ Disabled'} - **Enhanced Mode**: ${enhancedMode ? '✅ Enabled' : '❌ Disabled'} ## Project Analysis - **Project Path**: ${projectPath} - **Existing ADRs**: ${existingAdrs?.length || 0} ADRs provided - **Analysis Type**: Comprehensive (AI-driven with enhancements) ${knowledgeContext} ${reflexionContext} ## AI Analysis Instructions ${implicitResult.instructions} ## Enhanced AI Analysis Prompt ${enhancedPrompt} ## Recommended Workflow ### 1. **Initial Analysis** Submit the prompt above to get comprehensive decision detection ### 2. **Priority Review** - Focus on **high** and **critical** priority decisions first - Consider **risk level** and **complexity** for planning - Group related decisions using suggested clusters ### 3. **ADR Generation** Use \`generate_adr_from_decision\` tool for each prioritized decision ### 4. **Integration** - Save generated ADRs to your ADR directory - Update ADR index/catalog - Schedule team review sessions - Plan implementation tasks `, }, ], }; } } default: throw new McpAdrError(`Unknown analysis type: ${analysisType}`, 'INVALID_INPUT'); } } catch (error) { throw new McpAdrError(`Failed to suggest ADRs: ${error instanceof Error ? error.message : String(error)}`, 'SUGGESTION_ERROR'); } } /** * Generate ADR from decision data */ export async function generateAdrFromDecision(args) { const { decisionData, templateFormat = 'nygard', existingAdrs = [], adrDirectory = 'docs/adrs', } = args; try { const { generateAdrFromDecision, generateNextAdrNumber, suggestAdrFilename } = await import('../utils/adr-suggestions.js'); if (!decisionData.title || !decisionData.context || !decisionData.decision || !decisionData.consequences) { throw new McpAdrError('Decision data must include title, context, decision, and consequences', 'INVALID_INPUT'); } const result = await generateAdrFromDecision(decisionData, templateFormat, existingAdrs); // Generate suggested metadata const adrNumber = generateNextAdrNumber(existingAdrs); const filename = suggestAdrFilename(decisionData.title, adrNumber); const fullPath = `${adrDirectory}/${filename}`; // Execute ADR generation with AI if enabled const { executeADRGenerationPrompt } = await import('../utils/prompt-execution.js'); const executionResult = await executeADRGenerationPrompt(result.generationPrompt, result.instructions, { temperature: 0.1, maxTokens: 6000, responseFormat: 'text', }); if (executionResult.isAIGenerated) { // AI execution successful - return actual ADR content return formatMCPResponse({ ...executionResult, content: `# Generated ADR: ${decisionData.title} ## ADR Metadata - **ADR Number**: ${adrNumber} - **Filename**: ${filename} - **Full Path**: ${fullPath} - **Template Format**: ${templateFormat.toUpperCase()} ## Generated ADR Content ${executionResult.content} ## File Creation Instructions To save this ADR to your project: 1. **Create the ADR directory** (if it doesn't exist): \`\`\`bash mkdir -p ${adrDirectory} \`\`\` 2. **Save the ADR content** to the file: \`\`\`bash cat > "${fullPath}" << 'EOF' ${executionResult.content} EOF \`\`\` 3. **Verify the file** was created successfully: \`\`\`bash ls -la "${fullPath}" \`\`\` ## Next Steps 1. **Review the generated ADR** for accuracy and completeness 2. **Save the file** using the instructions above 3. **Update your ADR index** or catalog 4. **Share with stakeholders** for review and approval 5. **Plan implementation** of the architectural decision ## Quality Checklist - ✅ **Title** is clear and descriptive - ✅ **Context** explains the problem and constraints - ✅ **Decision** is specific and actionable - ✅ **Consequences** cover both positive and negative impacts - ✅ **Format** follows ${templateFormat.toUpperCase()} template standards - ✅ **Numbering** is sequential (${adrNumber}) `, }); } else { // Fallback to prompt-only mode const { ensureDirectory, writeFile } = await import('../utils/file-system.js'); const ensureDirPrompt = await ensureDirectory(adrDirectory); const writeFilePrompt = await writeFile(fullPath, '[ADR_CONTENT_PLACEHOLDER]'); return { content: [ { type: 'text', text: `# ADR Generation: ${decisionData.title} ${result.instructions} ## Suggested ADR Metadata - **ADR Number**: ${adrNumber} - **Filename**: ${filename} - **Full Path**: ${fullPath} - **Template Format**: ${templateFormat.toUpperCase()} ## Step 1: Create ADR Directory ${ensureDirPrompt.prompt} ## Step 2: Generate ADR Content ${result.generationPrompt} ## Step 3: Save ADR to File After generating the ADR content from Step 2, create the ADR file: ${writeFilePrompt.prompt} **Important**: Replace \`[ADR_CONTENT_PLACEHOLDER]\` with the actual generated ADR content from Step 2. `, }, ], }; } } catch (error) { throw new McpAdrError(`Failed to generate ADR: ${error instanceof Error ? error.message : String(error)}`, 'GENERATION_ERROR'); } } /** * Discover existing ADRs in the project using internal file system tools * * IMPORTANT FOR AI ASSISTANTS: This tool performs TWO critical functions: * 1. PRIMARY: Scans the specified ADR directory and catalogs all existing ADRs * 2. SECONDARY: ALWAYS initializes the complete .mcp-adr-cache infrastructure * * The cache initialization happens REGARDLESS of whether ADRs are found, making * this the recommended FIRST STEP for any project workflow. All other MCP tools * depend on this cache infrastructure to function properly. * * Cache files created: * - .mcp-adr-cache/todo-data.json (TODO management backend) * - .mcp-adr-cache/project-health-scores.json (project health metrics) * - .mcp-adr-cache/knowledge-graph-snapshots.json (architectural knowledge) * - .mcp-adr-cache/todo-sync-state.json (synchronization state) * * Therefore, always run this tool first, even for projects without existing ADRs. */ export async function discoverExistingAdrs(args) { const { adrDirectory = 'docs/adrs', includeContent = false, projectPath = process.cwd() } = args; try { // INITIALIZE COMPLETE CACHE INFRASTRUCTURE (since this is typically the first command) console.log('🚀 Initializing complete cache infrastructure...'); // 1. Initialize TodoJsonManager (creates todo-data.json and cache directory) const { TodoJsonManager } = await import('../utils/todo-json-manager.js'); const todoManager = new TodoJsonManager(projectPath); await todoManager.loadTodoData(); // Creates cache dir and todo-data.json console.log('✅ Initialized todo-data.json and cache directory'); // 2. Initialize ProjectHealthScoring (creates project-health-scores.json) const { ProjectHealthScoring } = await import('../utils/project-health-scoring.js'); const healthScoring = new ProjectHealthScoring(projectPath); await healthScoring.getProjectHealthScore(); // Creates project-health-scores.json console.log('✅ Initialized project-health-scores.json'); // 3. Initialize KnowledgeGraphManager (creates knowledge-graph-snapshots.json and todo-sync-state.json) // Set PROJECT_PATH temporarily for proper initialization const originalConfig = process.env['PROJECT_PATH']; process.env['PROJECT_PATH'] = projectPath; const { KnowledgeGraphManager } = await import('../utils/knowledge-graph-manager.js'); const kgManager = new KnowledgeGraphManager(); await kgManager.loadKnowledgeGraph(); // Creates knowledge-graph-snapshots.json and todo-sync-state.json console.log('✅ Initialized knowledge-graph-snapshots.json and todo-sync-state.json'); // Restore original config if (originalConfig !== undefined) { process.env['PROJECT_PATH'] = originalConfig; } else { delete process.env['PROJECT_PATH']; } console.log('🎯 Complete cache infrastructure ready!'); // Use the new ADR discovery utility const { discoverAdrsInDirectory } = await import('../utils/adr-discovery.js'); const discoveryResult = await discoverAdrsInDirectory(adrDirectory, includeContent, projectPath); // Format the results for MCP response return { content: [ { type: 'text', text: `# 🎯 Complete ADR Discovery & Cache Infrastructure Initialized ## Cache Infrastructure Status ✅ **todo-data.json** - JSON-first TODO system initialized ✅ **project-health-scores.json** - Multi-component project health scoring ✅ **knowledge-graph-snapshots.json** - Knowledge graph system & intent tracking ✅ **todo-sync-state.json** - TODO synchronization state ✅ **Cache Directory** - Complete infrastructure ready at \`.mcp-adr-cache/\` ## ADR Discovery Results ### Discovery Summary - **Directory**: ${discoveryResult.directory} - **Total ADRs Found**: ${discoveryResult.totalAdrs} - **Include Content**: ${includeContent ? 'Yes' : 'No (metadata only)'} ## Discovered ADRs ${discoveryResult.adrs.length > 0 ? discoveryResult.adrs.map(adr => ` ### ${adr.title} - **File**: ${adr.filename} - **Status**: ${adr.status} - **Date**: ${adr.date || 'Not specified'} - **Path**: ${adr.path} ${adr.metadata?.number ? `- **Number**: ${adr.metadata.number}` : ''} ${adr.metadata?.category ? `- **Category**: ${adr.metadata.category}` : ''} ${adr.metadata?.tags?.length ? `- **Tags**: ${adr.metadata.tags.join(', ')}` : ''} ${includeContent && adr.content ? ` #### Content Preview \`\`\`markdown ${adr.content.slice(0, 500)}${adr.content.length > 500 ? '...' : ''} \`\`\` ` : ''} `).join('\n') : 'No ADRs found in the specified directory.'} ## Summary Statistics ### By Status ${Object.entries(discoveryResult.summary.byStatus) .map(([status, count]) => `- **${status}**: ${count}`) .join('\n') || 'No status information available'} ### By Category ${Object.entries(discoveryResult.summary.byCategory) .map(([category, count]) => `- **${category}**: ${count}`) .join('\n') || 'No category information available'} ## Recommendations ${discoveryResult.recommendations.map(rec => `- ${rec}`).join('\n')} ## Next Steps Based on the discovered ADRs, you can: 1. **Analyze for Missing Decisions**: Use the \`suggest_adrs\` tool with the discovered ADR titles 2. **Generate Implementation TODOs**: Use the \`generate_adr_todo\` tool 3. **Create New ADRs**: Use the \`generate_adr_from_decision\` tool for new decisions ### Example Commands To suggest new ADRs based on discovered ones: \`\`\`json { "tool": "suggest_adrs", "args": { "existingAdrs": ${JSON.stringify(discoveryResult.adrs.map(adr => adr.title))}, "analysisType": "comprehensive" } } \`\`\` To generate a todo list from discovered ADRs: \`\`\`json { "tool": "generate_adr_todo", "args": { "adrDirectory": "${adrDirectory}", "scope": "all" } } \`\`\` ## Raw Discovery Data For programmatic use, the raw discovery data is: \`\`\`json ${JSON.stringify(discoveryResult, null, 2)} \`\`\` ` } ] }; } catch (error) { throw new McpAdrError(`Failed to discover ADRs: ${error instanceof Error ? error.message : String(error)}`, 'DISCOVERY_ERROR'); } } //# sourceMappingURL=adr-suggestion-tool.js.map