import { PolicyV2 } from '@nihal1983/core'; import { JiraClient, ReviewPolicyConfig } from '@nihal1983/context-gatherer'; import { TicketChecker } from './checkers/ticket-checker'; import { StructureChecker } from './checkers/structure-checker'; import { FileChecker } from './checkers/file-checker'; import { CodeChecker } from './checkers/code-checker'; import { PRContext, ValidationResult, Violation, Checker } from './types'; /** * PolicyValidator - Main orchestrator for policy validation */ export class PolicyValidator { private checkers: Checker[] = []; constructor( private policy: PolicyV2, private config?: ReviewPolicyConfig ) { this.initializeCheckers(); } /** * Initialize all checkers */ private initializeCheckers(): void { // Create Jira client if configured let jiraClient: JiraClient | undefined; if (this.config?.jira) { jiraClient = new JiraClient(this.config.jira); } // Add all checkers this.checkers = [ new TicketChecker(this.policy, jiraClient), new StructureChecker(this.policy), new FileChecker(this.policy), new CodeChecker(this.policy), ]; } /** * Validate PR against policy */ async validate(context: PRContext): Promise { const allViolations: Violation[] = []; let checksRun = 0; let checksPassed = 0; // Run all checkers for (const checker of this.checkers) { try { const violations = await checker.check(context); allViolations.push(...violations); checksRun++; if (violations.length === 0) { checksPassed++; } } catch (error: any) { // If a checker fails, record as violation allViolations.push({ ruleId: `${checker.name}-error`, severity: 'warn', message: `Checker ${checker.name} failed: ${error.message}`, location: 'internal' }); } } // Determine overall status const status = this.determineStatus(allViolations); // Apply enforcement level from policy meta const finalStatus = this.applyEnforcementLevel(status); return { status: finalStatus, violations: allViolations, timestamp: new Date(), metadata: { total_checks: checksRun, checks_passed: checksPassed, checks_failed: checksRun - checksPassed } }; } /** * Determine status from violations */ private determineStatus(violations: Violation[]): 'PASS' | 'WARN' | 'BLOCK' { const hasBlocking = violations.some(v => v.severity === 'block'); const hasWarnings = violations.some(v => v.severity === 'warn'); if (hasBlocking) { return 'BLOCK'; } else if (hasWarnings) { return 'WARN'; } else { return 'PASS'; } } /** * Apply global enforcement level from policy */ private applyEnforcementLevel(status: 'PASS' | 'WARN' | 'BLOCK'): 'PASS' | 'WARN' | 'BLOCK' { const enforcementLevel = this.policy.meta.enforcement_level; if (enforcementLevel === 'SHADOW') { // Shadow mode: never block return 'PASS'; } else if (enforcementLevel === 'WARN') { // Warn mode: convert BLOCK to WARN return status === 'BLOCK' ? 'WARN' : status; } else { // Block mode: return as-is return status; } } /** * Get summary statistics */ getSummary(result: ValidationResult): string { const blocking = result.violations.filter(v => v.severity === 'block').length; const warnings = result.violations.filter(v => v.severity === 'warn').length; const info = result.violations.filter(v => v.severity === 'info').length; return `Status: ${result.status} | Blocking: ${blocking} | Warnings: ${warnings} | Info: ${info}`; } }