initrepo-claude-agent
Version:
Autonomous AI agent for Claude Code - Build InitRepo projects with minimal human intervention
442 lines (368 loc) β’ 14.4 kB
JavaScript
/**
* MCP Context Manager for Claude Code
*
* Provides intelligent context retrieval and management using direct MCP server integration.
* This replaces the simple agent wrapper with sophisticated document intelligence.
*/
import { spawn } from 'child_process';
import { writeFileSync, readFileSync, existsSync } from 'fs';
import path from 'path';
class MCPContextManager {
constructor() {
this.mcpServer = null;
this.workingDirectory = process.cwd();
this.isConnected = false;
this.messageId = 0;
}
async initialize() {
try {
// Find MCP server path dynamically
const mcpServerPath = this.findMcpServer();
console.log(`π Connecting to MCP server at: ${mcpServerPath.path}`);
// Start MCP server
this.mcpServer = spawn('node', [mcpServerPath.path], {
stdio: ['pipe', 'pipe', 'pipe'],
cwd: mcpServerPath.cwd,
env: {
...process.env,
'PROJECT_ROOT': this.workingDirectory
}
});
await this.waitForServerReady();
this.isConnected = true;
console.log('β
MCP Context Manager initialized');
return true;
} catch (error) {
console.error('β Failed to initialize MCP Context Manager:', error.message);
return false;
}
}
findMcpServer() {
const possiblePaths = [
// Try current project's node_modules first
path.join(this.workingDirectory, 'node_modules', 'initrepo-mcp', 'dist', 'packages', 'mcp-server', 'src', 'index.js'),
// Try global installation
'/usr/local/lib/node_modules/initrepo-mcp/dist/packages/mcp-server/src/index.js',
// Try adjacent directory (development setup)
path.join(path.dirname(this.workingDirectory), 'initrepo-mcp', 'dist', 'packages', 'mcp-server', 'src', 'index.js'),
// Try standard location
'/mnt/c/initrepo-mcp/dist/packages/mcp-server/src/index.js'
];
for (const serverPath of possiblePaths) {
if (existsSync(serverPath)) {
return {
path: serverPath,
cwd: path.dirname(path.dirname(path.dirname(path.dirname(serverPath))))
};
}
}
throw new Error(`MCP server not found. Tried:\n${possiblePaths.join('\n')}`);
}
async waitForServerReady() {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('MCP server startup timeout'));
}, 10000);
this.mcpServer.stderr.on('data', (data) => {
const output = data.toString();
if (output.includes('InitRepo MCP Server running on stdio')) {
clearTimeout(timeout);
resolve();
}
});
this.mcpServer.on('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
}
async callMCPTool(toolName, parameters = {}) {
if (!this.isConnected) {
throw new Error('MCP server not connected');
}
return new Promise((resolve, reject) => {
const messageId = ++this.messageId;
const request = {
jsonrpc: '2.0',
id: messageId,
method: 'tools/call',
params: {
name: toolName,
arguments: parameters
}
};
// Set up response handler
const responseHandler = (data) => {
try {
const response = JSON.parse(data.toString());
if (response.id === messageId) {
this.mcpServer.stdout.off('data', responseHandler);
if (response.error) {
reject(new Error(response.error.message));
} else {
resolve(response.result);
}
}
} catch (error) {
// Ignore parse errors, wait for complete message
}
};
this.mcpServer.stdout.on('data', responseHandler);
// Send request
this.mcpServer.stdin.write(JSON.stringify(request) + '\n');
// Timeout after 30 seconds
setTimeout(() => {
this.mcpServer.stdout.off('data', responseHandler);
reject(new Error(`Tool call timeout: ${toolName}`));
}, 30000);
});
}
async searchForContext(query, options = {}) {
console.log(`π Searching for context: "${query}"`);
try {
// First, search for related tasks and features
const searchResults = await this.findRelatedContent(query);
if (!searchResults || searchResults.length === 0) {
return {
found: false,
message: `No documentation found for "${query}". Ensure your project has InitRepo documentation in the docs/ folder.`,
suggestions: [
'Check if docs/ folder exists',
'Verify documentation follows InitRepo structure',
'Run initrepo init to set up project structure'
]
};
}
console.log(`π Found ${searchResults.length} related items`);
// Get detailed context for each match
const contexts = await this.assembleContexts(searchResults, options);
return {
found: true,
query,
matches: searchResults,
contexts,
totalItems: searchResults.length
};
} catch (error) {
return {
found: false,
error: error.message,
message: `Failed to search documentation: ${error.message}`
};
}
}
async findRelatedContent(query) {
// Search for actual task IDs in project documentation
try {
// Try to get project status first to ensure connection
const projectStatus = await this.callMCPTool('getProjectStatus');
// Search for task IDs in actual documentation files
const taskIds = await this.searchDocumentationForTasks();
if (taskIds.length === 0) {
console.log(' β οΈ No task IDs found in documentation files');
return [];
}
console.log(` π Found ${taskIds.length} task IDs in documentation`);
// Filter tasks based on query if provided
if (query && query.trim()) {
const queryLower = query.toLowerCase();
// Return all tasks for general queries, or filter by context
if (queryLower.includes('email')) {
return taskIds.filter(id => id.includes('025') || id.includes('026') || id.includes('027'));
} else if (queryLower.includes('auth')) {
return taskIds.filter(id => id.includes('001') || id.includes('002'));
} else if (queryLower.includes('user')) {
return taskIds.filter(id => id.includes('003') || id.includes('004'));
}
}
// Return all found task IDs
return taskIds;
} catch (error) {
console.error('Error finding related content:', error.message);
return [];
}
}
async searchDocumentationForTasks() {
try {
const documentFiles = [
'README.md',
'AUTONOMOUS_WORKFLOW.md',
'CLAUDE_PROJECT_BUILDER_AGENT.md',
'CLAUDE_AGENT_USAGE_GUIDE.md',
'docs/README.md',
'docs/business_analysis.md',
'docs/prd.md',
'docs/user_stories.md',
'docs/technical_architecture.md'
];
const taskIds = new Set();
const taskPatterns = [
/T-\d{3}/g, // T-001 format
/F-\d{3}/g, // F-001 format
/US-\d{3}/g // US-001 format
];
for (const filename of documentFiles) {
const filePath = path.join(this.workingDirectory, filename);
if (existsSync(filePath)) {
try {
const content = readFileSync(filePath, 'utf8');
// Search for all task patterns
taskPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches) {
matches.forEach(match => taskIds.add(match));
}
});
} catch (error) {
console.log(` β οΈ Could not read ${filename}: ${error.message}`);
}
}
}
return Array.from(taskIds).sort();
} catch (error) {
console.error('Error searching documentation for tasks:', error.message);
return [];
}
}
async assembleContexts(itemIds, options = {}) {
const contexts = [];
for (const id of itemIds.slice(0, 5)) { // Limit to first 5 for performance
try {
console.log(`π Getting context for ${id}`);
const context = await this.callMCPTool('getContextById', { id });
if (context && context.content) {
// Get implementation brief if this is a task
let implementationBrief = null;
if (id.startsWith('T-') && options.includeImplementation !== false) {
try {
implementationBrief = await this.callMCPTool('generateSmartImplementationBrief', {
id,
includeTestingStrategy: true
});
} catch (error) {
console.log(`β οΈ Could not get implementation brief for ${id}`);
}
}
contexts.push({
id,
context,
implementationBrief,
timestamp: new Date().toISOString()
});
}
} catch (error) {
console.error(`Error getting context for ${id}:`, error.message);
}
}
return contexts;
}
async analyzeDependencies(taskIds) {
try {
console.log('π Analyzing task dependencies...');
const dependencies = await this.callMCPTool('analyzeTaskDependencies', {
taskIds
});
return dependencies;
} catch (error) {
console.error('Error analyzing dependencies:', error.message);
return null;
}
}
async getImplementationPlan(taskIds) {
try {
const dependencies = await this.analyzeDependencies(taskIds);
// Get next critical tasks
const criticalTasks = await this.callMCPTool('identifyNextCriticalTasks');
return {
dependencies,
criticalTasks,
recommendedOrder: dependencies?.implementationOrder || taskIds,
estimatedEffort: dependencies?.estimatedEffort || 'Unknown'
};
} catch (error) {
console.error('Error creating implementation plan:', error.message);
return null;
}
}
formatIntelligentResponse(searchResult, userIntent = 'general') {
if (!searchResult.found) {
return {
success: false,
message: searchResult.message,
suggestions: searchResult.suggestions || []
};
}
const { contexts, matches } = searchResult;
let response = `## π Found ${matches.length} Related Items\n\n`;
// Add context summaries
contexts.forEach(({ id, context, implementationBrief }) => {
const contentSummary = typeof context.content === 'string' ?
context.content.split('\n')[0] :
(context.content?.description || context.content?.title || 'No description');
response += `### ${id}: ${contentSummary}\n`;
response += `**Source**: ${context.source.filePath}\n`;
if (implementationBrief) {
response += `**Implementation**: ${implementationBrief.technicalApproach}\n`;
}
response += '\n';
});
// Add action suggestions based on intent
response += `## π― Recommended Actions\n\n`;
if (userIntent.includes('implement') || userIntent.includes('build')) {
response += `- π¨ **Start Implementation**: Begin with ${matches[0]}\n`;
response += `- π **Show Details**: Get full specifications\n`;
response += `- πΊοΈ **Create Plan**: Analyze dependencies and create roadmap\n`;
response += `- π§ͺ **Generate Tests**: Create test cases for implementation\n`;
} else if (userIntent.includes('understand') || userIntent.includes('explain')) {
response += `- π **Show Context**: Display full documentation\n`;
response += `- π **Map Relationships**: Show how items connect\n`;
response += `- π **Explain Dependencies**: Understand prerequisites\n`;
} else {
response += `- π **Explore Details**: Get comprehensive information\n`;
response += `- π¨ **Start Building**: Begin implementation\n`;
response += `- πΊοΈ **Plan Approach**: Create implementation strategy\n`;
}
return {
success: true,
message: response,
data: {
matches,
contexts,
actionSuggestions: this.getActionSuggestions(userIntent)
}
};
}
getActionSuggestions(intent) {
const suggestions = {
implementation: [
{ command: '/initrepo-implement', label: 'π¨ Start implementing', description: 'Begin systematic implementation' },
{ command: '/initrepo-plan', label: 'πΊοΈ Create plan', description: 'Analyze dependencies and create roadmap' },
{ command: '/initrepo-test', label: 'π§ͺ Generate tests', description: 'Create comprehensive test cases' }
],
understanding: [
{ command: '/initrepo-context', label: 'π Show context', description: 'Display full documentation context' },
{ command: '/initrepo-relationships', label: 'π Map relationships', description: 'Show item relationships' },
{ command: '/initrepo-explain', label: 'π Explain dependencies', description: 'Understand prerequisites' }
],
general: [
{ command: '/initrepo-explore', label: 'π Explore', description: 'Get comprehensive information' },
{ command: '/initrepo-build', label: 'β‘ Full build', description: 'Run complete systematic build' }
]
};
if (intent.includes('implement') || intent.includes('build')) {
return suggestions.implementation;
} else if (intent.includes('understand') || intent.includes('explain')) {
return suggestions.understanding;
} else {
return suggestions.general;
}
}
async cleanup() {
if (this.mcpServer) {
this.mcpServer.kill();
this.isConnected = false;
}
}
}
export default MCPContextManager;