Scoring

Source: src/engine/scorer/index.ts, src/engine/scorer/weights.ts, src/engine/scorer/labels.ts

Converts filtered diagnostics into a 0-100 health score with a quality label. One number lets you track progress over time, set a CI threshold with --min-score, and compare projects. The weights are chosen so a security error costs more than an info-level performance hint.

Input is the filtered diagnostics and the file count:

diagnostics: Diagnostic[]    // filtered diagnostics
fileCount: number            // total files scanned

The result is the score and its label:

interface Score {
  value: number    // 0-100, clamped
  label: string    // "Excellent" | "Good" | "Fair" | "Poor" | "Critical"
}

What counts

A rule declares which surfaces it appears on. Only diagnostics carrying the score surface reach the formula below. A rule marked surfaces: ["cli"] still reports every finding in the console and the HTML report, and weighs nothing, which keeps a matter of taste from moving a number meant to track defects. See Surfaces.

Formula

The score starts at 100 and loses points for every diagnostic:

penalty per diagnostic = SEVERITY_WEIGHT × CATEGORY_MULTIPLIER
total penalty = sum of all diagnostic penalties
score = 100 − (totalPenalty / fileCount) × PENALTY_SCALE

The score is clamped to the 0-100 range.

Severity weights

SeverityWeight
error3.0
warning1.5
info0.5

Category multipliers

CategoryMultiplierRationale
security1.5xThe most impactful category
correctness1.3xBugs that cause runtime errors
schema1.1xDatabase structural integrity
architecture1.0xSlows the project down as it grows
performance0.8xImportant, but less critical than correctness

Penalty scale

PENALTY_SCALE = 10 is a calibration constant. Combined with the severity weights and category multipliers, it produces scores on this scale:

ScenarioScore
1 security error per 10 files~95 (Excellent)
1 error per 3 files~85 (Good)
1 error per file~55 (Fair)
2 errors per file~10 (Critical)

Normalization

The score is divided by fileCount so projects of different sizes are comparable. A 500-file project with 10 errors scores the same as a 50-file project with 1 error, because the density is the same.

Labels

ScoreLabel
90-100Excellent
75-89Good
50-74Fair
25-49Poor
0-24Critical

Monorepo scoring

In a monorepo, each sub-project gets its own score. The combined score comes from the merged diagnostics of every sub-project, divided by the total file count across all of them.

Merging drops a diagnostic an earlier sub-project already reported, matched on rule, file, position and message. Two sub-projects sharing a workspace-root package.json or ORM schema therefore pay for it once.

A sub-project carries the diagnostics the merge kept, so the breakdown table stays consistent with the combined counts. Its score is not recalculated from them: a score always describes the whole project it was measured on, never the subset a report shows.

Debugging tips

  • If the score seems too low, look for high-severity rules producing many diagnostics. A single security error per file adds 3.0 × 1.5 = 4.5 penalty points.
  • The score is normalized by file count. Adding files without adding diagnostics raises the score.
  • Use --json to see the full diagnostic list and what is contributing to the penalty.