UNPKG

claude-buddy

Version:

Your friendly AI development companion for Claude Code - supercharge Claude Code with intelligent workflows and safety features

434 lines (433 loc) 17.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); class PersonaLearningEngine { dataDir; memoryFile; analyticsFile; sessionMemory; persistentMemory; config; sessionId; sessionStartTime; constructor(dataDir) { this.dataDir = dataDir || path_1.default.join(__dirname, '..', '.claude-buddy'); this.memoryFile = path_1.default.join(this.dataDir, 'persona-memory.json'); this.analyticsFile = path_1.default.join(this.dataDir, 'persona-analytics.json'); this.sessionMemory = { interactions: [], patterns: new Map(), feedback: [], adaptations: [], contextHistory: [] }; this.persistentMemory = { successfulPatterns: [], failedPatterns: [], userPreferences: {}, projectPatterns: {}, adaptationHistory: [], performanceMetrics: {} }; this.config = { maxSessionInteractions: 100, maxPersistentPatterns: 500, learningRate: 0.1, confidenceThreshold: 0.8, patternExpiryDays: 30, adaptationEnabled: true }; } async initialize() { try { await fs_1.promises.mkdir(this.dataDir, { recursive: true }); await this.loadPersistentMemory(); this.sessionId = this.generateSessionId(); this.sessionStartTime = Date.now(); console.log('Learning engine initialized successfully'); return true; } catch (error) { console.error('Failed to initialize learning engine:', error); return false; } } async loadPersistentMemory() { try { const memoryContent = await fs_1.promises.readFile(this.memoryFile, 'utf8'); this.persistentMemory = JSON.parse(memoryContent); this.cleanExpiredPatterns(); } catch (error) { console.log('No existing memory file found, starting with empty memory'); } } async savePersistentMemory() { try { const memoryData = { ...this.persistentMemory, lastUpdated: Date.now(), version: '1.0.0' }; await fs_1.promises.writeFile(this.memoryFile, JSON.stringify(memoryData, null, 2)); } catch (error) { console.error('Failed to save persistent memory:', error); } } recordActivation(activationData) { const interaction = { sessionId: this.sessionId, timestamp: Date.now(), type: 'activation', context: this.captureContext(activationData), ...activationData }; this.sessionMemory.interactions.push(interaction); this.updateActivationPatterns(interaction); if (this.sessionMemory.interactions.length > this.config.maxSessionInteractions) { this.sessionMemory.interactions = this.sessionMemory.interactions.slice(-this.config.maxSessionInteractions); } } recordFeedback(feedbackData) { const feedback = { sessionId: this.sessionId, timestamp: Date.now(), type: 'feedback', personas: feedbackData.personas, rating: feedbackData.rating, comments: feedbackData.comments || '', context: feedbackData.context || {} }; this.sessionMemory.feedback.push(feedback); this.sessionMemory.interactions.push(feedback); this.learnFromFeedback(feedback); this.updatePatternSuccessRates(feedback); } captureContext(activationData) { return { projectType: activationData.projectType, filePatterns: this.normalizeFilePatterns(activationData.filePatterns), commandType: activationData.command, userInput: this.sanitizeUserInput(activationData.userInput), activatedPersonas: activationData.personas || [], collaborationPattern: activationData.collaborationPattern, confidence: activationData.confidence }; } normalizeFilePatterns(filePatterns) { if (!filePatterns) return {}; const normalized = { frontend: 0, backend: 0, test: 0, config: 0, docs: 0 }; for (const pattern of filePatterns) { if (/\.(jsx?|tsx?|vue|svelte)$/i.test(pattern)) normalized.frontend++; if (/\.(py|java|php|rb|go|rs)$/i.test(pattern)) normalized.backend++; if (/\.(test|spec)\./i.test(pattern)) normalized.test++; if (/\.(json|yaml|yml|toml|ini)$/i.test(pattern)) normalized.config++; if (/\.(md|txt|rst)$/i.test(pattern)) normalized.docs++; } return normalized; } updateActivationPatterns(interaction) { const patternKey = this.generatePatternKey(interaction); if (!this.sessionMemory.patterns.has(patternKey)) { this.sessionMemory.patterns.set(patternKey, { count: 0, successCount: 0, contexts: [], lastUsed: Date.now(), confidence: 0 }); } const pattern = this.sessionMemory.patterns.get(patternKey); pattern.count++; pattern.lastUsed = Date.now(); pattern.contexts.push(interaction.context); if (pattern.contexts.length > 10) { pattern.contexts = pattern.contexts.slice(-10); } } learnFromFeedback(feedback) { const relatedInteractions = this.findRelatedInteractions(feedback); for (const interaction of relatedInteractions) { if (interaction.type === 'activation') { const patternKey = this.generatePatternKey(interaction); const pattern = this.sessionMemory.patterns.get(patternKey); if (pattern) { if (feedback.rating >= 4) { pattern.successCount++; this.reinforceSuccessfulPattern(patternKey, interaction, feedback); } else if (feedback.rating <= 2) { this.recordFailedPattern(patternKey, interaction, feedback); } pattern.confidence = pattern.successCount / pattern.count; } } } } findRelatedInteractions(feedback) { const timeWindow = 5 * 60 * 1000; const feedbackTime = feedback.timestamp; return this.sessionMemory.interactions.filter((interaction) => { return Math.abs(interaction.timestamp - feedbackTime) <= timeWindow && interaction.type === 'activation'; }); } reinforceSuccessfulPattern(patternKey, interaction, feedback) { const successPattern = { pattern: patternKey, context: interaction.context, personas: interaction.personas, rating: feedback.rating, comments: feedback.comments || '', timestamp: Date.now(), reinforcementCount: 1, averageRating: feedback.rating }; const existingPattern = this.persistentMemory.successfulPatterns.find(p => p.pattern === patternKey); if (existingPattern) { existingPattern.reinforcementCount++; existingPattern.lastReinforced = Date.now(); existingPattern.averageRating = (existingPattern.averageRating + feedback.rating) / 2; } else { this.persistentMemory.successfulPatterns.push(successPattern); } if (this.persistentMemory.successfulPatterns.length > this.config.maxPersistentPatterns) { this.persistentMemory.successfulPatterns.sort((a, b) => b.reinforcementCount - a.reinforcementCount); this.persistentMemory.successfulPatterns = this.persistentMemory.successfulPatterns.slice(0, this.config.maxPersistentPatterns); } } recordFailedPattern(patternKey, interaction, feedback) { const failedPattern = { pattern: patternKey, context: interaction.context, personas: interaction.personas, rating: feedback.rating, issues: feedback.comments || '', timestamp: Date.now() }; this.persistentMemory.failedPatterns.push(failedPattern); if (this.persistentMemory.failedPatterns.length > this.config.maxPersistentPatterns / 2) { this.persistentMemory.failedPatterns = this.persistentMemory.failedPatterns.slice(-250); } } generatePatternKey(interaction) { const context = interaction.context; const key = [ context.projectType || 'unknown', context.commandType || 'unknown', (context.activatedPersonas || []).sort().join(','), this.categorizeFilePatterns(context.filePatterns) ].join('|'); return key; } categorizeFilePatterns(filePatterns) { if (!filePatterns) return 'none'; const categories = []; if (filePatterns.frontend > 10) categories.push('frontend'); if (filePatterns.backend > 10) categories.push('backend'); if (filePatterns.test > 5) categories.push('test'); if (filePatterns.config > 5) categories.push('config'); if (filePatterns.docs > 3) categories.push('docs'); return categories.length > 0 ? categories.sort().join(',') : 'general'; } getActivationRecommendations(context) { const recommendations = { personas: [], confidence: 0, reasoning: [], patterns: [], adaptations: [] }; const matchingPatterns = this.findMatchingPatterns(context); if (matchingPatterns.length > 0) { matchingPatterns.sort((a, b) => { return (b.reinforcementCount * b.averageRating) - (a.reinforcementCount * a.averageRating); }); const bestPattern = matchingPatterns[0]; recommendations.personas = bestPattern.personas || []; recommendations.confidence = bestPattern.averageRating / 5; recommendations.reasoning.push(`Learned pattern: ${bestPattern.reinforcementCount} successful uses`); recommendations.patterns.push(bestPattern.pattern); const adaptations = this.suggestAdaptations(context, bestPattern); recommendations.adaptations = adaptations; } const antiPatterns = this.findAntiPatterns(context); if (antiPatterns.length > 0) { recommendations.reasoning.push(`Avoiding ${antiPatterns.length} known unsuccessful patterns`); } return recommendations; } findMatchingPatterns(context) { return this.persistentMemory.successfulPatterns.filter(pattern => { return this.contextMatches(pattern.context, context); }); } findAntiPatterns(context) { return this.persistentMemory.failedPatterns.filter(pattern => { return this.contextMatches(pattern.context, context); }); } contextMatches(patternContext, currentContext) { const projectTypeMatch = patternContext.projectType === currentContext.projectType; const commandMatch = patternContext.commandType === currentContext.command; const normalizedCurrentFiles = this.normalizeFilePatterns(currentContext.files); const filePatternSimilarity = this.calculateFilePatternSimilarity(patternContext.filePatterns, normalizedCurrentFiles); return projectTypeMatch && commandMatch && filePatternSimilarity > 0.7; } calculateFilePatternSimilarity(pattern1, pattern2) { if (!pattern1 || !pattern2) return 0; const keys = ['frontend', 'backend', 'test', 'config', 'docs']; let similarity = 0; for (const key of keys) { const val1 = pattern1[key] || 0; const val2 = pattern2[key] || 0; const maxVal = Math.max(val1, val2, 1); similarity += 1 - Math.abs(val1 - val2) / maxVal; } return similarity / keys.length; } suggestAdaptations(context, bestPattern) { const adaptations = []; const normalizedCurrentFiles = this.normalizeFilePatterns(context.files); if (normalizedCurrentFiles && bestPattern.context.filePatterns) { const contextFiles = normalizedCurrentFiles; const patternFiles = bestPattern.context.filePatterns; if (contextFiles.frontend > patternFiles.frontend + 5) { adaptations.push({ type: 'persona_selection', reason: 'More frontend files detected than in learned pattern', impact: 0.3, confidence: 0.7 }); } if (contextFiles.config > patternFiles.config + 3) { adaptations.push({ type: 'persona_selection', reason: 'Additional configuration files may need security review', impact: 0.4, confidence: 0.8 }); } } return adaptations; } cleanExpiredPatterns() { const expiryTime = Date.now() - (this.config.patternExpiryDays * 24 * 60 * 60 * 1000); this.persistentMemory.successfulPatterns = this.persistentMemory.successfulPatterns.filter(pattern => pattern.timestamp > expiryTime); this.persistentMemory.failedPatterns = this.persistentMemory.failedPatterns.filter(pattern => pattern.timestamp > expiryTime); } updatePatternSuccessRates(feedback) { } sanitizeUserInput(userInput) { if (!userInput) return ''; return userInput .replace(/--?[a-zA-Z-]+=["']?[^"'\s]*["']?/g, '[FLAG]') .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[EMAIL]') .replace(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g, '[IP]') .substring(0, 200); } generateSessionId() { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } getAnalytics() { return { sessionStats: { interactions: this.sessionMemory.interactions.length, feedback: this.sessionMemory.feedback.length, patterns: this.sessionMemory.patterns.size, sessionDuration: Date.now() - this.sessionStartTime }, persistentStats: { successfulPatterns: this.persistentMemory.successfulPatterns.length, failedPatterns: this.persistentMemory.failedPatterns.length, totalLearningEvents: this.persistentMemory.adaptationHistory.length }, learningEffectiveness: this.calculateLearningEffectiveness(), topPatterns: this.getTopPatterns(), recommendations: this.getSystemRecommendations() }; } calculateLearningEffectiveness() { const recentFeedback = this.sessionMemory.feedback.filter(f => f.timestamp > Date.now() - (7 * 24 * 60 * 60 * 1000)); if (recentFeedback.length === 0) return 0; const averageRating = recentFeedback.reduce((sum, f) => sum + f.rating, 0) / recentFeedback.length; return averageRating / 5; } getTopPatterns() { return this.persistentMemory.successfulPatterns .sort((a, b) => (b.reinforcementCount * b.averageRating) - (a.reinforcementCount * a.averageRating)) .slice(0, 10) .map(pattern => ({ pattern: pattern.pattern, usage: pattern.reinforcementCount, rating: pattern.averageRating, personas: pattern.personas })); } getSystemRecommendations() { const recommendations = []; const effectiveness = this.calculateLearningEffectiveness(); if (effectiveness < 0.6) { recommendations.push({ type: 'improvement', message: 'Learning effectiveness is low. Consider providing more feedback.', priority: 'medium' }); } const uniquePatterns = new Set(this.persistentMemory.successfulPatterns.map(p => p.pattern)); if (uniquePatterns.size < 5) { recommendations.push({ type: 'diversity', message: 'Limited pattern diversity detected. Try using different commands and contexts.', priority: 'low' }); } return recommendations; } async endSession() { this.persistentMemory.adaptationHistory.push({ sessionId: this.sessionId, duration: Date.now() - this.sessionStartTime, interactions: this.sessionMemory.interactions.length, feedback: this.sessionMemory.feedback.length, patterns: this.sessionMemory.patterns.size, timestamp: Date.now() }); await this.savePersistentMemory(); this.sessionMemory = { interactions: [], patterns: new Map(), feedback: [], adaptations: [], contextHistory: [] }; } } exports.default = PersonaLearningEngine;