From 0722d35164544bba2e791fc4c3cd7a23b4b62059 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 10:14:16 +0000 Subject: [PATCH] =?UTF-8?q?=EA=B3=84=ED=9A=8D=20=EA=B3=84=EC=95=BD=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=EC=84=9C=20=EC=A0=95=EC=9D=98=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EC=A4=80=20=EB=B6=84=EC=84=9D=20?= =?UTF-8?q?(iss-6da9ba883259)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- planning-contract/contract-validator.ts | 300 ++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 planning-contract/contract-validator.ts diff --git a/planning-contract/contract-validator.ts b/planning-contract/contract-validator.ts new file mode 100644 index 0000000..837b14f --- /dev/null +++ b/planning-contract/contract-validator.ts @@ -0,0 +1,300 @@ +/** + * 계획 계약 검증기 (Planning Contract Validator) + * + * 조항별 검증 로직을 구현하여 계획의 계약 충족 여부를 판단한다. + */ + +import { Plan, ClauseResult, ContractGrade, Epitaph } from './types'; + +/** + * 조항 1: 목표 명확성 검증 + */ +export function validateGoalClarity(plan: Plan): ClauseResult { + const issues: string[] = []; + + // 목표가 존재하는지 확인 + if (!plan.goal) { + issues.push('목표가 정의되지 않음'); + return { passed: false, issues }; + } + + // 목표가 구체적인지 확인 (모호한 표현 체크) + const vaguePatterns = [ + /좋게|잘|빠르게|최적으로|최대한/g, + /better|good|faster|optimal|best/g + ]; + + for (const pattern of vaguePatterns) { + if (pattern.test(plan.goal.description)) { + issues.push(`모호한 표현 감지: "${plan.goal.description.match(pattern)?.[0]}"`); + } + } + + // 측정 가능한 기준 존재 확인 + if (!plan.goal.criteria || plan.goal.criteria.length === 0) { + issues.push('목표 달성 판단 기준이 없음'); + } + + // 단일 책임 원칙 확인 + if (plan.goal.subGoals && plan.goal.subGoals.length > 2) { + issues.push('두 개 이상의 독립적 목표 포함 (단일 책임 위반)'); + } + + return { + passed: issues.length === 0, + issues + }; +} + +/** + * 조항 2: 단계 완전성 검증 + */ +export function validateStepCompleteness(plan: Plan): ClauseResult { + const issues: string[] = []; + + if (!plan.steps || plan.steps.length === 0) { + issues.push('단계가 정의되지 않음'); + return { passed: false, issues }; + } + + // 각 단계의 선행 조건 확인 + for (let i = 0; i < plan.steps.length; i++) { + const step = plan.steps[i]; + + if (step.dependencies) { + for (const depId of step.dependencies) { + const depIndex = plan.steps.findIndex(s => s.id === depId); + if (depIndex === -1) { + issues.push(`단계 ${step.id}: 존재하지 않는 의존성 ${depId}`); + } else if (depIndex >= i) { + issues.push(`단계 ${step.id}: 의존성 ${depId}가 순서 위반 (${depIndex} >= ${i})`); + } + } + } + } + + // 최종 단계에서 목표 달성 확인 + const lastStep = plan.steps[plan.steps.length - 1]; + if (lastStep && plan.goal) { + if (!lastStep.produces?.some(p => plan.goal?.produces?.includes(p))) { + // 목표 달성을 위한产出가 최종 단계에 없음 + const hasGoalConnection = plan.steps.some(step => + step.produces?.some(p => plan.goal?.produces?.includes(p)) + ); + if (!hasGoalConnection) { + issues.push('어떤 단계도 목표 달성에 기여하지 않음'); + } + } + } + + return { + passed: issues.length === 0, + issues + }; +} + +/** + * 조항 3: 실행 가능성 검증 + */ +export function validateExecutability(plan: Plan, availableCommands: string[]): ClauseResult { + const issues: string[] = []; + + if (!plan.steps) return { passed: false, issues: ['단계 없음'] }; + + for (const step of plan.steps) { + // 명령어 존재 확인 + if (step.command && !availableCommands.includes(step.command)) { + issues.push(`단계 ${step.id}: 명령어 "${step.command}" 사용 불가`); + } + + // 리소스 확인 + if (step.requiredResources) { + for (const resource of step.requiredResources) { + if (!availableCommands.includes(resource)) { + issues.push(`단계 ${step.id}: 필요한 리소스 "${resource}" 없음`); + } + } + } + + // 타임아웃 확인 + if (!step.timeout || step.timeout <= 0) { + issues.push(`단계 ${step.id}: 타임아웃 미설정`); + } + } + + return { + passed: issues.length === 0, + issues + }; +} + +/** + * 조항 4: 리스크 평가 검증 + */ +export function validateRiskAssessment(plan: Plan): ClauseResult { + const issues: string[] = []; + + if (!plan.risks || plan.risks.length === 0) { + issues.push('리스크 식별 없음'); + } + + if (plan.risks) { + for (const risk of plan.risks) { + // 리스크에 대한 완화 전략 확인 + if (!risk.mitigation) { + issues.push(`리스크 "${risk.description}": 완화 전략 없음`); + } + + // 대안 경로 확인 (중간 리스크 이상) + if (risk.severity === 'high' || risk.severity === 'critical') { + if (!risk.alternativePath) { + issues.push(`고위험 리스크 "${risk.description}": 대안 경로 없음`); + } + } + } + } + + return { + passed: issues.length === 0, + issues + }; +} + +/** + * 조항 5: 검증 가능성 검증 + */ +export function validateVerifiability(plan: Plan): ClauseResult { + const issues: string[] = []; + + if (!plan.steps) return { passed: false, issues: ['단계 없음'] }; + + for (const step of plan.steps) { + // 예상 결과 확인 + if (!step.expectedOutcome) { + issues.push(`단계 ${step.id}: 예상 결과 미정의`); + } + + // 어설션 확인 + if (!step.assertions || step.assertions.length === 0) { + issues.push(`단계 ${step.id}: 검증 어설션 없음`); + } + } + + return { + passed: issues.length === 0, + issues + }; +} + +/** + * 조항 6: 시간적 제약 검증 + */ +export function validateTemporalConstraints(plan: Plan): ClauseResult { + const issues: string[] = []; + + // 전체 예상 소요 시간 확인 + if (!plan.estimatedDuration) { + issues.push('전체 예상 소요 시간 미정의'); + } + + // 각 단계 타임아웃 확인 + if (plan.steps) { + for (const step of plan.steps) { + if (!step.timeout || step.timeout <= 0) { + issues.push(`단계 ${step.id}: 타임아웃 없음`); + } + } + } + + // 시간 초과 시 대체 전략 확인 + if (plan.timeoutStrategy) { + issues.push('시간 초과 시 대체 전략 없음'); + } + + return { + passed: issues.length === 0, + issues + }; +} + +/** + * 조항 7: 롤백 가능성 검증 + */ +export function validateRollbackCapability(plan: Plan): ClauseResult { + const issues: string[] = []; + + // 롤백 명령 정의 확인 + if (!plan.rollbackCommands || plan.rollbackCommands.length === 0) { + issues.push('롤백 명령 미정의'); + } + + // 롤백 시퀀스 순서 확인 + if (plan.rollbackCommands) { + for (let i = 0; i < plan.rollbackCommands.length; i++) { + const cmd = plan.rollbackCommands[i]; + if (!cmd.order || cmd.order !== plan.rollbackCommands.length - i) { + issues.push(`롤백 명령 ${cmd.id}: 순서 미지정 또는 역순 아님`); + } + } + } + + // 롤백 성공 여부 검증 방법 확인 + if (plan.rollbackCommands) { + for (const cmd of plan.rollbackCommands) { + if (!cmd.verification) { + issues.push(`롤백 명령 ${cmd.id}: 성공 여부 검증 방법 없음`); + } + } + } + + return { + passed: issues.length === 0, + issues + }; +} + +/** + * 전체 계약 검증 + */ +export function validateContract( + plan: Plan, + availableCommands: string[] = [] +): { grade: ContractGrade; results: Record } { + const results: Record = { + goalClarity: validateGoalClarity(plan), + stepCompleteness: validateStepCompleteness(plan), + executability: validateExecutability(plan, availableCommands), + riskAssessment: validateRiskAssessment(plan), + verifiability: validateVerifiability(plan), + temporalConstraints: validateTemporalConstraints(plan), + rollbackCapability: validateRollbackCapability(plan) + }; + + const passedCount = Object.values(results).filter(r => r.passed).length; + + let grade: ContractGrade; + if (passedCount === 7) grade = 'A'; + else if (passedCount >= 5) grade = 'B'; + else if (passedCount >= 3) grade = 'C'; + else if (passedCount >= 1) grade = 'D'; + else grade = 'F'; + + return { grade, results }; +} + +/** + * 에피타이드 생성 + */ +export function createEpitaph( + plan: Plan, + validationResult: { grade: ContractGrade; results: Record }, + executionResult: { status: 'success' | 'partial' | 'failure'; duration: number; deviationFromPlan?: string } +): Epitaph { + return { + planId: plan.id, + timestamp: new Date().toISOString(), + grade: validationResult.grade, + clauses: validationResult.results, + executionResult + }; +}