UNPKG

@claude-vector/claude-tools

Version:

Claude integration tools for AI-powered development assistance

960 lines (816 loc) • 29.1 kB
/** * Enhanced Session Manager for Claude development tasks * Orchestrates the entire development session workflow * * Responsibilities: * - Manage development session lifecycle * - Coordinate between ContextManager, VectorSearchEngine, and QueryOptimizer * - Track and learn from development patterns * - Provide task-specific optimization */ import { promises as fs } from 'fs'; import path from 'path'; import crypto from 'crypto'; import os from 'os'; export class SessionManager { constructor(config = {}) { // Session storage this.sessionsDir = config.sessionsDir || path.join(process.cwd(), '.claude-sessions'); this.currentSessionFile = path.join(os.homedir(), '.claude-session'); this.maxSessions = config.maxSessions || 100; // Current session state this.currentSession = null; // Module references (dependency injection) this.contextManager = null; this.searchEngine = null; this.queryOptimizer = null; // Session will be loaded on first access // Task type definitions this.taskProfiles = { 'bug-fix': { description: 'Fixing bugs and errors', priorityTypes: ['error', 'stacktrace', 'log'], searchStrategy: { includeErrorPatterns: true, lookForSimilarFixes: true, prioritizeRecent: true }, contextRetention: { errorContext: 'high', previousFixes: 'high', relatedCode: 'medium' } }, 'feature': { description: 'Implementing new features', priorityTypes: ['example', 'pattern', 'api'], searchStrategy: { findSimilarImplementations: true, includeDesignPatterns: true, lookForExamples: true }, contextRetention: { examples: 'high', apiDocs: 'high', existingCode: 'medium' } }, 'refactor': { description: 'Refactoring existing code', priorityTypes: ['pattern', 'bestpractice', 'antipattern'], searchStrategy: { findCodeSmells: true, suggestPatterns: true, analyzeStructure: true }, contextRetention: { currentStructure: 'high', targetPatterns: 'high', dependencies: 'medium' } }, 'debug': { description: 'Debugging issues', priorityTypes: ['log', 'trace', 'state'], searchStrategy: { traceExecution: true, findRelatedLogs: true, analyzeState: true }, contextRetention: { executionTrace: 'high', variableState: 'high', logs: 'medium' } }, 'explore': { description: 'Exploring codebase', priorityTypes: ['overview', 'structure', 'documentation'], searchStrategy: { mapStructure: true, findDocumentation: true, identifyPatterns: true }, contextRetention: { projectStructure: 'high', keyComponents: 'medium', documentation: 'medium' } } }; // Learning and statistics this.sessionStats = { totalSessions: 0, successfulSessions: 0, averageDuration: 0, commonPatterns: new Map() }; } /** * Set module dependencies */ setModules({ contextManager, searchEngine, queryOptimizer }) { this.contextManager = contextManager; this.searchEngine = searchEngine; this.queryOptimizer = queryOptimizer; } /** * Start a new development session */ async startSession(taskType, description, metadata = {}) { // Validate task type if (!this.taskProfiles[taskType]) { throw new Error(`Unknown task type: ${taskType}. Valid types: ${Object.keys(this.taskProfiles).join(', ')}`); } // Create session const session = { id: this.generateSessionId(), taskType, task: description, startTime: new Date().toISOString(), status: 'active', activities: [], searchHistory: [], contextSnapshots: [], metadata: { ...metadata, projectPath: process.cwd(), environment: process.env.NODE_ENV || 'development', taskProfile: this.taskProfiles[taskType] } }; // Initialize context manager with task if (this.contextManager) { this.contextManager.setTask(taskType, description); } // Find and load related sessions const relatedSessions = await this.findRelatedSessions(description, taskType); if (relatedSessions.length > 0) { session.metadata.relatedSessions = relatedSessions.map(s => s.id); await this.loadContextFromSessions(relatedSessions, taskType); } // Save session first this.currentSession = session; await this.saveCurrentSession(); await this.saveSession(session); // Perform initial search based on task type (unless disabled) if (metadata.autoContext !== false) { await this.performInitialSearch(taskType, description); } this.sessionStats.totalSessions++; return { sessionId: session.id, taskType, task: description, relatedSessions: relatedSessions.length, contextSize: this.contextManager?.getStats().totalItems || 0 }; } /** * Search for relevant information */ async search(query, options = {}) { if (!this.currentSession) { throw new Error('No active session'); } const taskProfile = this.taskProfiles[this.currentSession.taskType]; // Optimize query based on task type let optimizedQuery = query; if (this.queryOptimizer) { optimizedQuery = await this.queryOptimizer.optimizeQuery(query, { taskType: this.currentSession.taskType, taskProfile, previousSearches: this.currentSession.searchHistory.slice(-5) }); } // Perform search const searchOptions = { ...taskProfile.searchStrategy, ...options }; const results = await this.searchEngine.search(optimizedQuery, searchOptions); // Record search this.currentSession.searchHistory.push({ timestamp: Date.now(), originalQuery: query, optimizedQuery, resultCount: results.results.length, topScore: results.results[0]?.score || 0 }); // Add relevant results to context await this.addSearchResultsToContext(results, taskProfile); // Update session await this.addActivity('search', { query, resultCount: results.results.length, addedToContext: results.results.filter(r => r.addedToContext).length }); return results; } /** * Add activity to current session */ async addActivity(type, data) { if (!this.currentSession) { throw new Error('No active session'); } const activity = { type, timestamp: new Date().toISOString(), data }; this.currentSession.activities.push(activity); this.currentSession.lastActivity = activity.timestamp; // Learn from activity patterns this.learnFromActivity(activity); // Save session await this.saveCurrentSession(); await this.saveSession(this.currentSession); return activity; } /** * Take a snapshot of current context */ async takeContextSnapshot(label = '') { if (!this.currentSession || !this.contextManager) { return null; } const snapshot = { timestamp: Date.now(), label, stats: this.contextManager.getStats(), topItems: this.contextManager.contextItems .slice(0, 10) .map(item => ({ id: item.id, type: item.type, priority: item.priority, tokens: item.tokens })) }; this.currentSession.contextSnapshots.push(snapshot); await this.saveCurrentSession(); return snapshot; } /** * Complete current session */ async completeSession(summary = '', success = true) { if (!this.currentSession) { throw new Error('No active session'); } // Calculate duration const duration = Date.now() - new Date(this.currentSession.startTime).getTime(); // Update session this.currentSession.status = success ? 'completed' : 'failed'; this.currentSession.endTime = new Date().toISOString(); this.currentSession.summary = summary; this.currentSession.duration = duration; this.currentSession.finalContextStats = this.contextManager?.getStats() || null; // Update statistics if (success) { this.sessionStats.successfulSessions++; } this.updateAverageDuration(duration); // Learn from session await this.learnFromSession(this.currentSession); // Prepare return data before clearing const result = { sessionId: this.currentSession.id, duration: Math.round(duration / 1000), // seconds success, activitiesCount: this.currentSession.activities.length, searchesCount: this.currentSession.searchHistory.length }; // Save and clear await this.saveSession(this.currentSession); await this.clearCurrentSession(); // Clean up context if (this.contextManager) { this.contextManager.clear(); } return result; } /** * Get current session status */ async getCurrentSessionStatus() { // Load current session if not already loaded if (this.currentSession === null) { await this.loadCurrentSession(); } if (!this.currentSession) { return null; } const duration = Date.now() - new Date(this.currentSession.startTime).getTime(); return { sessionId: this.currentSession.id, taskType: this.currentSession.taskType, task: this.currentSession.task, status: this.currentSession.status, duration: Math.round(duration / 1000), // seconds activities: this.currentSession.activities.length, searches: this.currentSession.searchHistory.length, contextStats: this.contextManager?.getStats() || null, relatedSessions: this.currentSession.metadata.relatedSessions?.length || 0 }; } /** * Get task-aware context */ async getContext() { // Load current session if not already loaded if (this.currentSession === null) { await this.loadCurrentSession(); } if (!this.contextManager) { return 'No context manager available.'; } return this.contextManager.getFormattedContext(); } // Private methods async performInitialSearch(taskType, description) { console.log('\nšŸ” Analyzing task...'); if (!this.searchEngine) { console.log('āš ļø Search engine not available - skipping automatic search'); return; } const taskProfile = this.taskProfiles[taskType]; // Extract keywords from description const keywords = this.extractKeywords(description, taskType); console.log(`šŸ“ Extracted keywords: ${keywords.join(', ')}`); // Identify components const components = this.identifyComponents(keywords, description); console.log(`šŸ·ļø Identified components: ${components.join(', ')}`); // Build initial search query based on task type let queries = []; switch (taskType) { case 'bug-fix': // Extract error keywords const errorKeywords = description.match(/error|exception|fail|bug|issue/gi) || []; queries.push(...errorKeywords); // If no error keywords found, extract the problem description if (queries.length === 0) { // Look for problem indicators const problemMatch = description.match(/(cannot|can't|not|doesn't|won't|unable|broken|fix)\s+(\w+\s*){1,3}/gi); if (problemMatch) { queries.push(...problemMatch); } // Also search for the main action/feature mentioned const actionMatch = description.match(/(login|logout|save|load|create|delete|update|submit|process|authenticate)\w*/gi); if (actionMatch) { queries.push(...actionMatch); } // If still no queries, use the full description if (queries.length === 0) { queries.push(description); } } break; case 'feature': // Look for feature-related terms queries.push(`implement ${description}`); queries.push(`example ${description}`); break; case 'refactor': // Search for current implementation queries.push(`function ${description}`); queries.push(`class ${description}`); break; case 'debug': // Search for logs and traces queries.push(`log ${description}`); queries.push(`debug ${description}`); break; } // Generate search queries for different aspects const searchQueries = this.generateSearchQueries(queries, taskType, components); console.log('\nšŸ”Ž Searching for related code...'); // Perform searches and add to context let searchResults = []; let totalResults = 0; for (let i = 0; i < searchQueries.slice(0, 4).length; i++) { // Limit to 4 searches const query = searchQueries[i]; console.log(` [${i + 1}/${Math.min(searchQueries.length, 4)}] Searching for "${query}"...`); try { const results = await this.search(query, { maxResults: 5, autoAddToContext: true }); if (results && results.results) { searchResults.push(...results.results); totalResults += results.results.length; } } catch (error) { // Log but don't fail session start console.error(` āŒ Search failed: ${error.message}`); } } // Analyze results console.log('\nšŸ“Š Analyzing search results...'); console.log(`āœ“ Found ${totalResults} relevant code snippets`); if (searchResults.length > 0) { // Group results by file const fileGroups = this.groupResultsByFile(searchResults); const criticalFiles = this.identifyCriticalFiles(fileGroups, taskType); if (criticalFiles.length > 0) { console.log(`āœ“ Identified ${criticalFiles.length} critical files:`); criticalFiles.slice(0, 3).forEach(file => { console.log(` - ${file.path} (${file.description})`); }); } } // Show context building progress if (totalResults > 0) { console.log('\nšŸ“‹ Building context...'); const addedItems = await this.getContextItemsSummary(); addedItems.forEach(item => { console.log(`āœ“ Added ${item.description} (priority: ${item.priority})`); }); } } async addSearchResultsToContext(results, taskProfile) { if (!this.contextManager || !results.results) return; const contextRetention = taskProfile.contextRetention; for (const result of results.results) { // Determine priority based on result type and task profile let priority = 'medium'; if (result.chunk?.type && contextRetention[result.chunk.type]) { priority = contextRetention[result.chunk.type]; } else if (result.score > 0.9) { priority = 'high'; } else if (result.score < 0.7) { priority = 'low'; } // Add to context await this.contextManager.addItem({ type: result.chunk?.type || 'search-result', content: result.chunk?.content || result.content, metadata: { file: result.chunk?.file, score: result.score, query: results.query, relevantToTask: this.currentSession.taskType, ...result.chunk?.metadata }, source: 'search' }, priority); result.addedToContext = true; } } async findRelatedSessions(description, taskType) { const sessions = await this.getAllSessions(); // Score sessions by relevance const scored = sessions.map(session => { let score = 0; // Same task type if (session.taskType === taskType) { score += 0.5; } // Keyword overlap const descWords = description.toLowerCase().split(/\s+/); const sessionWords = session.task.toLowerCase().split(/\s+/); const overlap = descWords.filter(word => sessionWords.includes(word) && word.length > 3 ).length; score += overlap * 0.2; // Recent sessions score higher const age = Date.now() - new Date(session.startTime).getTime(); const daysSinceSession = age / (24 * 60 * 60 * 1000); if (daysSinceSession < 7) { score += (7 - daysSinceSession) / 14; // Max 0.5 for very recent } // Successful sessions score higher if (session.status === 'completed') { score += 0.3; } return { session, score }; }); // Return top matches return scored .filter(item => item.score > 0.5) .sort((a, b) => b.score - a.score) .slice(0, 5) .map(item => item.session); } async loadContextFromSessions(sessions, taskType) { if (!this.contextManager) return; const taskProfile = this.taskProfiles[taskType]; for (const session of sessions) { // Extract valuable activities const valuableActivities = session.activities .filter(activity => activity.type === 'solution' || activity.type === 'fix' || activity.type === 'implementation' ) .slice(-3); // Last 3 valuable activities // Add to context with appropriate priority for (const activity of valuableActivities) { await this.contextManager.addItem({ type: 'previous-session', content: `From session "${session.task}" (${session.status}): ${JSON.stringify(activity.data)}`, metadata: { sessionId: session.id, sessionTask: session.task, sessionStatus: session.status, activityType: activity.type, relevantToTask: taskType } }, session.status === 'completed' ? 'medium' : 'low'); } } } learnFromActivity(activity) { // Track common patterns const pattern = `${this.currentSession.taskType}:${activity.type}`; const count = this.sessionStats.commonPatterns.get(pattern) || 0; this.sessionStats.commonPatterns.set(pattern, count + 1); } async learnFromSession(session) { // This would be where we'd implement more sophisticated learning // For now, just track success patterns if (session.status === 'completed') { const pattern = { taskType: session.taskType, duration: session.duration, searches: session.searchHistory.length, activities: session.activities.length }; // Store pattern for future optimization // In a real implementation, this would persist to a learning database } } updateAverageDuration(duration) { const total = this.sessionStats.totalSessions; const currentAvg = this.sessionStats.averageDuration; this.sessionStats.averageDuration = (currentAvg * (total - 1) + duration) / total; } async getAllSessions() { try { await fs.mkdir(this.sessionsDir, { recursive: true }); const files = await fs.readdir(this.sessionsDir); const sessions = []; for (const file of files) { if (file.endsWith('.json')) { try { const data = await fs.readFile(path.join(this.sessionsDir, file), 'utf-8'); sessions.push(JSON.parse(data)); } catch (error) { // Skip invalid files } } } return sessions.sort((a, b) => new Date(b.startTime) - new Date(a.startTime) ); } catch (error) { return []; } } generateSessionId() { const timestamp = Date.now(); const random = crypto.randomBytes(4).toString('hex'); return `session-${timestamp}-${random}`; } async saveCurrentSession() { if (!this.currentSession) return; await fs.writeFile( this.currentSessionFile, JSON.stringify(this.currentSession, null, 2) ); } async saveSession(session) { await fs.mkdir(this.sessionsDir, { recursive: true }); const filePath = path.join(this.sessionsDir, `${session.id}.json`); await fs.writeFile(filePath, JSON.stringify(session, null, 2)); } async loadCurrentSession() { try { const sessionData = await fs.readFile(this.currentSessionFile, 'utf-8'); this.currentSession = JSON.parse(sessionData); } catch (error) { // No current session file exists, or file is invalid this.currentSession = null; } } async clearCurrentSession() { try { await fs.unlink(this.currentSessionFile); } catch (error) { // File might not exist } this.currentSession = null; } /** * Get session recommendations based on current task */ async getRecommendations() { // Load current session if not already loaded if (this.currentSession === null) { await this.loadCurrentSession(); } if (!this.currentSession) return null; const taskProfile = this.taskProfiles[this.currentSession.taskType]; const recommendations = []; // Based on task type switch (this.currentSession.taskType) { case 'bug-fix': recommendations.push({ type: 'search', suggestion: 'Search for similar error messages in the codebase', query: 'error similar to current' }); recommendations.push({ type: 'context', suggestion: 'Look for recent fixes in the same module', priority: 'high' }); break; case 'feature': recommendations.push({ type: 'search', suggestion: 'Find examples of similar features', query: 'example implementation' }); recommendations.push({ type: 'explore', suggestion: 'Review API documentation for dependencies', priority: 'medium' }); break; } // Based on session history if (this.currentSession.searchHistory.length === 0) { recommendations.push({ type: 'action', suggestion: 'Start with a broad search to understand the context', priority: 'high' }); } return recommendations; } // Helper methods for enhanced display extractKeywords(description, taskType) { const words = description.toLowerCase().split(/\s+/); const stopWords = ['the', 'a', 'an', 'is', 'are', 'was', 'were', 'been', 'be', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'should', 'could', 'may', 'might', 'must', 'can', 'to', 'for', 'in', 'on', 'at', 'by', 'with', 'from', 'up', 'down', 'out', 'off', 'over', 'under', 'again', 'then', 'once']; // Filter out stop words and extract meaningful keywords let keywords = words.filter(word => word.length > 2 && !stopWords.includes(word) && /^[a-z]+$/.test(word) ); // Add task-specific keywords if (taskType === 'bug-fix') { const bugKeywords = description.match(/error|exception|fail|bug|issue|cannot|can't|unable|broken/gi) || []; keywords.push(...bugKeywords.map(k => k.toLowerCase())); } // Remove duplicates return [...new Set(keywords)]; } identifyComponents(keywords, description) { const components = []; // Common system components const componentPatterns = { 'authentication': /auth|login|logout|signin|signup|password|credential/i, 'session': /session|cookie|token|jwt/i, 'database': /database|db|sql|query|table|schema/i, 'api': /api|endpoint|request|response|rest|graphql/i, 'ui': /ui|interface|button|form|input|display|render/i, 'security': /security|encrypt|decrypt|hash|salt|permission|role/i, 'payment': /payment|billing|subscription|charge|refund|stripe/i, 'file': /file|upload|download|storage|document|image/i, 'email': /email|mail|smtp|send|notification/i, 'user': /user|profile|account|member|customer/i }; // Check description and keywords for component patterns const fullText = `${description} ${keywords.join(' ')}`; for (const [component, pattern] of Object.entries(componentPatterns)) { if (pattern.test(fullText)) { components.push(component); } } // If no components identified, use generic ones if (components.length === 0) { components.push('general', 'system'); } return components; } generateSearchQueries(baseQueries, taskType, components) { let queries = [...baseQueries]; // Add component-specific queries switch (taskType) { case 'bug-fix': // Add error handling searches if (!queries.some(q => q.includes('error'))) { queries.push('error handling'); } // Add component-specific error searches components.forEach(component => { queries.push(`${component} error`); }); break; case 'feature': // Add implementation pattern searches queries.push('example implementation'); components.forEach(component => { queries.push(`${component} implementation`); }); break; case 'refactor': // Add pattern searches queries.push('best practices'); break; case 'debug': // Add logging and debug searches queries.push('console.log'); queries.push('debug trace'); break; } // Remove duplicates and limit return [...new Set(queries)].slice(0, 6); } groupResultsByFile(results) { const fileGroups = {}; results.forEach(result => { const filePath = result.chunk?.file || result.file || 'unknown'; if (!fileGroups[filePath]) { fileGroups[filePath] = { path: filePath, results: [], totalScore: 0 }; } fileGroups[filePath].results.push(result); fileGroups[filePath].totalScore += result.score || 0; }); return Object.values(fileGroups); } identifyCriticalFiles(fileGroups, taskType) { // Sort by total score and number of matches const sortedFiles = fileGroups.sort((a, b) => { const scoreA = a.totalScore / a.results.length; const scoreB = b.totalScore / b.results.length; return scoreB - scoreA; }); // Add descriptions based on file patterns return sortedFiles.slice(0, 5).map(file => { let description = 'related code'; // Identify file purpose from path if (file.path.includes('auth') || file.path.includes('login')) { description = 'authentication logic'; } else if (file.path.includes('session')) { description = 'session handling'; } else if (file.path.includes('route') || file.path.includes('api')) { description = 'API endpoint'; } else if (file.path.includes('test')) { description = 'test cases'; } else if (file.path.includes('util') || file.path.includes('helper')) { description = 'utility functions'; } else if (file.path.includes('model') || file.path.includes('schema')) { description = 'data model'; } return { path: file.path, description, score: file.totalScore / file.results.length, matchCount: file.results.length }; }); } async getContextItemsSummary() { if (!this.contextManager) return []; const items = []; try { // Try to get context stats instead of full context const stats = this.contextManager.getStats ? this.contextManager.getStats() : null; if (stats && stats.totalItems > 0) { // Create summary based on stats items.push({ description: `${stats.totalItems} code snippets and documentation`, priority: 'high' }); if (stats.errorContext > 0) { items.push({ description: `${stats.errorContext} error handling patterns`, priority: 'high' }); } if (stats.relatedCode > 0) { items.push({ description: `${stats.relatedCode} related code examples`, priority: 'medium' }); } } } catch (error) { // If contextManager doesn't have expected methods, return generic summary items.push({ description: 'relevant code patterns', priority: 'medium' }); } return items; } } export default SessionManager;