const fs = require('fs'); const path = require('path'); class HandoffManager { constructor(workspacePath = null) { this.workspacePath = workspacePath || path.join(process.cwd(), '.workspace'); this.handoffsPath = path.join(this.workspacePath, 'handoffs'); this.contextPath = path.join(this.workspacePath, 'context'); // Initialize directories this.initialize(); // Agent-specific filtering rules with multi-role support this.agentFilters = { 'dev': { includePatterns: ['technical', 'implementation', 'code', 'architecture', 'bug', 'feature'], excludePatterns: ['business', 'stakeholder', 'marketing'], requiredSections: ['technical details', 'code references', 'implementation requirements'] }, 'qa': { includePatterns: ['testing', 'validation', 'quality', 'acceptance', 'bug', 'criteria'], excludePatterns: ['implementation details', 'code specifics'], requiredSections: ['acceptance criteria', 'testing requirements', 'quality standards'] }, 'architect': { includePatterns: ['design', 'architecture', 'system', 'integration', 'technical', 'pattern'], excludePatterns: ['implementation specifics', 'testing details'], requiredSections: ['design decisions', 'technical constraints', 'system architecture'] }, 'pm': { includePatterns: ['requirements', 'business', 'stakeholder', 'scope', 'timeline', 'priority'], excludePatterns: ['technical implementation', 'code details'], requiredSections: ['business requirements', 'stakeholder decisions', 'scope changes'] }, 'ux-expert': { includePatterns: ['user', 'interface', 'experience', 'design', 'usability', 'interaction'], excludePatterns: ['backend', 'database', 'server'], requiredSections: ['user requirements', 'design specifications', 'usability considerations'] }, 'analyst': { includePatterns: ['data', 'analysis', 'metrics', 'trends', 'insights', 'research', 'patterns', 'statistics'], excludePatterns: ['implementation', 'specific code'], requiredSections: ['data analysis', 'insights and trends', 'research findings'] }, 'brainstorming': { includePatterns: ['creative', 'ideation', 'brainstorm', 'innovation', 'alternative', 'possibility', 'exploration'], excludePatterns: ['constraints', 'limitations', 'final decisions'], requiredSections: ['creative exploration', 'alternative approaches', 'innovative solutions'] }, 'research': { includePatterns: ['research', 'investigation', 'study', 'benchmark', 'industry', 'best-practice', 'standards'], excludePatterns: ['implementation', 'specific solutions'], requiredSections: ['research methodology', 'findings and insights', 'recommendations'] } }; // Multi-role combinations for complex scenarios this.multiRoleFilters = { 'dev-analyst': { primary: 'dev', secondary: 'analyst', description: 'Development with data analysis capabilities' }, 'qa-research': { primary: 'qa', secondary: 'research', description: 'Quality assurance with research methodologies' }, 'architect-brainstorming': { primary: 'architect', secondary: 'brainstorming', description: 'Architecture design with creative exploration' }, 'pm-analyst': { primary: 'pm', secondary: 'analyst', description: 'Project management with data analysis' }, 'ux-research': { primary: 'ux-expert', secondary: 'research', description: 'UX design with user research capabilities' } }; } initialize() { if (!fs.existsSync(this.handoffsPath)) { fs.mkdirSync(this.handoffsPath, { recursive: true }); } } async createHandoff(sourceAgent, targetAgent, context = {}) { try { const timestamp = new Date().toISOString(); const handoffId = `${sourceAgent}-to-${targetAgent}-${timestamp.replace(/[:.]/g, '-')}`; const handoffFile = path.join(this.handoffsPath, `${handoffId}.md`); // Load workspace context using our ContextManager integration const workspaceContext = await this.loadWorkspaceContext(); // Filter context for target agent const filteredContext = this.filterContextForAgent(workspaceContext, targetAgent); // Generate handoff package const handoffContent = await this.generateHandoffPackage({ handoffId, sourceAgent, targetAgent, timestamp, context: filteredContext, customContext: context }); // Validate handoff completeness const validation = this.validateHandoff(handoffContent, targetAgent); // Write handoff file fs.writeFileSync(handoffFile, handoffContent); // Update handoff registry await this.updateHandoffRegistry(handoffId, sourceAgent, targetAgent, validation); // Log handoff in audit trail await this.logHandoffEvent({ handoffId, sourceAgent, targetAgent, timestamp, status: 'created', validationScore: validation.score, filePath: handoffFile }); return { handoffId, filePath: handoffFile, validation, success: true }; } catch (error) { console.error('Failed to create handoff:', error.message); throw error; } } async loadWorkspaceContext() { try { const context = { shared: {}, decisions: [], progress: {}, quality: {} }; // Load shared context const sharedContextFile = path.join(this.contextPath, 'shared-context.md'); if (fs.existsSync(sharedContextFile)) { context.shared = this.parseSharedContext(fs.readFileSync(sharedContextFile, 'utf8')); } // Load decisions const decisionsFile = path.join(this.workspacePath, 'decisions', 'decisions-log.md'); if (fs.existsSync(decisionsFile)) { context.decisions = this.parseDecisions(fs.readFileSync(decisionsFile, 'utf8')); } // Load progress const progressFile = path.join(this.workspacePath, 'progress', 'progress-summary.md'); if (fs.existsSync(progressFile)) { context.progress = this.parseProgress(fs.readFileSync(progressFile, 'utf8')); } // Load quality metrics const qualityFile = path.join(this.workspacePath, 'quality', 'quality-metrics.md'); if (fs.existsSync(qualityFile)) { context.quality = this.parseQualityMetrics(fs.readFileSync(qualityFile, 'utf8')); } return context; } catch (error) { console.error('Failed to load workspace context:', error.message); return { shared: {}, decisions: [], progress: {}, quality: {} }; } } parseSharedContext(content) { const context = {}; try { const lastUpdatedMatch = content.match(/\*\*Last Updated:\*\* (.+)/); if (lastUpdatedMatch) context.lastUpdated = lastUpdatedMatch[1]; const primaryAgentMatch = content.match(/\*\*Primary Agent:\*\* (.+)/); if (primaryAgentMatch) context.primaryAgent = primaryAgentMatch[1]; const currentFocusMatch = content.match(/## Current Focus\n([\s\S]*?)(?=\n## |$)/); if (currentFocusMatch) context.currentFocus = currentFocusMatch[1].trim(); const nextStepsMatch = content.match(/## Next Steps\n([\s\S]*?)(?=\n## |$)/); if (nextStepsMatch) { context.nextSteps = nextStepsMatch[1] .split('\n') .filter(line => line.startsWith('- ')) .map(line => line.substring(2).trim()) .filter(step => step.length > 0); } const sessionNotesMatch = content.match(/## Session Notes\n([\s\S]*?)$/); if (sessionNotesMatch) context.sessionNotes = sessionNotesMatch[1].trim(); } catch (error) { console.warn('Failed to parse shared context:', error.message); } return context; } parseDecisions(content) { const decisions = []; const decisionBlocks = content.split(/## Decision \d+:/); for (let i = 1; i < decisionBlocks.length; i++) { try { const block = decisionBlocks[i]; const lines = block.split('\n'); const decision = { id: `${i.toString().padStart(3, '0')}`, title: lines[0].trim(), date: this.extractField(block, 'Date'), agent: this.extractField(block, 'Agent'), context: this.extractField(block, 'Context'), decision: this.extractField(block, 'Decision'), rationale: this.extractField(block, 'Rationale'), impact: this.extractField(block, 'Impact'), status: this.extractField(block, 'Status') }; decisions.push(decision); } catch (error) { console.warn(`Failed to parse decision block ${i}:`, error.message); } } return decisions.slice(-10); // Last 10 decisions for handoff } parseProgress(content) { const progress = {}; try { const currentStoryMatch = content.match(/\*\*Current Story:\*\* (.+)/); if (currentStoryMatch) progress.currentStory = currentStoryMatch[1]; const qualityScoreMatch = content.match(/\*\*Quality Score:\*\* (.+)/); if (qualityScoreMatch) progress.qualityScore = qualityScoreMatch[1]; const completedMatch = content.match(/## Completed Tasks\n([\s\S]*?)(?=\n## |$)/); if (completedMatch) { progress.completedTasks = completedMatch[1] .split('\n') .filter(line => line.startsWith('- ✅')) .map(line => line.substring(4).trim()) .filter(task => task.length > 0); } const pendingMatch = content.match(/## Pending Tasks\n([\s\S]*?)(?=\n## |$)/); if (pendingMatch) { progress.pendingTasks = pendingMatch[1] .split('\n') .filter(line => line.startsWith('- ⏳')) .map(line => line.substring(4).trim()) .filter(task => task.length > 0); } const blockersMatch = content.match(/## Blockers\n([\s\S]*?)$/); if (blockersMatch) { progress.blockers = blockersMatch[1] .split('\n') .filter(line => line.startsWith('- 🚫')) .map(line => line.substring(4).trim()) .filter(blocker => blocker.length > 0); } } catch (error) { console.warn('Failed to parse progress:', error.message); } return progress; } parseQualityMetrics(content) { const quality = {}; try { // Get the most recent quality assessment const assessments = content.split('## Quality Assessment -'); if (assessments.length > 1) { const latest = assessments[1]; quality.timestamp = latest.split('\n')[0].trim(); quality.agent = this.extractField(latest, 'Agent'); quality.story = this.extractField(latest, 'Story'); quality.realityAuditScore = this.extractField(latest, 'Reality Audit Score'); quality.overallQuality = this.extractField(latest, 'Overall Quality'); } } catch (error) { console.warn('Failed to parse quality metrics:', error.message); } return quality; } extractField(content, fieldName) { const regex = new RegExp(`\\*\\*${fieldName}:\\*\\* (.+)`, 'i'); const match = content.match(regex); return match ? match[1].trim() : ''; } filterContextForAgent(context, targetAgent) { const agentType = this.getAgentType(targetAgent); // Handle multi-role filtering if (this.multiRoleFilters[agentType]) { return this.filterMultiRoleContext(context, agentType); } // Handle single role filtering const filter = this.agentFilters[agentType] || this.agentFilters['dev']; // Default to dev filter const filtered = { shared: context.shared, decisions: this.filterDecisions(context.decisions, filter), progress: context.progress, quality: context.quality, relevantContent: this.extractRelevantContent(context, filter), roleType: 'single', primaryRole: agentType }; return filtered; } filterMultiRoleContext(context, multiRoleType) { const multiRole = this.multiRoleFilters[multiRoleType]; const primaryFilter = this.agentFilters[multiRole.primary]; const secondaryFilter = this.agentFilters[multiRole.secondary]; // Combine include patterns from both roles const combinedIncludePatterns = [ ...primaryFilter.includePatterns, ...secondaryFilter.includePatterns ]; // Use primary role's exclude patterns but remove conflicts with secondary role const combinedExcludePatterns = primaryFilter.excludePatterns.filter( pattern => !secondaryFilter.includePatterns.includes(pattern) ); const combinedFilter = { includePatterns: combinedIncludePatterns, excludePatterns: combinedExcludePatterns, requiredSections: [ ...primaryFilter.requiredSections, ...secondaryFilter.requiredSections ] }; const filtered = { shared: context.shared, decisions: this.filterDecisions(context.decisions, combinedFilter), progress: context.progress, quality: context.quality, relevantContent: this.extractRelevantContent(context, combinedFilter), roleType: 'multi', primaryRole: multiRole.primary, secondaryRole: multiRole.secondary, roleDescription: multiRole.description }; return filtered; } getAgentType(agentName) { const lowerName = agentName.toLowerCase(); // Check for multi-role patterns first (e.g., "dev-analyst", "qa+research") const multiRolePatterns = [ { pattern: ['dev', 'analyst'], type: 'dev-analyst' }, { pattern: ['qa', 'research'], type: 'qa-research' }, { pattern: ['architect', 'brainstorm'], type: 'architect-brainstorming' }, { pattern: ['pm', 'analyst'], type: 'pm-analyst' }, { pattern: ['ux', 'research'], type: 'ux-research' } ]; for (const multiRole of multiRolePatterns) { if (multiRole.pattern.every(part => lowerName.includes(part))) { return multiRole.type; } } // Check for specialized roles if (lowerName.includes('analyst') || lowerName.includes('analysis')) return 'analyst'; if (lowerName.includes('brainstorm') || lowerName.includes('creative')) return 'brainstorming'; if (lowerName.includes('research') || lowerName.includes('investigat')) return 'research'; // Check for primary roles if (lowerName.includes('dev') || lowerName.includes('developer')) return 'dev'; if (lowerName.includes('qa') || lowerName.includes('test')) return 'qa'; if (lowerName.includes('arch') || lowerName.includes('architect')) return 'architect'; if (lowerName.includes('pm') || lowerName.includes('manager')) return 'pm'; if (lowerName.includes('ux') || lowerName.includes('design')) return 'ux-expert'; return 'dev'; // Default fallback } filterDecisions(decisions, filter) { return decisions.filter(decision => { const decisionText = `${decision.title} ${decision.decision} ${decision.rationale} ${decision.impact}`.toLowerCase(); // Check if decision matches include patterns const matchesInclude = filter.includePatterns.some(pattern => decisionText.includes(pattern.toLowerCase()) ); // Check if decision matches exclude patterns const matchesExclude = filter.excludePatterns.some(pattern => decisionText.includes(pattern.toLowerCase()) ); return matchesInclude && !matchesExclude; }); } extractRelevantContent(context, filter) { const relevant = []; // Extract relevant next steps if (context.shared.nextSteps) { context.shared.nextSteps.forEach(step => { const stepText = step.toLowerCase(); const isRelevant = filter.includePatterns.some(pattern => stepText.includes(pattern.toLowerCase()) ); if (isRelevant) { relevant.push(`Next Step: ${step}`); } }); } // Extract relevant progress items if (context.progress.pendingTasks) { context.progress.pendingTasks.forEach(task => { const taskText = task.toLowerCase(); const isRelevant = filter.includePatterns.some(pattern => taskText.includes(pattern.toLowerCase()) ); if (isRelevant) { relevant.push(`Pending Task: ${task}`); } }); } return relevant; } async generateHandoffPackage(params) { const { handoffId, sourceAgent, targetAgent, timestamp, context, customContext } = params; const agentType = this.getAgentType(targetAgent); const nextActions = this.generateNextActions(context, agentType); const fileReferences = this.generateFileReferences(context); const blockers = this.extractBlockers(context); return `# Agent Handoff: ${sourceAgent} → ${targetAgent} **Created:** ${timestamp} **Handoff ID:** ${handoffId} **Source Agent:** ${sourceAgent} **Target Agent:** ${targetAgent} **Target Agent Type:** ${agentType} ## Context Summary ${context.shared.currentFocus || 'No current focus available.'} ${customContext.summary || ''} ## Key Decisions Made ${context.decisions.map(d => `- **${d.title}** (${d.agent}, ${d.date}): ${d.decision}`).join('\n') || '- No relevant decisions for this agent type'} ## Current Progress **Story:** ${context.progress.currentStory || 'No active story'} **Quality Score:** ${context.progress.qualityScore || 'Not assessed'} **Completed Tasks:** ${context.progress.completedTasks ? context.progress.completedTasks.map(task => `- ✅ ${task}`).join('\n') : '- No completed tasks'} **Pending Tasks:** ${context.progress.pendingTasks ? context.progress.pendingTasks.map(task => `- ⏳ ${task}`).join('\n') : '- No pending tasks'} ## Next Actions for ${targetAgent} ${nextActions.map(action => `- [ ] ${action}`).join('\n')} ## Files and References ${fileReferences.join('\n') || '- No specific file references available'} ## Blockers and Dependencies ${blockers.join('\n') || '- No blockers identified'} ## Quality Metrics ${context.quality.overallQuality ? `**Latest Quality Score:** ${context.quality.overallQuality}` : 'No quality metrics available'} ${context.quality.story ? `**Last Assessed Story:** ${context.quality.story}` : ''} ## Relevant Context ${context.relevantContent.map(item => `- ${item}`).join('\n') || '- No additional relevant context'} ## Handoff Validation - [ ] Context completeness verified - [ ] Decisions documented and relevant - [ ] Next actions clearly defined for ${agentType} role - [ ] References included - [ ] Quality metrics current - [ ] Agent-specific filtering applied - [ ] Blockers and dependencies identified ## Handoff Notes ${customContext.notes || 'No additional notes provided.'} --- *Generated by BMAD Agent Handoff System v1.0* *Handoff Quality Score: ${this.calculateHandoffScore(context, agentType)}/100* `; } generateNextActions(context, agentType) { const actions = []; // Handle multi-role actions if (this.multiRoleFilters[agentType]) { return this.generateMultiRoleActions(context, agentType); } // Handle single-role actions switch (agentType) { case 'dev': actions.push('Review technical requirements and architecture decisions'); actions.push('Examine current code implementation status'); actions.push('Address any pending technical tasks or bugs'); if (context.progress.blockers && context.progress.blockers.length > 0) { actions.push('Resolve identified blockers and technical dependencies'); } break; case 'qa': actions.push('Review acceptance criteria and testing requirements'); actions.push('Validate completed functionality against requirements'); actions.push('Execute test cases and identify quality issues'); actions.push('Update quality metrics and provide feedback'); break; case 'architect': actions.push('Review system design and architectural decisions'); actions.push('Validate technical approach and integration points'); actions.push('Assess scalability and performance implications'); actions.push('Document any new architectural requirements'); break; case 'pm': actions.push('Review project scope and timeline status'); actions.push('Assess stakeholder requirements and priority changes'); actions.push('Update project planning and resource allocation'); actions.push('Communicate progress to stakeholders'); break; case 'ux-expert': actions.push('Review user experience requirements and design specifications'); actions.push('Validate interface design and usability considerations'); actions.push('Assess user interaction patterns and feedback'); actions.push('Update design documentation and prototypes'); break; case 'analyst': actions.push('Analyze available data and identify key patterns'); actions.push('Generate insights from metrics and performance data'); actions.push('Create data visualization and trend analysis'); actions.push('Provide data-driven recommendations'); break; case 'brainstorming': actions.push('Explore creative alternatives and innovative approaches'); actions.push('Generate multiple solution options without constraints'); actions.push('Challenge existing assumptions and think outside the box'); actions.push('Facilitate ideation sessions and creative problem-solving'); break; case 'research': actions.push('Conduct comprehensive research on relevant topics'); actions.push('Investigate industry best practices and standards'); actions.push('Gather evidence and benchmark against competitors'); actions.push('Synthesize research findings into actionable insights'); break; default: actions.push('Review handoff context and understand current state'); actions.push('Identify specific tasks relevant to your role'); actions.push('Address any pending items in your domain'); } // Add context-specific actions if (context.shared.nextSteps) { context.shared.nextSteps.forEach(step => { if (!actions.some(action => action.toLowerCase().includes(step.toLowerCase().substring(0, 20)))) { actions.push(step); } }); } return actions.slice(0, 8); // Limit to 8 actions for readability } generateMultiRoleActions(context, multiRoleType) { const multiRole = this.multiRoleFilters[multiRoleType]; const actions = []; switch (multiRoleType) { case 'dev-analyst': actions.push('Analyze current system performance and identify optimization opportunities'); actions.push('Review code metrics and technical debt patterns'); actions.push('Implement data-driven development improvements'); actions.push('Create performance monitoring and analysis dashboards'); actions.push('Research and apply evidence-based development practices'); break; case 'qa-research': actions.push('Research industry testing standards and compliance frameworks'); actions.push('Investigate best practices for quality assurance methodologies'); actions.push('Analyze quality trends and benchmark against industry standards'); actions.push('Design comprehensive testing strategies based on research findings'); actions.push('Validate testing approaches through evidence-based research'); break; case 'architect-brainstorming': actions.push('Explore creative architectural patterns and innovative design approaches'); actions.push('Brainstorm multiple system design alternatives without constraints'); actions.push('Challenge conventional architecture assumptions'); actions.push('Generate innovative solutions for complex integration challenges'); actions.push('Facilitate collaborative design exploration sessions'); break; case 'pm-analyst': actions.push('Analyze project data to identify trends and optimization opportunities'); actions.push('Research stakeholder feedback and user behavior patterns'); actions.push('Create data-driven project prioritization and resource allocation'); actions.push('Generate insights from project metrics and timeline analysis'); actions.push('Develop evidence-based project planning and risk assessment'); break; case 'ux-research': actions.push('Conduct user research and usability studies'); actions.push('Investigate accessibility standards and inclusive design practices'); actions.push('Analyze user behavior data and interaction patterns'); actions.push('Research industry UX trends and best practices'); actions.push('Validate design decisions through evidence-based user research'); break; default: actions.push('Apply multi-role perspective to current challenges'); actions.push('Integrate primary and secondary role capabilities'); actions.push('Provide comprehensive analysis from multiple viewpoints'); } // Add context-specific actions if (context.shared.nextSteps) { context.shared.nextSteps.forEach(step => { if (!actions.some(action => action.toLowerCase().includes(step.toLowerCase().substring(0, 20)))) { actions.push(step); } }); } return actions.slice(0, 10); // Allow more actions for multi-role scenarios } generateFileReferences(context) { const references = []; // Add standard workspace references references.push('📁 `.workspace/context/shared-context.md` - Current workspace context'); references.push('📋 `.workspace/decisions/decisions-log.md` - Architectural decisions'); references.push('📈 `.workspace/progress/progress-summary.md` - Development progress'); references.push('📊 `.workspace/quality/quality-metrics.md` - Quality assessments'); // Add story-specific references if available if (context.progress.currentStory) { references.push(`📖 Story documentation for: ${context.progress.currentStory}`); } return references; } extractBlockers(context) { const blockers = []; if (context.progress.blockers && context.progress.blockers.length > 0) { context.progress.blockers.forEach(blocker => { blockers.push(`🚫 ${blocker}`); }); } // Check for decision-based blockers context.decisions.forEach(decision => { if (decision.status === 'pending' || decision.impact.toLowerCase().includes('blocker')) { blockers.push(`⚠️ Decision pending: ${decision.title}`); } }); return blockers; } validateHandoff(handoffContent, targetAgent) { const validation = { score: 0, maxScore: 100, issues: [], strengths: [] }; const agentType = this.getAgentType(targetAgent); const requiredSections = this.agentFilters[agentType]?.requiredSections || []; // Check required sections (30 points) let sectionsFound = 0; requiredSections.forEach(section => { if (handoffContent.toLowerCase().includes(section.toLowerCase())) { sectionsFound++; validation.strengths.push(`Required section present: ${section}`); } else { validation.issues.push(`Missing required section: ${section}`); } }); if (requiredSections.length > 0) { validation.score += (sectionsFound / requiredSections.length) * 30; } else { validation.score += 30; // No specific requirements } // Check context completeness (25 points) const hasContext = handoffContent.includes('## Context Summary') && handoffContent.length > 500; if (hasContext) { validation.score += 25; validation.strengths.push('Comprehensive context summary provided'); } else { validation.issues.push('Context summary incomplete or missing'); } // Check decisions documentation (20 points) const hasDecisions = handoffContent.includes('## Key Decisions Made'); if (hasDecisions) { validation.score += 20; validation.strengths.push('Key decisions documented'); } else { validation.issues.push('Key decisions not documented'); } // Check next actions (15 points) const hasNextActions = handoffContent.includes('## Next Actions for') && handoffContent.includes('- [ ]'); if (hasNextActions) { validation.score += 15; validation.strengths.push('Clear next actions defined'); } else { validation.issues.push('Next actions unclear or missing'); } // Check references (10 points) const hasReferences = handoffContent.includes('## Files and References'); if (hasReferences) { validation.score += 10; validation.strengths.push('File references provided'); } else { validation.issues.push('File references missing'); } validation.grade = this.scoreToGrade(validation.score); return validation; } scoreToGrade(score) { if (score >= 90) return 'A'; if (score >= 80) return 'B'; if (score >= 70) return 'C'; if (score >= 60) return 'D'; return 'F'; } calculateHandoffScore(context, agentType) { let score = 50; // Base score // Add points for context richness if (context.shared.currentFocus) score += 10; if (context.decisions.length > 0) score += 15; if (context.progress.currentStory) score += 10; if (context.quality.overallQuality) score += 10; if (context.relevantContent.length > 0) score += 5; return Math.min(score, 100); } async updateHandoffRegistry(handoffId, sourceAgent, targetAgent, validation) { try { const registryFile = path.join(this.handoffsPath, 'handoff-registry.json'); let registry = []; if (fs.existsSync(registryFile)) { const content = fs.readFileSync(registryFile, 'utf8'); registry = JSON.parse(content); } registry.push({ handoffId, sourceAgent, targetAgent, timestamp: new Date().toISOString(), validationScore: validation.score, grade: validation.grade, status: 'pending' }); // Keep only last 100 handoffs if (registry.length > 100) { registry = registry.slice(-100); } fs.writeFileSync(registryFile, JSON.stringify(registry, null, 2)); } catch (error) { console.error('Failed to update handoff registry:', error.message); } } async logHandoffEvent(event) { try { const auditFile = path.join(this.handoffsPath, 'audit-trail.md'); let auditContent = ''; if (fs.existsSync(auditFile)) { auditContent = fs.readFileSync(auditFile, 'utf8'); } else { auditContent = '# Handoff Audit Trail\n\n'; } const logEntry = `## Handoff ${event.handoffId} **Timestamp:** ${event.timestamp} **Source:** ${event.sourceAgent} **Target:** ${event.targetAgent} **Status:** ${event.status} **Validation Score:** ${event.validationScore}/100 **File:** ${event.filePath} --- `; auditContent += logEntry; fs.writeFileSync(auditFile, auditContent); } catch (error) { console.error('Failed to log handoff event:', error.message); } } async getPendingHandoffs(targetAgent = null) { try { const registryFile = path.join(this.handoffsPath, 'handoff-registry.json'); if (!fs.existsSync(registryFile)) { return []; } const content = fs.readFileSync(registryFile, 'utf8'); const registry = JSON.parse(content); let pending = registry.filter(handoff => handoff.status === 'pending'); if (targetAgent) { pending = pending.filter(handoff => handoff.targetAgent === targetAgent); } return pending.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); } catch (error) { console.error('Failed to get pending handoffs:', error.message); return []; } } async getHandoffStats() { try { const registryFile = path.join(this.handoffsPath, 'handoff-registry.json'); if (!fs.existsSync(registryFile)) { return { total: 0, pending: 0, avgScore: 0, gradeDistribution: {} }; } const content = fs.readFileSync(registryFile, 'utf8'); const registry = JSON.parse(content); const stats = { total: registry.length, pending: registry.filter(h => h.status === 'pending').length, avgScore: registry.reduce((sum, h) => sum + h.validationScore, 0) / registry.length, gradeDistribution: {} }; // Calculate grade distribution registry.forEach(handoff => { stats.gradeDistribution[handoff.grade] = (stats.gradeDistribution[handoff.grade] || 0) + 1; }); return stats; } catch (error) { console.error('Failed to get handoff stats:', error.message); return { total: 0, pending: 0, avgScore: 0, gradeDistribution: {} }; } } } module.exports = HandoffManager;