Scoring

Scoring Formula

How Technical Debt Radar calculates debt scores per violation, computes the debt delta, and determines whether a pull request passes or fails the merge gate.

Scoring Formula

Every violation detected by Technical Debt Radar carries a point value. Points are summed into a debt delta score that represents the net change in technical debt introduced by a pull request. If the delta exceeds your configured threshold, the merge is blocked.

This page explains exactly how scores are calculated, how credits work, and how the final gate decision is made.


Point Values by Violation Type

Each violation type has a default point value. These defaults are designed to reflect the real-world cost of each issue --- architecture violations and circular dependencies are weighted heavily because they compound over time, while maintainability issues carry lower weight because they are easier to fix later.

Violation TypePointsCategoryGate Behavior
Architecture violation+5ArchitectureAlways blocks
Circular dependency+10ArchitectureAlways blocks
Event loop blocking (Critical)+8Runtime RiskBlocks on Critical
Event loop warning+3Runtime RiskWarns
Performance risk (XL/XXL tables)+8PerformanceBlocks merge
Performance risk (L/M tables)+3PerformanceWarns
N+1 query (any volume)+8PerformanceBlocks merge
Unhandled promise rejection+5ReliabilityBlocks on Critical
Missing error handling / timeout+3ReliabilityWarns
Code duplication+2MaintainabilityWarns only
Dead code / unused export+1MaintainabilityWarns only
Coverage drop (per percentage)+3MaintainabilityWarns only
Indirect violation (cross-file)+2AI Cross-FileWarns only
Violation fixed (credit)-5 to -8CreditReduces score

Note: These values are defaults. You can override any of them in the scoring section of your radar.yml. See Customizing Weights below.


How debt_delta_score Is Computed

The debt delta score measures the net change in technical debt introduced by a pull request. It is not the total debt of your codebase --- it is the difference between new violations and fixed violations in the changed files.

debt_delta_score = sum(new_violation_points) + sum(fix_credits)

New violation points are positive values added for every violation introduced in the PR's changed files. Fix credits are negative values subtracted when the PR resolves existing violations.

Step-by-Step Calculation

  1. Identify changed files. Radar diffs the PR branch against the base branch to find all added, modified, and deleted files.

  2. Run analyzers on changed files. The seven deterministic analyzers scan each changed file for violations.

  3. Compare against baseline. Radar compares the current violations against the last known state of those files (from the previous scan stored in the database, or from the base branch).

  4. Score new violations. Every violation that exists in the PR but not in the baseline gets its point value added to the delta.

  5. Apply fix credits. Every violation that existed in the baseline but is resolved in the PR gets its fix credit subtracted from the delta.

  6. Sum the delta. The final debt_delta_score is the sum of all new violation points and all fix credits.


Example: A PR That Passes

A developer opens a PR that modifies three files. Radar detects the following:

FindingTypePoints
New: fs.readFileSync in utility function (not in handler)Runtime Risk Warning+3
New: Code duplication with utils/format.tsMaintainability+2
Fixed: Removed direct PrismaClient import from domain layerArchitecture Fix Credit-5
Fixed: Added pagination to order.findMany() on XL tablePerformance Fix Credit-8
debt_delta_score = (+3) + (+2) + (-5) + (-8) = -8

The delta is -8. This is well below the default threshold of 15. The PR passes the gate and actually reduces the overall technical debt by 8 points.


Example: A PR That Fails

A different developer opens a PR that adds a new module with several issues:

FindingTypePoints
New: Domain service imports from infrastructureArchitecture Violation+5
New: Circular dependency between orders and billingCircular Dependency+10
New: findMany() on XL table without paginationPerformance Critical+8
New: Missing try/catch in use caseReliability Warning+3
debt_delta_score = (+5) + (+10) + (+8) + (+3) = 26

The delta is 26, which exceeds the default threshold of 15. The PR is blocked.

But this PR would actually be blocked even if the score were lower. The architecture violation (architecture_violations > 0) and the circular dependency (circular_dependencies_introduced > 0) each independently trigger a block regardless of the total score. See Gate Rules for details.


Fix Credits

When a PR removes an existing violation, Radar applies a negative credit to the debt delta. Credits reward teams for actively reducing technical debt.

Fix TypeCreditDescription
Architecture violation fixed-5Removed a layer or module boundary violation
Runtime risk fixed-8Removed a critical event-loop blocker
Reliability issue fixed-3Added error handling, timeout, or null guard
Complexity reduced-1 per unitReduced cyclomatic complexity of a function

Credits are calculated automatically by comparing the PR's analysis results against the baseline. No manual annotation is required.

Tip: A PR that only fixes violations without introducing new code will have a negative debt delta score. This is by design --- it incentivizes cleanup PRs.


Category Weights

Each debt category contributes differently to the overall score. The weights reflect both the severity of the issue and the difficulty of fixing it later.

Architecture (Highest Weight)

Architecture violations (+5) and circular dependencies (+10) carry the highest weights because they are the most expensive to fix once they spread. A single layer violation is a 5-minute fix today but a 2-week refactor after 50 files depend on the wrong abstraction.

Runtime Risk (High Weight)

Critical runtime risks (+8) carry high weight because they cause production incidents. An fs.readFileSync in a request handler works in development but causes cascading timeouts under load. Warning-level runtime issues (+3) are lower because they represent potential risks rather than guaranteed failures.

Performance (High Weight)

Critical performance risks (+8) target unbounded queries on large tables. An unbounded findMany() on a table with 2 million rows will eventually cause an out-of-memory crash or a 30-second query timeout. N+1 queries always score +8 regardless of table size because they scale linearly with result count.

Reliability (Medium Weight)

Unhandled promise rejections (+5) and missing error handling (+3) are weighted lower than runtime risks because they are easier to fix and less likely to cause immediate production failures. They still matter --- an unhandled rejection crashes the Node.js process in strict mode.

Maintainability (Low Weight)

Code duplication (+2), dead code (+1), and coverage drops (+3) carry the lowest weights. They represent long-term maintenance cost rather than immediate risk. They warn but never block merges by default.

AI Cross-File (Low Weight)

Indirect violations found through cross-file analysis (+2) carry low weight because they are inferred rather than directly observed. They provide valuable context but should not block merges on their own.


Customizing Weights

Override any default weight in the scoring section of your radar.yml:

scoring:
  architecture_violation: 5      # default: 5
  circular_dependency: 10        # default: 10
  runtime_risk_critical: 8       # default: 8
  runtime_risk_warning: 3        # default: 3
  performance_risk_critical: 8   # default: 8
  performance_risk_warning: 3    # default: 3
  reliability_critical: 5        # default: 5
  reliability_warning: 3         # default: 3
  complexity_point: 1            # default: 1
  missing_tests: 3               # default: 3
  coverage_drop_per_pct: 2       # default: 2
  ai_concern: 2                  # default: 2
  violation_fixed: -5            # default: -5
  runtime_risk_fixed: -8         # default: -8
  complexity_reduced: -1         # default: -1
  reliability_fixed: -3          # default: -3

Only include the keys you want to change. Omitted keys use their default values.

When to Adjust Weights

Increase architecture weights if you are actively enforcing DDD boundaries and want zero tolerance for drift.

Decrease runtime risk weights if your application is a background worker or CLI tool where event-loop blocking is acceptable.

Increase performance weights if you operate at high data volumes where unbounded queries cause outages.

Decrease maintainability weights if you are in a rapid prototyping phase and plan to address code quality later.


Score in the PR Comment

When Radar posts a PR comment, it includes a score breakdown:

## Technical Debt Radar Report

Gate: BLOCKED | Score: +26 (threshold: 15)

### Score Breakdown
| Category        | New  | Fixed | Net  |
|-----------------|------|-------|------|
| Architecture    | +15  | 0     | +15  |
| Runtime Risk    | 0    | 0     | 0    |
| Performance     | +8   | 0     | +8   |
| Reliability     | +3   | 0     | +3   |
| Maintainability | 0    | 0     | 0    |
| AI Cross-File   | 0    | 0     | 0    |
|-----------------|------|-------|------|
| Total           | +26  | 0     | +26  |

The breakdown makes it clear which categories are contributing the most to the score, so developers know where to focus their fixes.

Technical Debt Radar Documentation