UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

426 lines (370 loc) 11 kB
/** * Configuration Control Board (CCB) Core Models * Handles data management for change requests, assessments, and decisions */ import { ChangeRequest, ConfigurationItem, ImpactAssessment, CCBDecision, ChangeRequestStatus, Priority, ChangeType, CCBConfiguration, CCBMetrics, Comment, } from "../types/ccb.js"; export class ChangeRequestModel { private static nextId = 1; static create( data: Omit< ChangeRequest, "id" | "status" | "createdAt" | "updatedAt" | "comments" | "attachments" > ): ChangeRequest { const now = new Date(); return { id: `CR-${String(this.nextId++).padStart(6, "0")}`, status: "submitted" as ChangeRequestStatus, comments: [], attachments: [], createdAt: now, updatedAt: now, ...data, }; } static updateStatus( request: ChangeRequest, newStatus: ChangeRequestStatus ): ChangeRequest { return { ...request, status: newStatus, updatedAt: new Date(), }; } static addComment( request: ChangeRequest, comment: Omit<Comment, "id" | "createdAt"> ): ChangeRequest { const newComment: Comment = { id: `COMMENT-${Date.now()}`, createdAt: new Date(), ...comment, }; return { ...request, comments: [...request.comments, newComment], updatedAt: new Date(), }; } static assignReviewer( request: ChangeRequest, reviewerId: string ): ChangeRequest { return { ...request, assignedReviewer: reviewerId, status: "under_review" as ChangeRequestStatus, updatedAt: new Date(), }; } static validateRequest(request: Partial<ChangeRequest>): string[] { const errors: string[] = []; if (!request.title?.trim()) { errors.push("Title is required"); } if (!request.description?.trim()) { errors.push("Description is required"); } if (!request.requesterId?.trim()) { errors.push("Requester ID is required"); } if (!request.changeType) { errors.push("Change type is required"); } if (!request.priority) { errors.push("Priority is required"); } if (!request.justification?.trim()) { errors.push("Justification is required"); } if (!request.affectedItems || request.affectedItems.length === 0) { errors.push("At least one affected item must be specified"); } return errors; } } export class ConfigurationItemModel { private static items: Map<string, ConfigurationItem> = new Map(); static register(item: ConfigurationItem): void { this.items.set(item.id, item); } static get(id: string): ConfigurationItem | undefined { return this.items.get(id); } static getAll(): ConfigurationItem[] { return Array.from(this.items.values()); } static getByType(type: ConfigurationItem["type"]): ConfigurationItem[] { return Array.from(this.items.values()).filter((item) => item.type === type); } static updateBaseline(id: string, newBaseline: string): boolean { const item = this.items.get(id); if (!item) return false; const updatedItem = { ...item, baseline: newBaseline, lastModified: new Date(), }; this.items.set(id, updatedItem); return true; } static getDependents(itemId: string): ConfigurationItem[] { return Array.from(this.items.values()).filter((item) => item.dependencies.includes(itemId) ); } static getDependencies(itemId: string): ConfigurationItem[] { const item = this.items.get(itemId); if (!item) return []; return item.dependencies .map((depId) => this.items.get(depId)) .filter((item): item is ConfigurationItem => item !== undefined); } // Initialize with AgentC Starter Kit components static initializeDefaults(): void { const components = [ { id: "hero-section", name: "HeroSection Component", type: "component" as const, currentVersion: "0.2.5", baseline: "0.2.5", lastModified: new Date(), owner: "frontend-team", dependencies: [], criticalityLevel: "high" as const, }, { id: "cta-section", name: "CTASection Component", type: "component" as const, currentVersion: "0.2.5", baseline: "0.2.5", lastModified: new Date(), owner: "frontend-team", dependencies: [], criticalityLevel: "medium" as const, }, { id: "pricing-section", name: "PricingSection Component", type: "component" as const, currentVersion: "0.2.5", baseline: "0.2.5", lastModified: new Date(), owner: "frontend-team", dependencies: [], criticalityLevel: "high" as const, }, { id: "features-section", name: "FeaturesSection Component", type: "component" as const, currentVersion: "0.2.5", baseline: "0.2.5", lastModified: new Date(), owner: "frontend-team", dependencies: [], criticalityLevel: "medium" as const, }, ]; components.forEach((component) => this.register(component)); } } export class ImpactAssessmentModel { static create( data: Omit<ImpactAssessment, "id" | "assessmentDate" | "approved"> ): ImpactAssessment { return { id: `IA-${Date.now()}`, assessmentDate: new Date(), approved: false, ...data, }; } static approve( assessment: ImpactAssessment, approver: string ): ImpactAssessment { return { ...assessment, approved: true, approvedBy: approver, approvedAt: new Date(), }; } static calculateOverallRisk( assessment: ImpactAssessment ): "low" | "medium" | "high" | "critical" { const riskScores = assessment.impacts.map((impact) => { const severityScore = { minimal: 1, moderate: 2, significant: 3, major: 4, }[impact.severity]; const likelihoodScore = { low: 1, medium: 2, high: 3, }[impact.likelihood]; return severityScore * likelihoodScore; }); const maxScore = Math.max(...riskScores); const avgScore = riskScores.reduce((sum, score) => sum + score, 0) / riskScores.length; if (maxScore >= 12 || avgScore >= 9) return "critical"; if (maxScore >= 9 || avgScore >= 6) return "high"; if (maxScore >= 6 || avgScore >= 4) return "medium"; return "low"; } } export class CCBDecisionModel { static create(data: Omit<CCBDecision, "id" | "decisionDate">): CCBDecision { return { id: `DEC-${Date.now()}`, decisionDate: new Date(), ...data, }; } static calculateVoteResult( decision: CCBDecision, configuration: CCBConfiguration ): { passed: boolean; totalWeight: number; approvalWeight: number; percentage: number; } { const totalWeight = decision.votes.reduce((sum, vote) => { const member = decision.votingMembers.find((m) => m.id === vote.memberId); return sum + (member?.votingPower || 0); }, 0); const approvalWeight = decision.votes .filter((vote) => vote.vote === "approve") .reduce((sum, vote) => { const member = decision.votingMembers.find( (m) => m.id === vote.memberId ); return sum + (member?.votingPower || 0); }, 0); const percentage = totalWeight > 0 ? (approvalWeight / totalWeight) * 100 : 0; const passed = percentage >= configuration.votingThreshold; return { passed, totalWeight, approvalWeight, percentage, }; } } export class CCBMetricsModel { static calculate(requests: ChangeRequest[]): CCBMetrics { const totalRequests = requests.length; const pendingRequests = requests.filter( (r) => !["approved", "rejected", "closed", "implemented"].includes(r.status) ).length; const approvedRequests = requests.filter( (r) => r.status === "approved" ).length; const rejectedRequests = requests.filter( (r) => r.status === "rejected" ).length; // Calculate average processing time for completed requests const completedRequests = requests.filter((r) => ["approved", "rejected", "closed", "implemented"].includes(r.status) ); const averageProcessingTime = completedRequests.length > 0 ? completedRequests.reduce((sum, request) => { const completionDate = request.decision?.decisionDate || request.updatedAt; const processingDays = Math.ceil( (completionDate.getTime() - request.createdAt.getTime()) / (1000 * 60 * 60 * 24) ); return sum + processingDays; }, 0) / completedRequests.length : 0; // Group by priority const requestsByPriority: Record<Priority, number> = { low: 0, medium: 0, high: 0, critical: 0, }; requests.forEach((request) => { requestsByPriority[request.priority]++; }); // Group by type const requestsByType: Record<ChangeType, number> = { component_modification: 0, template_change: 0, configuration_update: 0, database_schema: 0, api_modification: 0, security_update: 0, performance_optimization: 0, }; requests.forEach((request) => { requestsByType[request.changeType]++; }); // Impact distribution (simplified) const impactDistribution = { low_impact: requests.filter((r) => r.priority === "low").length, medium_impact: requests.filter((r) => r.priority === "medium").length, high_impact: requests.filter((r) => r.priority === "high").length, critical_impact: requests.filter((r) => r.priority === "critical").length, }; return { totalRequests, pendingRequests, approvedRequests, rejectedRequests, averageProcessingTime, requestsByPriority, requestsByType, impactDistribution, }; } } export class CCBConfigurationModel { private static defaultConfig: CCBConfiguration = { quorumSize: 3, votingThreshold: 60, // 60% approval required defaultReviewDays: 5, escalationThreshold: 10, meetingSchedule: "0 14 * * 2,4", // Tuesdays and Thursdays at 2 PM notificationSettings: { emailEnabled: true, slackEnabled: false, notifyOnSubmission: true, notifyOnDecision: true, notifyOnEscalation: true, reminderDaysBefore: 1, }, }; static getDefault(): CCBConfiguration { return { ...this.defaultConfig }; } static update(updates: Partial<CCBConfiguration>): CCBConfiguration { this.defaultConfig = { ...this.defaultConfig, ...updates }; return this.getDefault(); } } // Initialize default configuration items ConfigurationItemModel.initializeDefaults();