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
JavaScript
"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;