claude-buddy
Version:
Your friendly AI development companion for Claude Code - supercharge Claude Code with intelligent workflows and safety features
445 lines (444 loc) • 19.2 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"));
const auto_activation_js_1 = __importDefault(require("./auto-activation.js"));
class PersonaManager {
configDir;
specialistsDir;
activationEngine;
config = null;
personas = new Map();
activePersonas = [];
sessionMemory = {
interactions: [],
preferences: {},
successfulPatterns: [],
feedbackHistory: [],
activationHistory: []
};
collaborationMatrix = new Map();
constructor(configDir) {
this.configDir = configDir || path_1.default.join(__dirname, 'config');
this.specialistsDir = path_1.default.join(__dirname, 'specialists');
this.activationEngine = new auto_activation_js_1.default();
}
async initialize() {
try {
await this.activationEngine.initialize();
await this.loadConfiguration();
await this.loadPersonaDefinitions();
this.initializeCollaborationMatrix();
console.log(`Persona manager initialized with ${this.personas.size} personas`);
return true;
}
catch (error) {
console.error('Failed to initialize persona manager:', error);
return false;
}
}
async loadConfiguration() {
const configPath = path_1.default.join(this.configDir, 'personas-config.json');
const configContent = await fs_1.promises.readFile(configPath, 'utf8');
this.config = JSON.parse(configContent);
}
async loadPersonaDefinitions() {
const files = await fs_1.promises.readdir(this.specialistsDir);
for (const file of files) {
if (path_1.default.extname(file) === '.md') {
const personaName = path_1.default.basename(file, '.md');
const personaPath = path_1.default.join(this.specialistsDir, file);
const personaContent = await fs_1.promises.readFile(personaPath, 'utf8');
const persona = this.parsePersonaDefinition(personaName, personaContent);
this.personas.set(personaName, persona);
}
}
}
parsePersonaDefinition(name, content) {
const config = this.config.personas?.[name] || {};
return {
name,
content,
category: config.category || 'unknown',
description: config.description || '',
specializations: config.specializations || [],
auto_activation: config.auto_activation || {},
compatible_with: config.compatible_with || [],
priority_hierarchy: config.priority_hierarchy || [],
isActive: false,
activationReason: null,
confidence: 0
};
}
initializeCollaborationMatrix() {
const patterns = this.config.collaboration_patterns || {};
for (const [pattern, description] of Object.entries(patterns)) {
const [persona1, persona2] = pattern.split('_');
if (persona1 && persona2) {
this.collaborationMatrix.set(`${persona1}-${persona2}`, {
description: String(description),
synergy: 'positive',
leadPersona: persona1
});
this.collaborationMatrix.set(`${persona2}-${persona1}`, {
description: String(description),
synergy: 'positive',
leadPersona: persona1
});
}
}
}
async selectPersonas(userInput, context = {}) {
const manualOverrides = this.parseManualOverrides(userInput);
if (manualOverrides.length > 0) {
return this.activateManualPersonas(manualOverrides, userInput, context);
}
const detectionResults = await this.activationEngine.detectPersonas(userInput, context);
if (detectionResults.recommendations.length === 0) {
return {
activePersonas: [],
reasoning: 'No personas met confidence threshold',
detectionResults,
fallbackMode: true
};
}
return this.activateRecommendedPersonas(detectionResults, userInput, context);
}
parseManualOverrides(userInput) {
const overrides = [];
const input = userInput.toLowerCase();
for (const personaName of this.personas.keys()) {
if (input.includes(`--persona-${personaName}`) || input.includes(`--${personaName}`)) {
overrides.push(personaName);
}
}
return overrides;
}
activateManualPersonas(personaNames, userInput, context) {
const activePersonas = [];
for (const name of personaNames) {
const persona = this.personas.get(name);
if (persona) {
persona.isActive = true;
persona.activationReason = 'manual_override';
persona.confidence = 1.0;
activePersonas.push({
name: persona.name,
confidence: persona.confidence,
activationReason: 'manual',
reasoning: 'Manually activated by user',
category: persona.category,
specializations: persona.specializations
});
}
}
this.activePersonas = activePersonas.map(ap => {
const persona = this.personas.get(ap.name);
return { ...persona, ...ap };
});
this.recordInteraction({
input: userInput,
personas: activePersonas.map(p => p.name),
activationType: 'manual',
context: context || {}
});
return {
activePersonas,
reasoning: `Manually activated: ${personaNames.join(', ')}`,
detectionResults: null,
manualMode: true,
collaboration: this.planCollaboration(activePersonas)
};
}
activateRecommendedPersonas(detectionResults, userInput, context) {
const activePersonas = [];
for (const recommendation of detectionResults.recommendations) {
const persona = this.personas.get(recommendation.persona);
if (persona) {
persona.isActive = true;
persona.activationReason = 'automatic';
persona.confidence = recommendation.confidence;
persona.reasoning = recommendation.reasoning;
activePersonas.push({
name: persona.name,
confidence: recommendation.confidence,
activationReason: 'automatic',
reasoning: recommendation.reasoning,
category: persona.category,
specializations: persona.specializations
});
}
}
this.activePersonas = activePersonas.map(ap => {
const persona = this.personas.get(ap.name);
return { ...persona, ...ap };
});
this.recordInteraction({
input: userInput,
personas: activePersonas.map(p => p.name),
activationType: 'automatic',
confidence: detectionResults.recommendations.map(r => r.confidence),
context: context || {}
});
return {
activePersonas,
reasoning: this.generateActivationSummary(activePersonas),
detectionResults,
automaticMode: true,
collaboration: this.planCollaboration(activePersonas)
};
}
planCollaboration(activePersonas) {
if (activePersonas.length <= 1) {
return {
strategy: 'single_persona',
leadPersona: activePersonas[0]?.name || null,
consultingPersonas: [],
collaborationPatterns: [],
validationChain: []
};
}
const leadPersona = this.determineLead(activePersonas);
const collaborationPatterns = [];
for (let i = 0; i < activePersonas.length; i++) {
for (let j = i + 1; j < activePersonas.length; j++) {
const pattern = this.getCollaborationPattern(activePersonas[i].name, activePersonas[j].name);
if (pattern) {
collaborationPatterns.push({
personas: [activePersonas[i].name, activePersonas[j].name],
description: pattern.description,
synergy: pattern.synergy,
leadPersona: pattern.leadPersona
});
}
}
}
return {
strategy: 'multi_persona',
leadPersona: leadPersona.name,
consultingPersonas: activePersonas.filter(p => p !== leadPersona).map(p => p.name),
collaborationPatterns,
validationChain: this.planValidationChain(activePersonas)
};
}
determineLead(activePersonas) {
if (activePersonas.length === 0) {
throw new Error('Cannot determine lead persona from empty array');
}
const leadPriority = {
'security': 10,
'architect': 9,
'analyzer': 8,
'qa': 7,
'performance': 6,
'backend': 5,
'frontend': 5,
'devops': 5,
'refactorer': 4,
'mentor': 3,
'scribe': 2
};
return activePersonas.reduce((lead, current) => {
const leadScore = (leadPriority[lead.name] || 0) + (lead.confidence * 2);
const currentScore = (leadPriority[current.name] || 0) + (current.confidence * 2);
return currentScore > leadScore ? current : lead;
});
}
getCollaborationPattern(persona1, persona2) {
const key1 = `${persona1}-${persona2}`;
const key2 = `${persona2}-${persona1}`;
return this.collaborationMatrix.get(key1) || this.collaborationMatrix.get(key2);
}
planValidationChain(activePersonas) {
const validationConfig = this.config.validation_chain || {};
const activeNames = activePersonas.map(p => p.name);
const validationSteps = [];
if (validationConfig.security_validation?.some((p) => activeNames.includes(p))) {
validationSteps.push({
type: 'security',
personas: validationConfig.security_validation.filter((p) => activeNames.includes(p)),
description: 'Security review and threat assessment'
});
}
if (validationConfig.quality_validation?.some((p) => activeNames.includes(p))) {
validationSteps.push({
type: 'quality',
personas: validationConfig.quality_validation.filter((p) => activeNames.includes(p)),
description: 'Code quality and maintainability review'
});
}
if (validationConfig.performance_validation?.some((p) => activeNames.includes(p))) {
validationSteps.push({
type: 'performance',
personas: validationConfig.performance_validation.filter((p) => activeNames.includes(p)),
description: 'Performance impact assessment'
});
}
return validationSteps;
}
generateActivationSummary(activePersonas) {
if (activePersonas.length === 0)
return 'No personas activated';
if (activePersonas.length === 1) {
const persona = activePersonas[0];
return `Activated ${persona.name} persona (${Math.round(persona.confidence * 100)}% confidence): ${persona.reasoning}`;
}
const lead = this.determineLead(activePersonas);
const supporting = activePersonas.filter(p => p !== lead);
return `Multi-persona activation: ${lead.name} leading (${Math.round(lead.confidence * 100)}%), supported by ${supporting.map(p => p.name).join(', ')}`;
}
generatePersonaPrompt(activePersonas, userInput, context = {}) {
if (activePersonas.length === 0) {
return {
systemPrompt: '',
personaContext: null,
collaboration: null
};
}
let systemPrompt = '';
if (activePersonas.length === 1) {
const persona = activePersonas[0];
const personaContent = this.personas.get(persona.name)?.content || '';
systemPrompt = `You are now operating as the **${persona.name} persona** for Claude Buddy.\n\n${personaContent}`;
}
else {
const collaboration = this.planCollaboration(activePersonas);
const lead = activePersonas.find(p => p.name === collaboration.leadPersona);
const supporting = activePersonas.filter(p => p.name !== collaboration.leadPersona);
if (lead) {
const leadContent = this.personas.get(lead.name)?.content || '';
systemPrompt = `You are operating in **multi-persona collaboration mode** for Claude Buddy.\n\n`;
systemPrompt += `**Lead Persona: ${lead.name}**\n${leadContent}\n\n`;
systemPrompt += `**Supporting Personas:**\n`;
for (const persona of supporting) {
const personaConfig = this.personas.get(persona.name);
systemPrompt += `- **${persona.name}**: Focus on ${personaConfig?.description || 'specialized tasks'}\n`;
}
systemPrompt += `\n**Collaboration Strategy:**\n`;
systemPrompt += `- ${lead.name} leads the response and decision-making\n`;
systemPrompt += `- Supporting personas provide specialized input and validation\n`;
if (collaboration.collaborationPatterns && collaboration.collaborationPatterns.length > 0) {
systemPrompt += `- Known collaboration patterns: ${collaboration.collaborationPatterns.map(p => p.description).join('; ')}\n`;
}
}
}
const personaContext = {
active: activePersonas.map(p => ({
name: p.name,
confidence: p.confidence,
role: p.name === this.determineLead(activePersonas).name ? 'lead' : 'supporting'
})),
collaboration: this.planCollaboration(activePersonas)
};
return {
systemPrompt,
personaContext,
collaboration: this.planCollaboration(activePersonas),
userInput,
context
};
}
recordInteraction(interaction) {
const fullInteraction = {
input: interaction.input || '',
personas: interaction.personas || [],
activationType: interaction.activationType || 'automatic',
confidence: Array.isArray(interaction.confidence) ? interaction.confidence : (interaction.confidence ? [interaction.confidence] : []),
context: interaction.context || {},
timestamp: new Date().toISOString()
};
this.sessionMemory.interactions.push(fullInteraction);
if (this.sessionMemory.interactions.length > 100) {
this.sessionMemory.interactions = this.sessionMemory.interactions.slice(-100);
}
this.activationEngine.recordPersonaUsage(fullInteraction.personas);
}
getSessionId() {
return Date.now().toString();
}
provideFeedback(feedback) {
const { personas, rating, comments, context } = feedback;
const feedbackRecord = {
type: 'feedback',
personas,
rating,
comments: comments || '',
context: context || {},
timestamp: Date.now()
};
this.sessionMemory.feedbackHistory.push(feedbackRecord);
const feedbackType = rating >= 4 ? 'positive' : rating <= 2 ? 'negative' : 'neutral';
this.activationEngine.recordPersonaUsage(personas, feedbackType);
if (rating >= 4) {
const successfulPattern = {
personas,
context,
timestamp: Date.now()
};
this.sessionMemory.successfulPatterns.push(successfulPattern);
}
}
getAnalytics() {
const interactions = this.sessionMemory.interactions;
const analytics = {
totalInteractions: interactions.length,
personaUsage: {},
collaborationPatterns: {},
averageConfidence: 0,
successfulPatterns: this.sessionMemory.successfulPatterns.length
};
for (const interaction of interactions) {
if (interaction.personas) {
for (const persona of interaction.personas) {
if (!analytics.personaUsage[persona]) {
analytics.personaUsage[persona] = { count: 0, totalConfidence: 0 };
}
analytics.personaUsage[persona].count++;
if (interaction.confidence && interaction.confidence.length > 0) {
const confidence = interaction.confidence[0];
analytics.personaUsage[persona].totalConfidence += confidence;
}
}
if (interaction.personas.length > 1) {
const pattern = interaction.personas.sort().join('-');
analytics.collaborationPatterns[pattern] =
(analytics.collaborationPatterns[pattern] || 0) + 1;
}
}
}
const confidenceSum = interactions
.filter(i => i.confidence && i.confidence.length > 0)
.reduce((sum, i) => {
const confidence = i.confidence[0];
return sum + confidence;
}, 0);
const confidenceCount = interactions.filter(i => i.confidence && i.confidence.length > 0).length;
analytics.averageConfidence = confidenceCount > 0 ? confidenceSum / confidenceCount : 0;
return analytics;
}
reset() {
this.activePersonas.forEach(persona => {
persona.isActive = false;
persona.activationReason = null;
persona.confidence = 0;
});
this.activePersonas = [];
}
getActivePersonas() {
return this.activePersonas.map(p => ({
name: p.name,
confidence: p.confidence,
activationReason: (p.activationReason === 'manual_override' ? 'manual' : 'automatic'),
reasoning: p.reasoning,
category: p.category,
specializations: p.specializations
}));
}
isPersonaActive(personaName) {
return this.activePersonas.some(p => p.name === personaName);
}
}
exports.default = PersonaManager;