ultimate-mcp-server
Version:
The definitive all-in-one Model Context Protocol server for AI-assisted coding across 30+ platforms
878 lines • 32.5 kB
JavaScript
/**
* Autonomous Exploration Engine
* Self-guided codebase exploration and task execution
*/
import { Logger } from '../utils/logger.js';
import { CodeContextManager } from '../code-context/context-manager.js';
import * as fs from 'fs/promises';
import * as path from 'path';
const logger = new Logger('ExplorationEngine');
export class ExplorationEngine {
workingMemory;
contextManager;
strategies;
_currentTask = null;
constructor() {
this.contextManager = new CodeContextManager();
this.strategies = this.initializeStrategies();
this.workingMemory = this.initializeWorkingMemory();
}
/**
* Execute an autonomous task
*/
async executeTask(task) {
logger.info(`Starting autonomous task: ${task.description}`);
this._currentTask = task;
const startTime = Date.now();
try {
// Initialize task context
this.workingMemory.taskContext = {
id: task.id,
description: task.description,
goals: this.extractGoals(task),
constraints: this.extractConstraints(task),
currentPhase: 'exploration',
progress: 0,
startTime: new Date()
};
// Select strategy
const strategy = task.strategy || this.selectStrategy(task);
// Execute exploration phases
for (const phase of strategy.phases) {
await this.executePhase(phase);
this.updateProgress();
}
// Generate result
const result = {
taskId: task.id,
success: true,
workingMemory: this.workingMemory,
summary: this.generateSummary(),
keyFindings: this.extractKeyFindings(),
recommendations: this.generateRecommendations(),
duration: Date.now() - startTime
};
logger.info(`Task completed in ${result.duration}ms`);
return result;
}
catch (error) {
logger.error('Task execution failed:', error);
return {
taskId: task.id,
success: false,
workingMemory: this.workingMemory,
summary: `Task failed: ${error}`,
keyFindings: [],
recommendations: [],
duration: Date.now() - startTime
};
}
finally {
this._currentTask = null;
}
}
/**
* Explore a codebase autonomously
*/
async exploreCodebase(rootPath) {
logger.info(`Exploring codebase at: ${rootPath}`);
// Build initial structure
const structure = await this.buildProjectStructure(rootPath);
this.workingMemory.projectStructure = structure;
// Identify key entry points
await this.identifyEntryPoints(structure);
// Explore important files
await this.exploreKeyFiles();
// Analyze patterns and conventions
await this.analyzePatterns();
return structure;
}
/**
* Navigate to a specific target with reasoning
*/
async navigateTo(target, reason) {
const step = {
id: this.generateId(),
action: this.determineAction(target),
target,
reason,
timestamp: new Date(),
findings: []
};
try {
// Execute navigation
switch (step.action) {
case 'read_file':
const analysis = await this.analyzeFile(target);
step.findings = this.extractFindings(analysis);
break;
case 'explore_directory':
const files = await this.exploreDirectory(target);
step.findings = files.map(f => `Found: ${f}`);
break;
case 'search_pattern':
const matches = await this.searchPattern(target);
step.findings = matches;
break;
case 'follow_import':
const resolved = await this.resolveImport(target);
step.findings = resolved ? [`Resolved to: ${resolved}`] : ['Could not resolve'];
break;
}
// Record step
this.workingMemory.navigationHistory.push(step);
// Determine next steps
step.nextSteps = await this.suggestNextSteps(step);
}
catch (error) {
logger.error(`Navigation failed: ${error}`);
step.findings = [`Error: ${error}`];
}
return step;
}
/**
* Make an autonomous decision
*/
async makeDecision(question, options) {
const decision = {
id: this.generateId(),
question,
options,
impact: this.assessImpact(question),
timestamp: new Date()
};
// Autonomous decision making
const selected = this.selectBestOption(options);
decision.selected = selected.id;
decision.rationale = this.generateRationale(selected, options);
// Record decision
this.workingMemory.decisions.push(decision);
return decision;
}
/**
* Plan navigation for a specific goal
*/
async planNavigation(goal) {
const steps = [];
// Analyze goal
const targets = await this.identifyTargets(goal);
// Plan steps
let order = 0;
for (const target of targets) {
const action = this.determineAction(target);
steps.push({
order: order++,
action,
target,
purpose: `Explore ${target} for ${goal}`,
dependencies: [],
optional: false
});
}
// Add follow-up steps
steps.push({
order: order++,
action: 'analyze_code',
target: 'collected_data',
purpose: 'Synthesize findings',
dependencies: steps.map((_s, i) => i),
optional: false
});
return {
steps,
estimatedDuration: steps.length * 500, // Rough estimate
requiredCapabilities: ['file_reading', 'code_analysis'],
risks: ['Large codebase may exceed time limit']
};
}
/**
* Generate insights from exploration
*/
async generateInsight(type, description, filePath) {
const insight = {
id: this.generateId(),
type,
description,
relevance: this.calculateRelevance(type, description),
timestamp: new Date(),
filePath
};
this.workingMemory.keyInsights.push(insight);
logger.info(`Generated insight: ${description}`);
return insight;
}
// Private helper methods
initializeStrategies() {
const strategies = new Map();
// Comprehensive exploration strategy
strategies.set('comprehensive', {
name: 'Comprehensive Exploration',
description: 'Thorough analysis of entire codebase',
phases: [
{
name: 'Structure Discovery',
goals: ['Map directory structure', 'Identify key files'],
actions: ['explore_directory', 'read_file'],
completionCriteria: ['All directories explored'],
maxDuration: 30000
},
{
name: 'Code Analysis',
goals: ['Analyze main components', 'Trace dependencies'],
actions: ['analyze_code', 'follow_import'],
completionCriteria: ['Key files analyzed'],
maxDuration: 60000
},
{
name: 'Pattern Recognition',
goals: ['Identify patterns', 'Find conventions'],
actions: ['search_pattern', 'analyze_code'],
completionCriteria: ['Patterns documented'],
maxDuration: 30000
}
],
heuristics: [
{
name: 'Prioritize entry points',
condition: 'File is main/index',
action: 'Analyze first',
priority: 10
},
{
name: 'Follow imports',
condition: 'Unknown import found',
action: 'Resolve and explore',
priority: 8
}
]
});
// Focused exploration strategy
strategies.set('focused', {
name: 'Focused Exploration',
description: 'Target specific areas of interest',
phases: [
{
name: 'Target Location',
goals: ['Find target files', 'Map local structure'],
actions: ['search_pattern', 'explore_directory'],
completionCriteria: ['Target found'],
maxDuration: 15000
},
{
name: 'Deep Analysis',
goals: ['Understand implementation', 'Trace usage'],
actions: ['analyze_code', 'check_references'],
completionCriteria: ['Implementation understood'],
maxDuration: 30000
}
],
heuristics: [
{
name: 'Skip unrelated',
condition: 'File not related to target',
action: 'Skip exploration',
priority: 10
}
]
});
return strategies;
}
initializeWorkingMemory() {
return {
projectStructure: {
rootPath: '',
directories: { name: 'root', path: '', type: 'directory' },
fileCount: 0,
totalSize: 0,
languages: {},
patterns: []
},
visitedFiles: new Set(),
keyInsights: [],
taskContext: {
id: '',
description: '',
goals: [],
constraints: [],
currentPhase: 'exploration',
progress: 0,
startTime: new Date()
},
navigationHistory: [],
decisions: []
};
}
async buildProjectStructure(rootPath) {
const structure = {
rootPath,
directories: await this.buildDirectoryTree(rootPath),
fileCount: 0,
totalSize: 0,
languages: {},
patterns: []
};
// Calculate statistics
this.calculateStructureStats(structure.directories, structure);
// Detect patterns
structure.patterns = await this.detectProjectPatterns(structure);
return structure;
}
async buildDirectoryTree(dirPath) {
const name = path.basename(dirPath);
const node = {
name,
path: dirPath,
type: 'directory',
children: []
};
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
// Skip common ignore patterns
if (this.shouldIgnore(entry.name))
continue;
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
const childNode = await this.buildDirectoryTree(fullPath);
node.children.push(childNode);
}
else {
const stats = await fs.stat(fullPath);
const fileNode = {
name: entry.name,
path: fullPath,
type: 'file',
size: stats.size,
language: this.detectLanguage(entry.name),
lastModified: stats.mtime
};
node.children.push(fileNode);
}
}
}
catch (error) {
logger.error(`Failed to read directory ${dirPath}:`, error);
}
return node;
}
shouldIgnore(name) {
const ignorePatterns = [
'node_modules', '.git', 'dist', 'build', '.cache',
'.next', '.nuxt', 'coverage', '.pytest_cache'
];
return ignorePatterns.includes(name) || name.startsWith('.');
}
detectLanguage(fileName) {
const ext = path.extname(fileName).toLowerCase();
const languageMap = {
'.js': 'javascript',
'.ts': 'typescript',
'.jsx': 'javascript',
'.tsx': 'typescript',
'.py': 'python',
'.java': 'java',
'.go': 'go',
'.rs': 'rust',
'.cpp': 'cpp',
'.c': 'c',
'.cs': 'csharp',
'.rb': 'ruby',
'.php': 'php',
'.swift': 'swift',
'.kt': 'kotlin',
'.scala': 'scala',
'.r': 'r',
'.m': 'objective-c',
'.h': 'c/cpp/objective-c',
'.json': 'json',
'.xml': 'xml',
'.yaml': 'yaml',
'.yml': 'yaml',
'.md': 'markdown',
'.sql': 'sql',
'.sh': 'shell',
'.bat': 'batch',
'.ps1': 'powershell'
};
return languageMap[ext] || 'unknown';
}
calculateStructureStats(node, structure) {
if (node.type === 'file') {
structure.fileCount++;
structure.totalSize += node.size || 0;
if (node.language && node.language !== 'unknown') {
structure.languages[node.language] =
(structure.languages[node.language] || 0) + 1;
}
}
else if (node.children) {
for (const child of node.children) {
this.calculateStructureStats(child, structure);
}
}
}
async detectProjectPatterns(structure) {
const patterns = [];
// Check for common patterns
const hasPackageJson = await this.fileExists(path.join(structure.rootPath, 'package.json'));
const hasTsConfig = await this.fileExists(path.join(structure.rootPath, 'tsconfig.json'));
const hasRequirements = await this.fileExists(path.join(structure.rootPath, 'requirements.txt'));
if (hasPackageJson) {
patterns.push({
name: 'Node.js Project',
type: 'framework',
evidence: ['package.json exists'],
confidence: 0.9,
implications: ['Use npm/yarn commands', 'Check node_modules']
});
}
if (hasTsConfig) {
patterns.push({
name: 'TypeScript Project',
type: 'framework',
evidence: ['tsconfig.json exists'],
confidence: 0.95,
implications: ['Type-safe code', 'Compilation required']
});
}
if (hasRequirements) {
patterns.push({
name: 'Python Project',
type: 'framework',
evidence: ['requirements.txt exists'],
confidence: 0.9,
implications: ['Use pip for dependencies', 'Check virtual env']
});
}
return patterns;
}
async fileExists(filePath) {
try {
await fs.access(filePath);
return true;
}
catch {
return false;
}
}
async identifyEntryPoints(structure) {
const entryPoints = [
'index.js', 'index.ts', 'main.js', 'main.ts', 'app.js', 'app.ts',
'server.js', 'server.ts', 'index.py', 'main.py', 'app.py',
'__main__.py', 'cli.js', 'cli.ts', 'cmd/main.go'
];
for (const entry of entryPoints) {
const found = this.findFileInStructure(structure.directories, entry);
if (found) {
await this.generateInsight('entry-point', `Found entry point: ${entry}`, found);
}
}
}
findFileInStructure(node, fileName) {
if (node.type === 'file' && node.name === fileName) {
return node.path;
}
if (node.children) {
for (const child of node.children) {
const found = this.findFileInStructure(child, fileName);
if (found)
return found;
}
}
return null;
}
async exploreKeyFiles() {
const insights = this.workingMemory.keyInsights
.filter(i => i.type === 'entry-point');
for (const insight of insights) {
if (insight.filePath) {
await this.analyzeFile(insight.filePath);
}
}
}
async analyzeFile(filePath) {
const content = await fs.readFile(filePath, 'utf-8');
const language = this.detectLanguage(filePath);
const analysis = {
filePath,
language,
purpose: 'Unknown',
exports: [],
imports: [],
dependencies: [],
complexity: this.calculateComplexity(content),
issues: [],
suggestions: []
};
// Extract file context
const fileContext = await this.contextManager.getFileContext(filePath);
if (fileContext) {
analysis.imports = fileContext.imports.map(i => i.source);
analysis.exports = fileContext.exports.map(e => e.name);
// Determine purpose
if (fileContext.classes.length > 0) {
analysis.purpose = `Defines ${fileContext.classes.length} classes`;
}
else if (fileContext.functions.length > 0) {
analysis.purpose = `Contains ${fileContext.functions.length} functions`;
}
}
// Mark as visited
this.workingMemory.visitedFiles.add(filePath);
return analysis;
}
calculateComplexity(content) {
// Simple complexity calculation
const lines = content.split('\n').length;
const conditions = (content.match(/if\s*\(|while\s*\(|for\s*\(/g) || []).length;
const functions = (content.match(/function\s+\w+|=>|def\s+\w+/g) || []).length;
return Math.round(Math.log(lines) + conditions * 2 + functions * 3);
}
async analyzePatterns() {
// Analyze code patterns across visited files
const patterns = new Map();
for (const filePath of this.workingMemory.visitedFiles) {
try {
const content = await fs.readFile(filePath, 'utf-8');
// Check for common patterns
if (content.includes('async') && content.includes('await')) {
patterns.set('async/await', (patterns.get('async/await') || 0) + 1);
}
if (content.includes('class') && content.includes('extends')) {
patterns.set('inheritance', (patterns.get('inheritance') || 0) + 1);
}
if (content.includes('import') || content.includes('require')) {
patterns.set('modular', (patterns.get('modular') || 0) + 1);
}
}
catch (error) {
// Skip files that can't be read
}
}
// Generate insights from patterns
for (const [pattern, count] of patterns) {
if (count > 3) {
await this.generateInsight('pattern', `Common pattern detected: ${pattern} (found in ${count} files)`);
}
}
}
async exploreDirectory(dirPath) {
const files = [];
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (!this.shouldIgnore(entry.name)) {
const fullPath = path.join(dirPath, entry.name);
files.push(fullPath);
if (entry.isFile()) {
this.workingMemory.visitedFiles.add(fullPath);
}
}
}
}
catch (error) {
logger.error(`Failed to explore directory ${dirPath}:`, error);
}
return files;
}
async searchPattern(pattern) {
const matches = [];
for (const filePath of this.workingMemory.visitedFiles) {
try {
const content = await fs.readFile(filePath, 'utf-8');
if (content.includes(pattern)) {
matches.push(`${filePath}: Found pattern "${pattern}"`);
}
}
catch (error) {
// Skip unreadable files
}
}
return matches;
}
async resolveImport(importPath) {
// Simple import resolution
const basePath = this.workingMemory.projectStructure.rootPath;
const possiblePaths = [
path.join(basePath, importPath),
path.join(basePath, importPath + '.js'),
path.join(basePath, importPath + '.ts'),
path.join(basePath, 'src', importPath),
path.join(basePath, 'src', importPath + '.js'),
path.join(basePath, 'src', importPath + '.ts')
];
for (const possiblePath of possiblePaths) {
if (await this.fileExists(possiblePath)) {
return possiblePath;
}
}
return null;
}
extractFindings(analysis) {
const findings = [];
findings.push(`Purpose: ${analysis.purpose}`);
findings.push(`Language: ${analysis.language}`);
findings.push(`Complexity: ${analysis.complexity}`);
if (analysis.exports.length > 0) {
findings.push(`Exports: ${analysis.exports.join(', ')}`);
}
if (analysis.imports.length > 0) {
findings.push(`Imports from: ${analysis.imports.slice(0, 3).join(', ')}`);
}
return findings;
}
async suggestNextSteps(step) {
const suggestions = [];
// Based on current action, suggest follow-ups
switch (step.action) {
case 'read_file':
// Suggest exploring imports
suggestions.push('Follow imports to understand dependencies');
suggestions.push('Look for test files');
break;
case 'explore_directory':
// Suggest analyzing key files
suggestions.push('Analyze main files in directory');
suggestions.push('Check for configuration files');
break;
case 'analyze_code':
// Suggest finding usage
suggestions.push('Find where this code is used');
suggestions.push('Check for related documentation');
break;
}
return suggestions;
}
extractGoals(task) {
const goals = [];
switch (task.type) {
case 'explore_codebase':
goals.push('Map project structure');
goals.push('Identify key components');
goals.push('Understand architecture');
break;
case 'find_implementation':
goals.push('Locate target implementation');
goals.push('Understand how it works');
goals.push('Trace usage patterns');
break;
case 'analyze_architecture':
goals.push('Identify architectural patterns');
goals.push('Map component relationships');
goals.push('Find design decisions');
break;
}
return goals;
}
extractConstraints(task) {
const constraints = [];
if (task.constraints?.maxFiles) {
constraints.push(`Limit to ${task.constraints.maxFiles} files`);
}
if (task.constraints?.timeLimit) {
constraints.push(`Complete within ${task.constraints.timeLimit}ms`);
}
if (task.constraints?.focusAreas) {
constraints.push(`Focus on: ${task.constraints.focusAreas.join(', ')}`);
}
return constraints;
}
selectStrategy(task) {
// Select appropriate strategy based on task type
if (task.type === 'explore_codebase' || task.type === 'analyze_architecture') {
return this.strategies.get('comprehensive');
}
else {
return this.strategies.get('focused');
}
}
async executePhase(phase) {
logger.info(`Executing phase: ${phase.name}`);
this.workingMemory.taskContext.currentPhase = phase.name.toLowerCase();
const startTime = Date.now();
// Execute actions in phase
for (const action of phase.actions) {
if (phase.maxDuration && Date.now() - startTime > phase.maxDuration) {
logger.warn(`Phase ${phase.name} exceeded time limit`);
break;
}
// Execute action based on current understanding
await this.executeAction(action);
}
}
async executeAction(action) {
switch (action) {
case 'explore_directory':
await this.exploreDirectory(this.workingMemory.projectStructure.rootPath);
break;
case 'analyze_code':
// Analyze unvisited key files
const keyFiles = this.workingMemory.keyInsights
.filter(i => i.filePath && !this.workingMemory.visitedFiles.has(i.filePath))
.map(i => i.filePath);
for (const file of keyFiles.slice(0, 5)) {
await this.analyzeFile(file);
}
break;
case 'search_pattern':
// Search for common patterns
const patterns = ['TODO', 'FIXME', 'HACK', 'BUG'];
for (const pattern of patterns) {
await this.searchPattern(pattern);
}
break;
}
}
updateProgress() {
const visited = this.workingMemory.visitedFiles.size;
const total = this.workingMemory.projectStructure.fileCount;
if (total > 0) {
this.workingMemory.taskContext.progress = Math.min(100, Math.round((visited / total) * 100));
}
}
generateSummary() {
const memory = this.workingMemory;
const insights = memory.keyInsights.length;
const files = memory.visitedFiles.size;
const decisions = memory.decisions.length;
return `Explored ${files} files, generated ${insights} insights, made ${decisions} decisions. ` +
`Project uses ${Object.keys(memory.projectStructure.languages).join(', ')}.`;
}
extractKeyFindings() {
return this.workingMemory.keyInsights
.sort((a, b) => b.relevance - a.relevance)
.slice(0, 10)
.map(i => i.description);
}
generateRecommendations() {
const recommendations = [];
const structure = this.workingMemory.projectStructure;
// Language-specific recommendations
if (structure.languages['typescript']) {
recommendations.push('Use TypeScript strict mode for better type safety');
}
if (structure.languages['python'] && !structure.patterns.find(p => p.name.includes('Type hints'))) {
recommendations.push('Consider adding type hints to Python code');
}
// Pattern-based recommendations
const hasTests = this.workingMemory.keyInsights.some(i => i.description.toLowerCase().includes('test'));
if (!hasTests) {
recommendations.push('Add unit tests to improve code reliability');
}
return recommendations;
}
determineAction(target) {
if (target.endsWith('.js') || target.endsWith('.ts') || target.endsWith('.py')) {
return 'read_file';
}
else if (target.includes('/') || target.includes('\\')) {
return 'explore_directory';
}
else if (target.includes('*') || target.includes('?')) {
return 'search_pattern';
}
else {
return 'analyze_code';
}
}
assessImpact(question) {
if (question.toLowerCase().includes('delete') ||
question.toLowerCase().includes('remove')) {
return 'high';
}
else if (question.toLowerCase().includes('change') ||
question.toLowerCase().includes('modify')) {
return 'medium';
}
else {
return 'low';
}
}
selectBestOption(options) {
// Select option with highest confidence
return options.reduce((best, current) => current.confidence > best.confidence ? current : best);
}
generateRationale(selected, allOptions) {
const pros = selected.pros.join(', ');
const betterThan = allOptions
.filter(o => o.id !== selected.id && o.confidence < selected.confidence)
.length;
return `Selected "${selected.label}" because: ${pros}. ` +
`This option has ${Math.round(selected.confidence * 100)}% confidence ` +
`and is better than ${betterThan} other options.`;
}
calculateRelevance(type, description) {
let relevance = 0.5;
// Type-based relevance
const typeRelevance = {
'entry-point': 0.9,
'architecture': 0.85,
'issue': 0.8,
'pattern': 0.7,
'dependency': 0.6,
'opportunity': 0.75,
'convention': 0.5,
'test-coverage': 0.65
};
relevance = typeRelevance[type] || relevance;
// Adjust based on description keywords
const keywords = ['critical', 'important', 'main', 'core', 'key'];
if (keywords.some(k => description.toLowerCase().includes(k))) {
relevance = Math.min(1, relevance + 0.1);
}
return relevance;
}
async identifyTargets(goal) {
const targets = [];
// Extract potential file/directory names from goal
const words = goal.toLowerCase().split(/\s+/);
for (const word of words) {
// Check if it looks like a file or directory
if (word.includes('.') || word.includes('/')) {
targets.push(word);
}
// Common file patterns
const patterns = [
`${word}.js`, `${word}.ts`, `${word}.py`,
`${word}/index.js`, `${word}/index.ts`
];
for (const pattern of patterns) {
const found = this.findFileInStructure(this.workingMemory.projectStructure.directories, pattern);
if (found) {
targets.push(found);
}
}
}
return [...new Set(targets)]; // Remove duplicates
}
generateId() {
return Math.random().toString(36).substring(2) + Date.now().toString(36);
}
// Public methods for interactive mode
/**
* Get current working memory state
*/
getWorkingMemory() {
return this.workingMemory;
}
/**
* Get current task progress
*/
getProgress() {
return {
phase: this.workingMemory.taskContext.currentPhase,
progress: this.workingMemory.taskContext.progress,
insights: this.workingMemory.keyInsights.length
};
}
/**
* Request user input for a decision
*/
async requestUserInput(question, options) {
// In a real implementation, this would interact with the user
// For now, return the first option
logger.info(`User input requested: ${question}`);
return options[0];
}
}
//# sourceMappingURL=exploration-engine.js.map