UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

884 lines (765 loc) 27.8 kB
import { EventEmitter } from 'events'; import { writeFile, readFile, mkdir, readdir, stat } from 'fs/promises'; import { join } from 'path'; import { Logger } from '../core/logger.js'; import { ConfigManager } from '../core/config.js'; export interface ProjectPhase { id: string; name: string; description: string; status: 'planned' | 'in-progress' | 'completed' | 'blocked' | 'cancelled'; startDate?: Date; endDate?: Date; estimatedDuration: number; // in hours actualDuration?: number; dependencies: string[]; assignedTeam: string[]; deliverables: string[]; risks: ProjectRisk[]; milestones: ProjectMilestone[]; budget: { estimated: number; actual: number; currency: string; }; resources: ProjectResource[]; completionPercentage: number; qualityMetrics: { testCoverage: number; codeQuality: number; documentation: number; securityScore: number; }; } export interface ProjectRisk { id: string; description: string; probability: 'low' | 'medium' | 'high'; impact: 'low' | 'medium' | 'high'; mitigation: string; status: 'open' | 'mitigated' | 'closed'; assignedTo: string; createdAt: Date; updatedAt: Date; } export interface ProjectMilestone { id: string; name: string; description: string; targetDate: Date; actualDate?: Date; status: 'pending' | 'achieved' | 'missed' | 'at-risk'; dependencies: string[]; deliverables: string[]; successCriteria: string[]; } export interface ProjectResource { id: string; name: string; type: 'human' | 'infrastructure' | 'software' | 'hardware'; availability: number; // percentage cost: { amount: number; currency: string; period: 'hour' | 'day' | 'week' | 'month'; }; skills: string[]; allocation: { phaseId: string; percentage: number; startDate: Date; endDate: Date; }[]; } export interface Project { id: string; name: string; description: string; type: 'web-app' | 'api' | 'microservice' | 'infrastructure' | 'research' | 'migration' | 'custom'; status: 'planning' | 'active' | 'on-hold' | 'completed' | 'cancelled'; priority: 'low' | 'medium' | 'high' | 'critical'; owner: string; stakeholders: string[]; phases: ProjectPhase[]; budget: { total: number; spent: number; remaining: number; currency: string; }; timeline: { plannedStart: Date; plannedEnd: Date; actualStart?: Date; actualEnd?: Date; }; tags: string[]; metadata: Record<string, any>; createdAt: Date; updatedAt: Date; auditLog: ProjectAuditEntry[]; collaboration: { teamMembers: TeamMember[]; communication: CommunicationChannel[]; sharedResources: string[]; }; qualityGates: QualityGate[]; complianceRequirements: ComplianceRequirement[]; } export interface TeamMember { id: string; name: string; email: string; role: string; skills: string[]; availability: number; permissions: string[]; joinDate: Date; status: 'active' | 'inactive' | 'on-leave'; } export interface CommunicationChannel { id: string; name: string; type: 'slack' | 'teams' | 'email' | 'webhook' | 'custom'; configuration: Record<string, any>; isActive: boolean; } export interface QualityGate { id: string; name: string; phase: string; criteria: { metric: string; threshold: number; operator: '>' | '<' | '>=' | '<=' | '==' | '!='; }[]; status: 'pending' | 'passed' | 'failed' | 'skipped'; executedAt?: Date; results: Record<string, number>; } export interface ComplianceRequirement { id: string; name: string; framework: string; // e.g., 'SOC2', 'GDPR', 'HIPAA', 'PCI-DSS' description: string; status: 'not-started' | 'in-progress' | 'compliant' | 'non-compliant'; evidence: string[]; reviewer: string; reviewDate?: Date; dueDate: Date; } export interface ProjectAuditEntry { id: string; timestamp: Date; userId: string; action: string; target: string; details: Record<string, any>; ipAddress?: string; userAgent?: string; } export interface ProjectMetrics { totalProjects: number; activeProjects: number; completedProjects: number; averageProjectDuration: number; budgetVariance: number; resourceUtilization: number; qualityScore: number; riskScore: number; teamProductivity: number; customerSatisfaction: number; } export interface ProjectReport { id: string; projectId: string; type: 'status' | 'financial' | 'quality' | 'risk' | 'resource' | 'compliance'; title: string; summary: string; details: Record<string, any>; recommendations: string[]; generatedAt: Date; generatedBy: string; format: 'json' | 'pdf' | 'html' | 'csv'; recipients: string[]; } export class ProjectManager extends EventEmitter { private projects: Map<string, Project> = new Map(); private projectsPath: string; private logger: Logger; private config: ConfigManager; constructor(projectsPath: string = './projects', logger?: Logger, config?: ConfigManager) { super(); this.projectsPath = projectsPath; this.logger = logger || new Logger({ level: 'info', format: 'text', destination: 'console' }); this.config = config || ConfigManager.getInstance(); } async initialize(): Promise<void> { try { await mkdir(this.projectsPath, { recursive: true }); await this.loadProjects(); this.logger.info('Project Manager initialized successfully'); } catch (error) { this.logger.error('Failed to initialize Project Manager', { error }); throw error; } } async createProject(projectData: Partial<Project>): Promise<Project> { const project: Project = { id: projectData.id || `project-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, name: projectData.name || 'Unnamed Project', description: projectData.description || '', type: projectData.type || 'custom', status: 'planning', priority: projectData.priority || 'medium', owner: projectData.owner || 'system', stakeholders: projectData.stakeholders || [], phases: projectData.phases || [], budget: projectData.budget || { total: 0, spent: 0, remaining: 0, currency: 'USD', }, timeline: { plannedStart: projectData.timeline?.plannedStart || new Date(), plannedEnd: projectData.timeline?.plannedEnd || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days actualStart: projectData.timeline?.actualStart, actualEnd: projectData.timeline?.actualEnd, }, tags: projectData.tags || [], metadata: projectData.metadata || {}, createdAt: new Date(), updatedAt: new Date(), auditLog: [], collaboration: { teamMembers: [], communication: [], sharedResources: [], }, qualityGates: [], complianceRequirements: [], }; // Add initial audit entry this.addAuditEntry(project, 'system', 'project_created', 'project', { projectId: project.id, projectName: project.name, }); this.projects.set(project.id, project); await this.saveProject(project); this.emit('project:created', project); this.logger.info(`Project created: ${project.name} (${project.id})`); return project; } async updateProject(projectId: string, updates: Partial<Project>): Promise<Project> { const project = this.projects.get(projectId); if (!project) { throw new Error(`Project not found: ${projectId}`); } const updatedProject = { ...project, ...updates, updatedAt: new Date() }; // Add audit entry this.addAuditEntry(updatedProject, 'system', 'project_updated', 'project', { projectId, changes: Object.keys(updates), }); this.projects.set(projectId, updatedProject); await this.saveProject(updatedProject); this.emit('project:updated', updatedProject); this.logger.info(`Project updated: ${project.name} (${projectId})`); return updatedProject; } async deleteProject(projectId: string, userId: string = 'system'): Promise<void> { const project = this.projects.get(projectId); if (!project) { throw new Error(`Project not found: ${projectId}`); } // Add audit entry before deletion this.addAuditEntry(project, userId, 'project_deleted', 'project', { projectId, projectName: project.name, }); this.projects.delete(projectId); // Archive project instead of deleting const archivePath = join(this.projectsPath, 'archived'); await mkdir(archivePath, { recursive: true }); await writeFile(join(archivePath, `${projectId}.json`), JSON.stringify(project, null, 2)); this.emit('project:deleted', { projectId, project }); this.logger.info(`Project archived: ${project.name} (${projectId})`); } async getProject(projectId: string): Promise<Project | null> { return this.projects.get(projectId) || null; } async listProjects(filters?: { status?: Project['status']; type?: Project['type']; priority?: Project['priority']; owner?: string; tags?: string[]; }): Promise<Project[]> { let projects = Array.from(this.projects.values()); if (filters) { if (filters.status) { projects = projects.filter((p) => p.status === filters.status); } if (filters.type) { projects = projects.filter((p) => p.type === filters.type); } if (filters.priority) { projects = projects.filter((p) => p.priority === filters.priority); } if (filters.owner) { projects = projects.filter((p) => p.owner === filters.owner); } if (filters.tags && filters.tags.length > 0) { projects = projects.filter((p) => filters.tags!.some((tag) => p.tags.includes(tag))); } } return projects.sort( (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), ); } async addPhase(projectId: string, phase: Omit<ProjectPhase, 'id'>): Promise<ProjectPhase> { const project = this.projects.get(projectId); if (!project) { throw new Error(`Project not found: ${projectId}`); } const newPhase: ProjectPhase = { id: `phase-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, ...phase, }; project.phases.push(newPhase); project.updatedAt = new Date(); this.addAuditEntry(project, 'system', 'phase_added', 'phase', { projectId, phaseId: newPhase.id, phaseName: newPhase.name, }); await this.saveProject(project); this.emit('phase:added', { project, phase: newPhase }); return newPhase; } async updatePhase( projectId: string, phaseId: string, updates: Partial<ProjectPhase>, ): Promise<ProjectPhase> { const project = this.projects.get(projectId); if (!project) { throw new Error(`Project not found: ${projectId}`); } const phaseIndex = project.phases.findIndex((p) => p.id === phaseId); if (phaseIndex === -1) { throw new Error(`Phase not found: ${phaseId}`); } const updatedPhase = { ...project.phases[phaseIndex], ...updates }; project.phases[phaseIndex] = updatedPhase; project.updatedAt = new Date(); this.addAuditEntry(project, 'system', 'phase_updated', 'phase', { projectId, phaseId, changes: Object.keys(updates), }); await this.saveProject(project); this.emit('phase:updated', { project, phase: updatedPhase }); return updatedPhase; } async addTeamMember(projectId: string, member: TeamMember): Promise<void> { const project = this.projects.get(projectId); if (!project) { throw new Error(`Project not found: ${projectId}`); } project.collaboration.teamMembers.push(member); project.updatedAt = new Date(); this.addAuditEntry(project, 'system', 'team_member_added', 'team', { projectId, memberId: member.id, memberName: member.name, }); await this.saveProject(project); this.emit('team:member_added', { project, member }); } async removeTeamMember(projectId: string, memberId: string): Promise<void> { const project = this.projects.get(projectId); if (!project) { throw new Error(`Project not found: ${projectId}`); } const memberIndex = project.collaboration.teamMembers.findIndex((m) => m.id === memberId); if (memberIndex === -1) { throw new Error(`Team member not found: ${memberId}`); } const member = project.collaboration.teamMembers[memberIndex]; project.collaboration.teamMembers.splice(memberIndex, 1); project.updatedAt = new Date(); this.addAuditEntry(project, 'system', 'team_member_removed', 'team', { projectId, memberId, memberName: member.name, }); await this.saveProject(project); this.emit('team:member_removed', { project, memberId }); } async getProjectMetrics(projectId?: string): Promise<ProjectMetrics> { const projects = projectId ? ([this.projects.get(projectId)].filter(Boolean) as Project[]) : Array.from(this.projects.values()); const totalProjects = projects.length; const activeProjects = projects.filter((p) => p.status === 'active').length; const completedProjects = projects.filter((p) => p.status === 'completed').length; const completedProjectsWithDuration = projects.filter( (p) => p.status === 'completed' && p.timeline.actualStart && p.timeline.actualEnd, ); const averageProjectDuration = completedProjectsWithDuration.length > 0 ? completedProjectsWithDuration.reduce((sum, p) => { const duration = p.timeline.actualEnd!.getTime() - p.timeline.actualStart!.getTime(); return sum + duration / (1000 * 60 * 60 * 24); // Convert to days }, 0) / completedProjectsWithDuration.length : 0; const budgetVariance = projects.reduce((sum, p) => { if (p.budget.total > 0) { return sum + (p.budget.spent - p.budget.total) / p.budget.total; } return sum; }, 0) / Math.max(projects.length, 1); const resourceUtilization = projects.reduce((sum, p) => { const totalResources = p.phases.reduce( (phaseSum, phase) => phaseSum + phase.resources.length, 0, ); const utilizedResources = p.phases.reduce( (phaseSum, phase) => phaseSum + phase.resources.filter((r) => r.availability > 0).length, 0, ); return sum + (totalResources > 0 ? utilizedResources / totalResources : 0); }, 0) / Math.max(projects.length, 1); const qualityScore = projects.reduce((sum, p) => { const phaseQuality = p.phases.reduce((phaseSum, phase) => { const metrics = phase.qualityMetrics; return ( phaseSum + (metrics.testCoverage + metrics.codeQuality + metrics.documentation + metrics.securityScore) / 4 ); }, 0) / Math.max(p.phases.length, 1); return sum + phaseQuality; }, 0) / Math.max(projects.length, 1); return { totalProjects, activeProjects, completedProjects, averageProjectDuration, budgetVariance, resourceUtilization, qualityScore, riskScore: 0, // Calculate based on risk assessment teamProductivity: 0, // Calculate based on velocity metrics customerSatisfaction: 0, // Calculate based on feedback }; } async generateReport( projectId: string, type: ProjectReport['type'], userId: string = 'system', ): Promise<ProjectReport> { const project = this.projects.get(projectId); if (!project) { throw new Error(`Project not found: ${projectId}`); } const report: ProjectReport = { id: `report-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, projectId, type, title: `${type.toUpperCase()} Report - ${project.name}`, summary: '', details: {}, recommendations: [], generatedAt: new Date(), generatedBy: userId, format: 'json', recipients: [], }; switch (type) { case 'status': report.summary = `Project ${project.name} is currently ${project.status}`; report.details = { status: project.status, progress: this.calculateProjectProgress(project), phases: project.phases.map((p) => ({ name: p.name, status: p.status, completion: p.completionPercentage, })), timeline: project.timeline, nextMilestones: this.getUpcomingMilestones(project), }; break; case 'financial': report.summary = `Budget utilization: ${((project.budget.spent / project.budget.total) * 100).toFixed(1)}%`; report.details = { budget: project.budget, costBreakdown: this.calculateCostBreakdown(project), variance: project.budget.spent - project.budget.total, projectedCost: this.projectFinalCost(project), }; break; case 'quality': const qualityMetrics = this.calculateQualityMetrics(project); report.summary = `Overall quality score: ${qualityMetrics.overall.toFixed(1)}%`; report.details = { qualityMetrics, qualityGates: project.qualityGates, recommendations: this.generateQualityRecommendations(project), }; break; case 'risk': const risks = this.getAllRisks(project); report.summary = `${risks.filter((r) => r.status === 'open').length} open risks identified`; report.details = { risks, riskMatrix: this.generateRiskMatrix(risks), mitigation: this.generateRiskMitigation(risks), }; break; case 'resource': report.summary = `${project.collaboration.teamMembers.length} team members, ${this.getTotalResources(project)} resources allocated`; report.details = { teamMembers: project.collaboration.teamMembers, resourceAllocation: this.calculateResourceAllocation(project), utilization: this.calculateResourceUtilization(project), capacity: this.calculateCapacity(project), }; break; case 'compliance': const compliance = this.calculateComplianceStatus(project); report.summary = `${compliance.compliant} of ${compliance.total} requirements met`; report.details = { requirements: project.complianceRequirements, status: compliance, gaps: this.identifyComplianceGaps(project), recommendations: this.generateComplianceRecommendations(project), }; break; } this.addAuditEntry(project, userId, 'report_generated', 'report', { projectId, reportId: report.id, reportType: type, }); this.emit('report:generated', { project, report }); return report; } private async loadProjects(): Promise<void> { try { const files = await readdir(this.projectsPath); const projectFiles = files.filter((f) => f.endsWith('.json') && !f.startsWith('.')); for (const file of projectFiles) { try { const content = await readFile(join(this.projectsPath, file), 'utf-8'); const project: Project = JSON.parse(content); this.projects.set(project.id, project); } catch (error) { this.logger.error(`Failed to load project file: ${file}`, { error }); } } this.logger.info(`Loaded ${this.projects.size} projects`); } catch (error) { this.logger.error('Failed to load projects', { error }); } } private async saveProject(project: Project): Promise<void> { const filePath = join(this.projectsPath, `${project.id}.json`); await writeFile(filePath, JSON.stringify(project, null, 2)); } private addAuditEntry( project: Project, userId: string, action: string, target: string, details: Record<string, any>, ): void { const entry: ProjectAuditEntry = { id: `audit-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date(), userId, action, target, details, }; project.auditLog.push(entry); } private calculateProjectProgress(project: Project): number { if (project.phases.length === 0) return 0; const totalProgress = project.phases.reduce( (sum, phase) => sum + phase.completionPercentage, 0, ); return totalProgress / project.phases.length; } private getUpcomingMilestones(project: Project): ProjectMilestone[] { const allMilestones = project.phases.flatMap((p) => p.milestones); const now = new Date(); return allMilestones .filter((m) => m.status === 'pending' && m.targetDate > now) .sort((a, b) => a.targetDate.getTime() - b.targetDate.getTime()) .slice(0, 5); } private calculateCostBreakdown(project: Project): Record<string, number> { const breakdown: Record<string, number> = {}; for (const phase of project.phases) { for (const resource of phase.resources) { const category = resource.type; const cost = resource.cost.amount; breakdown[category] = (breakdown[category] || 0) + cost; } } return breakdown; } private projectFinalCost(project: Project): number { const progress = this.calculateProjectProgress(project); if (progress === 0) return project.budget.total; return (project.budget.spent / progress) * 100; } private calculateQualityMetrics(project: Project): any { const allMetrics = project.phases.map((p) => p.qualityMetrics); if (allMetrics.length === 0) { return { overall: 0, testCoverage: 0, codeQuality: 0, documentation: 0, securityScore: 0 }; } const averages = { testCoverage: allMetrics.reduce((sum, m) => sum + m.testCoverage, 0) / allMetrics.length, codeQuality: allMetrics.reduce((sum, m) => sum + m.codeQuality, 0) / allMetrics.length, documentation: allMetrics.reduce((sum, m) => sum + m.documentation, 0) / allMetrics.length, securityScore: allMetrics.reduce((sum, m) => sum + m.securityScore, 0) / allMetrics.length, }; const overall = (averages.testCoverage + averages.codeQuality + averages.documentation + averages.securityScore) / 4; return { overall, ...averages }; } private generateQualityRecommendations(project: Project): string[] { const recommendations: string[] = []; const metrics = this.calculateQualityMetrics(project); if (metrics.testCoverage < 80) { recommendations.push('Increase test coverage to at least 80%'); } if (metrics.codeQuality < 70) { recommendations.push('Improve code quality through refactoring and code reviews'); } if (metrics.documentation < 60) { recommendations.push('Enhance documentation coverage for better maintainability'); } if (metrics.securityScore < 85) { recommendations.push( 'Address security vulnerabilities and implement security best practices', ); } return recommendations; } private getAllRisks(project: Project): ProjectRisk[] { return project.phases.flatMap((p) => p.risks); } private generateRiskMatrix(risks: ProjectRisk[]): any { const matrix = { low: { low: 0, medium: 0, high: 0 }, medium: { low: 0, medium: 0, high: 0 }, high: { low: 0, medium: 0, high: 0 }, }; for (const risk of risks) { if (risk.status === 'open') { matrix[risk.probability][risk.impact]++; } } return matrix; } private generateRiskMitigation(risks: ProjectRisk[]): any { const openRisks = risks.filter((r) => r.status === 'open'); const highPriorityRisks = openRisks.filter( (r) => (r.probability === 'high' && r.impact === 'high') || (r.probability === 'high' && r.impact === 'medium') || (r.probability === 'medium' && r.impact === 'high'), ); return { totalRisks: risks.length, openRisks: openRisks.length, highPriorityRisks: highPriorityRisks.length, mitigationActions: highPriorityRisks.map((r) => ({ risk: r.description, mitigation: r.mitigation, assignedTo: r.assignedTo, })), }; } private getTotalResources(project: Project): number { return project.phases.reduce((sum, phase) => sum + phase.resources.length, 0); } private calculateResourceAllocation(project: Project): any { const allocation: Record<string, number> = {}; for (const phase of project.phases) { for (const resource of phase.resources) { allocation[resource.type] = (allocation[resource.type] || 0) + 1; } } return allocation; } private calculateResourceUtilization(project: Project): any { const utilization: Record<string, number> = {}; for (const phase of project.phases) { for (const resource of phase.resources) { utilization[resource.type] = (utilization[resource.type] || 0) + resource.availability; } } return utilization; } private calculateCapacity(project: Project): any { const teamSize = project.collaboration.teamMembers.length; const totalAvailability = project.collaboration.teamMembers.reduce( (sum, member) => sum + member.availability, 0, ); return { teamSize, totalAvailability, averageAvailability: teamSize > 0 ? totalAvailability / teamSize : 0, }; } private calculateComplianceStatus(project: Project): any { const requirements = project.complianceRequirements; const total = requirements.length; const compliant = requirements.filter((r) => r.status === 'compliant').length; const inProgress = requirements.filter((r) => r.status === 'in-progress').length; const nonCompliant = requirements.filter((r) => r.status === 'non-compliant').length; return { total, compliant, inProgress, nonCompliant, compliancePercentage: total > 0 ? (compliant / total) * 100 : 0, }; } private identifyComplianceGaps(project: Project): ComplianceRequirement[] { return project.complianceRequirements.filter( (r) => r.status === 'not-started' || r.status === 'non-compliant', ); } private generateComplianceRecommendations(project: Project): string[] { const gaps = this.identifyComplianceGaps(project); const recommendations: string[] = []; for (const gap of gaps) { recommendations.push( `Address ${gap.framework} requirement: ${gap.name} (Due: ${gap.dueDate.toLocaleDateString()})`, ); } return recommendations; } }