@claude-vector/claude-tools
Version:
Claude integration tools for AI-powered development assistance
960 lines (816 loc) ⢠29.1 kB
JavaScript
/**
* 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;