@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
426 lines (370 loc) • 11 kB
text/typescript
/**
* 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();