All files / src scoring.ts

100% Statements 74/74
98.43% Branches 63/64
100% Functions 8/8
100% Lines 55/55

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145                              200x 200x 200x 8x 14x 206x 206x 206x 14x   8x 8x         14x 14x 8x 8x     8x 8x 14x   14x 22x     22x 22x     22x 18x 18x           14x 12x 12x 17x           14x 13x 13x 17x           14x 3x 11x   8x 7x 4x       18x   14x 22x 12x 7x             22x 16x 11x           8x 16x 2x           8x   22x 10x               22x   14x                                    
import { calculateProductivityImpact, ToolName } from '@aiready/core';
import type { ToolScoringOutput, CostConfig } from '@aiready/core';
import type { ConsistencyIssue } from './types';
 
/**
 * Calculate AI Readiness Score for code consistency (0-100).
 *
 * @param issues - Array of detected consistency issues.
 * @param totalFilesAnalyzed - Total number of files scanned.
 * @param costConfig - Optional configuration for productivity cost calculation.
 * @returns Standardized scoring output for the consistency tool.
 * @lastUpdated 2026-03-18
 */
export function calculateConsistencyScore(
  issues: ConsistencyIssue[],
  totalFilesAnalyzed: number,
  costConfig?: Partial<CostConfig>
): ToolScoringOutput {
  // Parameter reserved for future configuration; reference to avoid lint warnings
  void costConfig;
  const criticalIssues = issues.filter((i) => i.severity === 'critical').length;
  const majorIssues = issues.filter((i) => i.severity === 'major').length;
  const minorIssues = issues.filter((i) => i.severity === 'minor').length;
  const totalIssues = issues.length;
 
  // Issue density penalty (0-50 points)
  // Ideal: 0 issues/file = 0 penalty
  // Acceptable: <1 issue/file = 10 penalty
  // High: 1-3 issues/file = 10-40 penalty
  // Critical: >3 issues/file = 40-50 penalty
  const issuesPerFile =
    totalFilesAnalyzed > 0 ? totalIssues / totalFilesAnalyzed : 0;
  const densityPenalty = Math.min(50, issuesPerFile * 15);
 
  // Weighted severity penalty (0-50 points)
  // Each critical: 10 points
  // Each major: 3 points
  // Each minor: 0.5 points
  const weightedCount =
    criticalIssues * 10 + majorIssues * 3 + minorIssues * 0.5;
  const avgWeightedIssuesPerFile =
    totalFilesAnalyzed > 0 ? weightedCount / totalFilesAnalyzed : 0;
  const severityPenalty = Math.min(50, avgWeightedIssuesPerFile * 2);
 
  // Calculate final score
  const rawScore = 100 - densityPenalty - severityPenalty;
  const score = Math.max(0, Math.min(100, Math.round(rawScore)));
 
  // Build factors array
  const factors: ToolScoringOutput['factors'] = [
    {
      name: 'Issue Density',
      impact: -Math.round(densityPenalty),
      description: `${issuesPerFile.toFixed(2)} issues per file ${issuesPerFile < 1 ? '(excellent)' : issuesPerFile < 3 ? '(acceptable)' : '(high)'}`,
    },
  ];
 
  if (criticalIssues > 0) {
    const criticalImpact = Math.min(30, criticalIssues * 10);
    factors.push({
      name: 'Critical Issues',
      impact: -criticalImpact,
      description: `${criticalIssues} critical consistency issue${criticalIssues > 1 ? 's' : ''} (high AI confusion risk)`,
    });
  }
 
  if (majorIssues > 0) {
    const majorImpact = Math.min(20, Math.round(majorIssues * 3));
    factors.push({
      name: 'Major Issues',
      impact: -majorImpact,
      description: `${majorIssues} major issue${majorIssues > 1 ? 's' : ''} (moderate AI confusion risk)`,
    });
  }
 
  if (minorIssues > 0 && minorIssues >= totalFilesAnalyzed) {
    const minorImpact = -Math.round(minorIssues * 0.5);
    factors.push({
      name: 'Minor Issues',
      impact: minorImpact,
      description: `${minorIssues} minor issue${minorIssues > 1 ? 's' : ''} (slight AI confusion risk)`,
    });
  }
 
  // Generate recommendations
  const recommendations: ToolScoringOutput['recommendations'] = [];
 
  if (criticalIssues > 0) {
    const estimatedImpact = Math.min(30, criticalIssues * 10);
    recommendations.push({
      action:
        'Fix critical naming/pattern inconsistencies (highest AI confusion risk)',
      estimatedImpact,
      priority: 'high',
    });
  }
 
  if (majorIssues > 5) {
    const estimatedImpact = Math.min(15, Math.round(majorIssues / 2));
    recommendations.push({
      action: 'Standardize naming conventions across codebase',
      estimatedImpact,
      priority: 'medium',
    });
  }
 
  if (issuesPerFile > 3) {
    recommendations.push({
      action:
        'Establish and enforce coding style guide to reduce inconsistencies',
      estimatedImpact: 12,
      priority: 'medium',
    });
  }
 
  if (totalIssues > 20 && minorIssues / totalIssues > 0.7) {
    recommendations.push({
      action: 'Enable linter/formatter to automatically fix minor style issues',
      estimatedImpact: 8,
      priority: 'low',
    });
  }
 
  // Calculate business value metrics
  const productivityImpact = calculateProductivityImpact(issues);
 
  return {
    toolName: ToolName.NamingConsistency,
    score,
    rawMetrics: {
      totalIssues,
      criticalIssues,
      majorIssues,
      minorIssues,
      issuesPerFile: Math.round(issuesPerFile * 100) / 100,
      avgWeightedIssuesPerFile:
        Math.round(avgWeightedIssuesPerFile * 100) / 100,
      // Business value metrics
      estimatedDeveloperHours: productivityImpact.totalHours,
    },
    factors,
    recommendations,
  };
}