mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
738 lines (642 loc) • 30 kB
JavaScript
/**
* 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