UNPKG

context-forge

Version:

AI orchestration platform with autonomous teams, enhancement planning, migration tools, 25+ slash commands, checkpoints & hooks. Multi-IDE: Claude, Cursor, Windsurf, Cline, Copilot

1,089 lines (1,080 loc) 44.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OrchestrationService = void 0; const tmuxManager_1 = require("./tmuxManager"); const agentCommunication_1 = require("./agentCommunication"); const selfScheduler_1 = require("./selfScheduler"); const gitDiscipline_1 = require("./gitDiscipline"); const path_1 = __importDefault(require("path")); const fs_extra_1 = __importDefault(require("fs-extra")); const chalk_1 = __importDefault(require("chalk")); const uuid_1 = require("uuid"); const handlebars_1 = __importDefault(require("handlebars")); const child_process_1 = require("child_process"); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class OrchestrationService { constructor(projectPath, config) { this.agents = new Map(); this.errors = []; this.tmux = new tmuxManager_1.TmuxManager(); this.orchestrationId = (0, uuid_1.v4)(); this.projectPath = projectPath; this.config = config; // Initialize services this.communication = new agentCommunication_1.AgentCommunicationService(config.communicationModel); this.scheduler = new selfScheduler_1.SelfSchedulingService(config.selfScheduling, this.tmux); this.gitDiscipline = new gitDiscipline_1.GitDisciplineService(config.gitDiscipline, projectPath); // Load briefing template const templatePath = path_1.default.join(__dirname, '../../templates/orchestration/agent-briefing.hbs'); const templateContent = fs_extra_1.default.readFileSync(templatePath, 'utf-8'); this.briefingTemplate = handlebars_1.default.compile(templateContent); this.status = { id: this.orchestrationId, projectName: config.projectName, startTime: new Date(), status: 'initializing', completedPhases: [], activeAgents: [], metrics: { totalAgents: 0, activeAgents: 0, tasksCompleted: 0, tasksPending: 0, gitCommits: 0, linesOfCodeWritten: 0, testsWritten: 0, testsPassing: 0, blockers: 0, uptime: '0h', }, lastUpdate: new Date(), }; // Set up event listeners this.setupEventListeners(); } /** * Set up event listeners for integrated services */ setupEventListeners() { // Communication events this.communication.on('message', (message) => { console.log(chalk_1.default.gray(`[${message.fromAgent} → ${message.toAgent}] ${message.type}`)); }); this.communication.on('escalation', (message) => { console.log(chalk_1.default.red(`🚨 Escalation from ${message.fromAgent}: ${message.content}`)); this.recordError({ timestamp: new Date(), agentId: message.fromAgent, type: 'communication', message: `Escalation: ${message.content}`, severity: 'warning', requiresIntervention: true, }); }); // Scheduler events this.scheduler.on('check-in', async (data) => { console.log(chalk_1.default.blue(`Check-in from ${data.agentId}`)); await this.handleAgentCheckIn(data.agentId); }); this.scheduler.on('schedule-failed', (data) => { this.recordError({ timestamp: new Date(), agentId: data.schedule.agentId, type: 'scheduling', message: `Schedule failed: ${data.error}`, severity: 'error', requiresIntervention: false, }); }); // Git discipline events this.gitDiscipline.on('commit', (data) => { this.status.metrics.gitCommits++; console.log(chalk_1.default.green(`✓ Git commit by ${data.agentId}`)); }); this.gitDiscipline.on('commit-failed', (data) => { this.recordError({ timestamp: new Date(), agentId: data.agentId, type: 'git', message: `Commit failed: ${data.error.message}`, severity: 'warning', requiresIntervention: false, }); }); } /** * Initialize and deploy the orchestration */ async deploy() { console.log(chalk_1.default.blue('🚀 Deploying orchestration...')); // Check tmux availability const tmuxAvailable = await this.tmux.checkTmuxAvailable(); if (!tmuxAvailable) { throw new Error('tmux is not installed. Please install tmux to use orchestration features.'); } // Create orchestration session const sessionName = `cf-${this.config.projectName.toLowerCase().replace(/\s+/g, '-')}`; if (await this.tmux.sessionExists(sessionName)) { console.log(chalk_1.default.yellow(`Session ${sessionName} already exists. Using existing session.`)); } else { await this.tmux.createSession(sessionName, this.projectPath); } // Deploy agents based on strategy if (this.config.strategy === 'big-bang') { await this.deployAllAgents(sessionName); } else if (this.config.strategy === 'phased') { await this.deployPhasedAgents(sessionName); } else { await this.deployAdaptiveAgents(sessionName); } // Initialize git discipline if enabled if (this.config.gitDiscipline.enabled) { await this.gitDiscipline.initialize(); } // Self-scheduling is initialized per-agent during deployment this.status.status = 'running'; await this.saveStatus(); } /** * Deploy all agents at once */ async deployAllAgents(sessionName) { const team = this.config.teamStructure; // Deploy orchestrator await this.deployAgent(sessionName, team.orchestrator, 0); // Deploy project managers let windowIndex = 1; for (const pm of team.projectManagers) { await this.deployAgent(sessionName, pm, windowIndex++); } // Deploy developers for (const dev of team.developers) { await this.deployAgent(sessionName, dev, windowIndex++); } // Deploy QA engineers if (team.qaEngineers) { for (const qa of team.qaEngineers) { await this.deployAgent(sessionName, qa, windowIndex++); } } // Deploy other roles const otherRoles = [ team.devops, team.codeReviewers, team.researchers, team.documentationWriters, ] .filter(Boolean) .flat(); for (const agent of otherRoles) { if (agent) { await this.deployAgent(sessionName, agent, windowIndex++); } } } /** * Deploy agents in phases */ async deployPhasedAgents(sessionName) { // Start with orchestrator and first PM const team = this.config.teamStructure; await this.deployAgent(sessionName, team.orchestrator, 0); if (team.projectManagers.length > 0) { await this.deployAgent(sessionName, team.projectManagers[0], 1); } // Additional agents will be deployed based on phase requirements console.log(chalk_1.default.yellow('Phased deployment initialized. Additional agents will be deployed as needed.')); } /** * Deploy agents adaptively based on workload */ async deployAdaptiveAgents(sessionName) { // Start with minimal team const team = this.config.teamStructure; await this.deployAgent(sessionName, team.orchestrator, 0); console.log(chalk_1.default.yellow('Adaptive deployment initialized. Agents will be added based on workload.')); } /** * Deploy a single agent */ async deployAgent(sessionName, agentConfig, windowIndex) { console.log(chalk_1.default.gray(`Deploying ${agentConfig.role}: ${agentConfig.name}...`)); const windowName = `${agentConfig.role}-${agentConfig.id}`; // Create window if it doesn't exist const windows = await this.tmux.getSessionWindows(sessionName); const existingWindow = windows.find((w) => w.windowIndex === windowIndex); if (!existingWindow) { const windowConfig = { sessionName, windowIndex, windowName, workingDirectory: this.projectPath, command: 'claude', }; await this.tmux.createWindow(windowConfig); } else { // Rename existing window await this.tmux.renameWindow(sessionName, windowIndex, windowName); } // Wait for Claude to start await new Promise((resolve) => setTimeout(resolve, 5000)); // Send agent briefing const briefing = this.generateAgentBriefing(agentConfig); await this.briefAgent(sessionName, windowIndex, briefing); // Create agent session const session = { agentId: agentConfig.id, sessionName, windowIndex, windowName, status: 'active', startTime: new Date(), lastActivity: new Date(), completedTasks: 0, gitCommits: 0, messagesExchanged: 0, }; this.agents.set(agentConfig.id, session); this.status.activeAgents.push(session); this.status.metrics.totalAgents++; this.status.metrics.activeAgents++; // Register agent in communication hierarchy this.communication.registerAgentHierarchy(agentConfig.id, agentConfig.reportingTo); // Set up message handler for this agent this.communication.subscribe(agentConfig.id, async (message) => { await this.handleAgentMessage(agentConfig.id, message); }); // Start git auto-commit if (this.config.gitDiscipline.enabled) { this.gitDiscipline.startAutoCommit(agentConfig.id, agentConfig.role, this.orchestrationId); } // Schedule first check-in if (this.config.selfScheduling.enabled) { await this.scheduler.scheduleAgentCheckIn(session, undefined, // Use default interval `Initial check-in for ${agentConfig.name}`); } } /** * Generate agent briefing */ generateAgentBriefing(agent) { const gitInstructions = this.config.gitDiscipline.enabled ? this.generateGitInstructions() : 'Git discipline not required for this session.'; const schedulingInstructions = this.config.selfScheduling.enabled ? this.generateSchedulingInstructions() : 'Self-scheduling not required for this session.'; return { agentId: agent.id, role: agent.role, projectContext: `You are working on ${this.config.projectName}. ${agent.briefing}`, objectives: agent.responsibilities, constraints: agent.constraints || [], communicationProtocol: this.generateCommunicationProtocol(agent), gitInstructions, schedulingInstructions, escalationCriteria: this.generateEscalationCriteria(agent.role), successCriteria: this.generateSuccessCriteria(agent.role), resources: this.gatherResources(), }; } /** * Brief an agent with their instructions */ async briefAgent(sessionName, windowIndex, briefing) { const message = this.formatBriefingMessage(briefing); await this.tmux.sendClaudeMessage(sessionName, windowIndex, message); } /** * Format briefing message for Claude */ formatBriefingMessage(briefing) { const context = { agentId: briefing.agentId, role: briefing.role, projectContext: briefing.projectContext, objectives: briefing.objectives, communicationProtocol: briefing.communicationProtocol, gitInstructions: briefing.gitInstructions, schedulingInstructions: briefing.schedulingInstructions, escalationCriteria: briefing.escalationCriteria, successCriteria: briefing.successCriteria, resources: briefing.resources, constraints: briefing.constraints || [], timestamp: new Date().toISOString(), orchestrationId: this.orchestrationId, projectName: this.config.projectName, }; return this.briefingTemplate(context); } /** * Generate git instructions based on config */ generateGitInstructions() { const { gitDiscipline } = this.config; return `MANDATORY Git Discipline: - Auto-commit every ${gitDiscipline.autoCommitInterval} minutes - Use ${gitDiscipline.branchingStrategy} branching strategy - Commit message format: ${gitDiscipline.commitMessageFormat} - ${gitDiscipline.requireTests ? 'All code must have tests' : 'Tests optional'} - ${gitDiscipline.requireReview ? 'Code review required before merge' : 'Direct commits allowed'} - NEVER work more than 1 hour without committing - Create feature branches for new work - Tag stable versions before major changes`; } /** * Generate scheduling instructions */ generateSchedulingInstructions() { const { selfScheduling } = this.config; return `Self-Scheduling Protocol: - Schedule next check-in every ${selfScheduling.defaultCheckInterval} minutes - Use schedule_with_note.sh script for scheduling - ${selfScheduling.adaptiveScheduling ? 'Adjust schedule based on workload' : 'Fixed interval scheduling'} - Min interval: ${selfScheduling.minCheckInterval} minutes - Max interval: ${selfScheduling.maxCheckInterval} minutes - Recovery strategy: ${selfScheduling.recoveryStrategy}`; } /** * Generate communication protocol for agent */ generateCommunicationProtocol(agent) { const model = this.config.communicationModel; if (model === 'hub-and-spoke') { if (agent.role === 'project-manager') { return 'You are a communication hub. Aggregate reports from team members and report to orchestrator.'; } else if (agent.role === 'orchestrator') { return 'You receive reports from project managers. Do not communicate directly with developers.'; } else { return `Report to your project manager (${agent.reportingTo}). Do not communicate directly with other agents.`; } } else if (model === 'hierarchical') { return `Report to ${agent.reportingTo || 'orchestrator'}. You may receive instructions from higher-level agents.`; } else { return 'Direct communication with any team member is allowed when necessary.'; } } /** * Generate escalation criteria based on role */ generateEscalationCriteria(role) { const baseCriteria = [ 'Blocked for more than 10 minutes', 'Critical error or bug discovered', 'Architecture decision needed', 'Security vulnerability found', ]; const roleCriteria = { orchestrator: [ 'Project timeline at risk', 'Resource allocation needed', 'Cross-team conflict', ], 'project-manager': [ 'Team member blocked', 'Quality standards not met', 'Schedule slippage detected', ], developer: [ 'Implementation approach unclear', 'Missing requirements', 'Technical debt accumulating', ], 'qa-engineer': [ 'Critical bugs found', 'Test coverage below threshold', 'Performance regression', ], devops: ['Deployment failure', 'Infrastructure issues', 'Security concerns'], 'code-reviewer': [ 'Code quality issues', 'Security vulnerabilities', 'Best practices violations', ], researcher: [ 'Technology decision needed', 'Conflicting information found', 'Research blocked', ], 'documentation-writer': [ 'Documentation gaps identified', 'API changes not documented', 'User guide outdated', ], }; return [...baseCriteria, ...(roleCriteria[role] || [])]; } /** * Generate success criteria based on role */ generateSuccessCriteria(role) { const roleCriteria = { orchestrator: [ 'All phases completed successfully', 'Team operating efficiently', 'Project goals achieved', ], 'project-manager': [ 'Team productivity maintained', 'Quality standards met', 'No critical blockers', ], developer: ['Features implemented to spec', 'Tests passing', 'Code reviewed and approved'], 'qa-engineer': [ 'Test coverage above threshold', 'No critical bugs', 'Performance benchmarks met', ], devops: ['Deployments successful', 'Infrastructure stable', 'Security measures in place'], 'code-reviewer': ['Code quality maintained', 'Best practices followed', 'No security issues'], researcher: [ 'Research questions answered', 'Recommendations provided', 'Documentation created', ], 'documentation-writer': ['Documentation complete', 'Examples provided', 'User guide updated'], }; return roleCriteria[role] || ['Tasks completed successfully']; } /** * Gather resources for agents */ gatherResources() { const resources = ['CLAUDE.md - Project guidelines', 'README.md - Project overview']; // Check for PRPs const prpPath = path_1.default.join(this.projectPath, 'PRPs'); if (fs_extra_1.default.existsSync(prpPath)) { resources.push('PRPs/ - Project requirement documents'); } // Check for docs const docsPath = path_1.default.join(this.projectPath, 'docs'); if (fs_extra_1.default.existsSync(docsPath)) { resources.push('docs/ - Project documentation'); } // Add orchestration-specific resources resources.push('Use /orchestrate-status to check team status', 'Use /feature-status to check feature progress'); return resources; } /** * Initialize git discipline */ async initializeGitDiscipline() { console.log(chalk_1.default.blue('Initializing git discipline...')); // Create git hooks directory const hooksPath = path_1.default.join(this.projectPath, '.git', 'hooks'); await fs_extra_1.default.ensureDir(hooksPath); // Create auto-commit script const autoCommitScript = this.generateAutoCommitScript(); const scriptPath = path_1.default.join(this.projectPath, '.claude', 'orchestration', 'auto-commit.sh'); await fs_extra_1.default.ensureDir(path_1.default.dirname(scriptPath)); await fs_extra_1.default.writeFile(scriptPath, autoCommitScript); await fs_extra_1.default.chmod(scriptPath, 0o755); console.log(chalk_1.default.green('Git discipline initialized')); } /** * Generate auto-commit script */ generateAutoCommitScript() { const interval = this.config.gitDiscipline.autoCommitInterval; return `#!/bin/bash # Auto-commit script for orchestration while true; do sleep ${interval * 60} # Check if there are changes if [[ -n $(git status -s) ]]; then # Add all changes git add -A # Create commit message TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S") git commit -m "Auto-commit: Progress update at $TIMESTAMP" echo "Auto-commit completed at $TIMESTAMP" fi done `; } /** * Initialize self-scheduling */ async initializeSelfScheduling() { console.log(chalk_1.default.blue('Initializing self-scheduling...')); // Create scheduling script const scheduleScript = this.generateScheduleScript(); const scriptPath = path_1.default.join(this.projectPath, '.claude', 'orchestration', 'schedule.sh'); await fs_extra_1.default.ensureDir(path_1.default.dirname(scriptPath)); await fs_extra_1.default.writeFile(scriptPath, scheduleScript); await fs_extra_1.default.chmod(scriptPath, 0o755); console.log(chalk_1.default.green('Self-scheduling initialized')); } /** * Generate schedule script */ generateScheduleScript() { const { defaultCheckInterval, minCheckInterval, maxCheckInterval } = this.config.selfScheduling; return `#!/bin/bash # Self-scheduling script for orchestration MINUTES=\${1:-${defaultCheckInterval}} NOTE=\${2:-"Regular orchestration check"} TARGET=\${3:-"cf-orchestrator:0"} # Validate interval if [ \$MINUTES -lt ${minCheckInterval} ]; then MINUTES=${minCheckInterval} elif [ \$MINUTES -gt ${maxCheckInterval} ]; then MINUTES=${maxCheckInterval} fi # Create note file echo "=== Next Check Note ($(date)) ===" > .claude/orchestration/next_check.txt echo "Scheduled for: \$MINUTES minutes" >> .claude/orchestration/next_check.txt echo "" >> .claude/orchestration/next_check.txt echo "\$NOTE" >> .claude/orchestration/next_check.txt # Schedule the check SECONDS=\$(( \$MINUTES * 60 )) nohup bash -c "sleep \$SECONDS && tmux send-keys -t \$TARGET 'Time for orchestration check! cat .claude/orchestration/next_check.txt' && sleep 1 && tmux send-keys -t \$TARGET Enter" > /dev/null 2>&1 & echo "Scheduled check in \$MINUTES minutes" `; } /** * Get orchestration status */ async getStatus() { // Update metrics await this.updateMetrics(); return this.status; } /** * Update orchestration metrics */ async updateMetrics() { // Update active agent count this.status.metrics.activeAgents = Array.from(this.agents.values()).filter((agent) => agent.status === 'active').length; // Calculate uptime const uptime = Date.now() - this.status.startTime.getTime(); const hours = Math.floor(uptime / (1000 * 60 * 60)); const minutes = Math.floor((uptime % (1000 * 60 * 60)) / (1000 * 60)); this.status.metrics.uptime = `${hours}h ${minutes}m`; this.status.lastUpdate = new Date(); } /** * Save orchestration status */ async saveStatus() { const statusPath = path_1.default.join(this.projectPath, '.claude', 'orchestration', 'status.json'); await fs_extra_1.default.ensureDir(path_1.default.dirname(statusPath)); await fs_extra_1.default.writeJSON(statusPath, this.status, { spaces: 2 }); } /** * Monitor agent health */ async monitorAgents() { for (const [agentId, session] of this.agents) { try { const content = await this.tmux.captureWindowContent(session.sessionName, session.windowIndex, 20); // Check for activity if (content.includes('Error') || content.includes('error')) { session.status = 'error'; this.recordError({ timestamp: new Date(), agentId, type: 'agent-crash', message: 'Error detected in agent output', severity: 'error', requiresIntervention: true, }); } else if (Date.now() - session.lastActivity.getTime() > 30 * 60 * 1000) { // No activity for 30 minutes session.status = 'idle'; } else { session.status = 'active'; } } catch (error) { session.status = 'error'; this.recordError({ timestamp: new Date(), agentId, type: 'communication', message: `Failed to monitor agent: ${error}`, severity: 'warning', requiresIntervention: false, }); } } } /** * Record an orchestration error */ recordError(error) { this.errors.push(error); if (error.severity === 'critical' || error.requiresIntervention) { console.log(chalk_1.default.red(`🚨 Orchestration Error: ${error.message}`)); } } /** * Handle agent message */ async handleAgentMessage(agentId, message) { const session = this.agents.get(agentId); if (!session) { console.error(chalk_1.default.red(`Unknown agent: ${agentId}`)); return; } // Update activity session.lastActivity = new Date(); session.messagesExchanged++; // Process based on message type switch (message.type) { case 'status-update': console.log(chalk_1.default.blue(`Status from ${agentId}: ${message.content}`)); break; case 'task-completed': session.completedTasks++; this.status.metrics.tasksCompleted++; console.log(chalk_1.default.green(`✓ Task completed by ${agentId}: ${message.content}`)); break; case 'task-blocked': session.status = 'blocked'; this.status.metrics.blockers++; console.log(chalk_1.default.red(`⚠ ${agentId} blocked: ${message.content}`)); break; case 'code-review-request': console.log(chalk_1.default.yellow(`Code review requested by ${agentId}`)); // Forward to code reviewers await this.forwardToCodeReviewers(message); break; case 'deployment-request': console.log(chalk_1.default.cyan(`Deployment requested by ${agentId}`)); // Forward to DevOps await this.forwardToDevOps(message); break; case 'escalation': this.recordError({ timestamp: new Date(), agentId, type: 'escalation', message: message.content, severity: 'warning', requiresIntervention: true, }); break; } // Send to recipient via tmux if different agent if (message.toAgent !== 'orchestrator' && message.toAgent !== agentId) { const recipientSession = this.agents.get(message.toAgent); if (recipientSession) { const formattedMessage = this.formatAgentMessage(message); await this.tmux.sendClaudeMessage(recipientSession.sessionName, recipientSession.windowIndex, formattedMessage); } } } /** * Handle agent check-in */ async handleAgentCheckIn(agentId) { const session = this.agents.get(agentId); if (!session) { return; } // Capture recent output const output = await this.tmux.captureWindowContent(session.sessionName, session.windowIndex, 50); // Analyze output for status const hasErrors = output.toLowerCase().includes('error'); const isBlocked = output.toLowerCase().includes('blocked'); const isWaiting = output.toLowerCase().includes('waiting'); // Update session status if (hasErrors) { session.status = 'error'; } else if (isBlocked) { session.status = 'blocked'; } else if (isWaiting) { session.status = 'idle'; } else { session.status = 'active'; } // Update last activity session.lastActivity = new Date(); // Schedule next check-in with adaptive interval if (this.config.selfScheduling.adaptiveScheduling) { const interval = this.calculateAdaptiveInterval(session.status); await this.scheduler.scheduleAgentCheckIn(session, interval); } else { await this.scheduler.scheduleAgentCheckIn(session); } } /** * Calculate adaptive check-in interval based on agent status */ calculateAdaptiveInterval(status) { const { minCheckInterval, maxCheckInterval, defaultCheckInterval } = this.config.selfScheduling; switch (status) { case 'blocked': case 'error': return minCheckInterval; // Check frequently when issues case 'idle': return maxCheckInterval; // Check less frequently when idle case 'active': default: return defaultCheckInterval; } } /** * Format agent message for display */ formatAgentMessage(message) { const timestamp = new Date(message.timestamp).toLocaleTimeString(); return `\n[${timestamp}] Message from ${message.fromAgent}:\nType: ${message.type}\n${message.content}\n${message.metadata ? `\nMetadata: ${JSON.stringify(message.metadata, null, 2)}` : ''}`; } /** * Forward message to code reviewers */ async forwardToCodeReviewers(message) { const reviewers = Array.from(this.agents.values()).filter((agent) => { const config = this.getAgentConfig(agent.agentId); return config?.role === 'code-reviewer'; }); for (const reviewer of reviewers) { await this.communication.sendMessage(message.fromAgent, reviewer.agentId, 'code-review-request', message.content, message.metadata); } } /** * Forward message to DevOps */ async forwardToDevOps(message) { const devops = Array.from(this.agents.values()).filter((agent) => { const config = this.getAgentConfig(agent.agentId); return config?.role === 'devops'; }); for (const devopsAgent of devops) { await this.communication.sendMessage(message.fromAgent, devopsAgent.agentId, 'deployment-request', message.content, message.metadata); } } /** * Get agent config by ID */ getAgentConfig(agentId) { const team = this.config.teamStructure; const allAgents = [ team.orchestrator, ...team.projectManagers, ...team.developers, ...(team.qaEngineers || []), ...(team.devops || []), ...(team.codeReviewers || []), ...(team.researchers || []), ...(team.documentationWriters || []), ]; return allAgents.find((agent) => agent.id === agentId); } /** * Stop orchestration */ async stop() { console.log(chalk_1.default.yellow('Stopping orchestration...')); // Stop all auto-commit intervals this.gitDiscipline.cleanup(); // Cancel all scheduled check-ins await this.scheduler.cancelAllSchedules(); this.status.status = 'completed'; this.status.endTime = new Date(); await this.saveStatus(); // Archive agent logs await this.archiveAgentLogs(); // Generate final report await this.generateFinalReport(); console.log(chalk_1.default.green('Orchestration stopped')); } /** * Archive agent conversation logs */ async archiveAgentLogs() { const logsPath = path_1.default.join(this.projectPath, '.claude', 'orchestration', 'logs'); await fs_extra_1.default.ensureDir(logsPath); for (const [agentId, session] of this.agents) { try { const content = await this.tmux.captureWindowContent(session.sessionName, session.windowIndex, this.tmux['maxLinesCapture']); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const logFile = path_1.default.join(logsPath, `${agentId}_${timestamp}.log`); await fs_extra_1.default.writeFile(logFile, content); } catch (error) { console.log(chalk_1.default.red(`Failed to archive logs for agent ${agentId}: ${error}`)); } } } /** * Generate final orchestration report */ async generateFinalReport() { const reportPath = path_1.default.join(this.projectPath, '.claude', 'orchestration', 'reports', `final-report-${this.orchestrationId}.md`); await fs_extra_1.default.ensureDir(path_1.default.dirname(reportPath)); // Load report template const templatePath = path_1.default.join(__dirname, '../../templates/orchestration/status-report.hbs'); const templateContent = await fs_extra_1.default.readFile(templatePath, 'utf-8'); const reportTemplate = handlebars_1.default.compile(templateContent); // Gather report data const reportData = await this.gatherReportData(); // Generate report const report = reportTemplate(reportData); await fs_extra_1.default.writeFile(reportPath, report); console.log(chalk_1.default.green(`Final report saved to: ${reportPath}`)); } /** * Gather data for final report */ async gatherReportData() { const gitStats = this.gitDiscipline.getStats(); const recentCommits = await this.gitDiscipline.getRecentCommits(5); const communicationStats = this.communication.getStats(); // Calculate team composition const teamComposition = this.calculateTeamComposition(); // Calculate agent productivity const agentProductivity = this.calculateAgentProductivity(); // Get recent communications const recentCommunications = this.communication.getRecentMessages(5).map((msg) => ({ fromAgent: msg.fromAgent, toAgent: msg.toAgent, type: msg.type, summary: msg.content.substring(0, 50) + '...', })); return { timestamp: new Date().toISOString(), projectName: this.config.projectName, status: this.status.status, uptime: this.status.metrics.uptime, totalAgents: this.status.metrics.totalAgents, activeAgents: this.status.metrics.activeAgents, teamComposition, tasksCompleted: this.status.metrics.tasksCompleted, tasksPending: this.status.metrics.tasksPending, completionRate: this.calculateCompletionRate(), gitCommits: gitStats.totalCommits, linesOfCodeWritten: this.status.metrics.linesOfCodeWritten, filesModified: await this.countModifiedFiles(), testsWritten: this.status.metrics.testsWritten, testsPassing: this.status.metrics.testsPassing, testCoverage: await this.calculateTestCoverage(), codeReviewPassRate: this.calculateCodeReviewPassRate(), blockers: this.status.metrics.blockers, errors: this.errors.length, escalations: this.errors.filter((e) => e.type === 'escalation').length, agents: this.formatAgentStatuses(), recentCommits: recentCommits.map((c) => ({ hash: c.hash, message: c.message, agent: c.author, })), recentCommunications, currentPhase: this.formatCurrentPhase(), completedPhases: this.status.completedPhases, agentProductivity, totalMessages: communicationStats.totalMessages, avgResponseTime: communicationStats.averageResponseTime, communicationModel: this.config.communicationModel, blockedMessages: communicationStats.blockedMessages, autoCommitCompliance: gitStats.complianceRate, branchStrategy: this.config.gitDiscipline.branchingStrategy, activeBranches: await this.countActiveBranches(), recommendations: this.generateRecommendations(), nextSteps: this.generateNextSteps(), }; } /** * Calculate team composition */ calculateTeamComposition() { const composition = {}; for (const [, session] of this.agents) { const config = this.getAgentConfig(session.agentId); if (config) { composition[config.role] = (composition[config.role] || 0) + 1; } } return Object.entries(composition) .map(([role, count]) => ({ role, count })) .sort((a, b) => b.count - a.count); } /** * Calculate agent productivity */ calculateAgentProductivity() { const productivity = []; const uptime = Date.now() - this.status.startTime.getTime(); const hours = uptime / (1000 * 60 * 60); for (const [agentId, session] of this.agents) { const config = this.getAgentConfig(agentId); productivity.push({ agent: config?.name || agentId, tasksPerHour: (session.completedTasks / hours).toFixed(2), commitsPerHour: (session.gitCommits / hours).toFixed(2), }); } return productivity.sort((a, b) => parseFloat(b.tasksPerHour) - parseFloat(a.tasksPerHour)); } /** * Calculate completion rate */ calculateCompletionRate() { const total = this.status.metrics.tasksCompleted + this.status.metrics.tasksPending; return total > 0 ? Math.round((this.status.metrics.tasksCompleted / total) * 100) : 0; } /** * Count modified files */ async countModifiedFiles() { try { const { stdout } = await execAsync('git diff --name-only HEAD~10..HEAD | wc -l', { cwd: this.projectPath, }); return parseInt(stdout.trim()) || 0; } catch { return 0; } } /** * Calculate test coverage */ async calculateTestCoverage() { // This would normally integrate with a test coverage tool // For now, return a calculated estimate const { testsWritten, testsPassing } = this.status.metrics; return testsWritten > 0 ? Math.round((testsPassing / testsWritten) * 85) // Assume 85% coverage when tests pass : 0; } /** * Calculate code review pass rate */ calculateCodeReviewPassRate() { // This would normally track actual code review outcomes // For now, use error rate as a proxy const codeErrors = this.errors.filter((e) => e.type === 'code-quality' || e.message.includes('review')).length; return Math.max(0, 100 - codeErrors * 10); } /** * Format agent statuses for report */ formatAgentStatuses() { const statuses = []; for (const [agentId, session] of this.agents) { const config = this.getAgentConfig(agentId); const blocker = this.errors .filter((e) => e.agentId === agentId && e.type === 'task-blocked') .pop(); statuses.push({ name: config?.name || agentId, role: config?.role || 'unknown', status: session.status, sessionName: session.sessionName, windowIndex: session.windowIndex, currentTask: 'Task tracking not implemented', // Would need task tracking lastActivity: session.lastActivity.toISOString(), completedTasks: session.completedTasks, gitCommits: session.gitCommits, messagesExchanged: session.messagesExchanged, blockerDescription: blocker?.message, }); } return statuses; } /** * Format current phase info */ formatCurrentPhase() { // This would track actual phase progress // For now, return placeholder if (this.status.completedPhases.length === 0) { return { name: 'Initial Development', progress: 25, tasksComplete: this.status.metrics.tasksCompleted, totalTasks: this.status.metrics.tasksCompleted + this.status.metrics.tasksPending, estimatedCompletion: 'In Progress', }; } return null; } /** * Count active branches */ async countActiveBranches() { try { const { stdout } = await execAsync('git branch -r | wc -l', { cwd: this.projectPath }); return parseInt(stdout.trim()) || 1; } catch { return 1; } } /** * Generate recommendations based on metrics */ generateRecommendations() { const recommendations = []; // Check for blocked agents const blockedAgents = Array.from(this.agents.values()).filter((a) => a.status === 'blocked').length; if (blockedAgents > 0) { recommendations.push(`${blockedAgents} agent(s) are blocked. Consider manual intervention.`); } // Check error rate if (this.errors.length > 10) { recommendations.push('High error rate detected. Review error logs for patterns.'); } // Check git compliance const gitStats = this.gitDiscipline.getStats(); if (gitStats.complianceRate < 80) { recommendations.push('Git commit compliance below 80%. Agents may need reminders.'); } // Check productivity const idleAgents = Array.from(this.agents.values()).filter((a) => a.status === 'idle').length; if (idleAgents > this.agents.size * 0.3) { recommendations.push('Over 30% of agents are idle. Consider task redistribution.'); } return recommendations; } /** * Generate next steps */ generateNextSteps() { const steps = []; if (this.status.status === 'completed') { steps.push('Review final report and agent logs', 'Merge feature branches to main', 'Tag release version', 'Archive orchestration data'); } else { steps.push('Monitor agent progress', 'Address any blockers', 'Review code quality metrics', 'Prepare for next phase'); } return steps; } /** * Generate orchestration summary */ async generateSummary() { const status = await this.getStatus(); const gitStats = this.gitDiscipline.getStats(); const activeAgents = Array.from(this.agents.values()).filter((a) => a.status === 'active').length; return `📊 Orchestration Status: • Project: ${this.config.projectName} • Active Agents: ${activeAgents}/${status.metrics.totalAgents} • Tasks: ${status.metrics.tasksCompleted} completed, ${status.metrics.tasksPending} pending • Git: ${gitStats.totalCommits} commits (${gitStats.complianceRate.toFixed(0)}% compliance) • Uptime: ${status.metrics.uptime}`; } } exports.OrchestrationService = OrchestrationService; //# sourceMappingURL=orchestrationService.js.map