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 Type | Points | Category | Gate Behavior |
|---|---|---|---|
| Architecture violation | +5 | Architecture | Always blocks |
| Circular dependency | +10 | Architecture | Always blocks |
| Event loop blocking (Critical) | +8 | Runtime Risk | Blocks on Critical |
| Event loop warning | +3 | Runtime Risk | Warns |
| Performance risk (XL/XXL tables) | +8 | Performance | Blocks merge |
| Performance risk (L/M tables) | +3 | Performance | Warns |
| N+1 query (any volume) | +8 | Performance | Blocks merge |
| Unhandled promise rejection | +5 | Reliability | Blocks on Critical |
| Missing error handling / timeout | +3 | Reliability | Warns |
| Code duplication | +2 | Maintainability | Warns only |
| Dead code / unused export | +1 | Maintainability | Warns only |
| Coverage drop (per percentage) | +3 | Maintainability | Warns only |
| Indirect violation (cross-file) | +2 | AI Cross-File | Warns only |
| Violation fixed (credit) | -5 to -8 | Credit | Reduces score |
Note: These values are defaults. You can override any of them in the
scoringsection of yourradar.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
-
Identify changed files. Radar diffs the PR branch against the base branch to find all added, modified, and deleted files.
-
Run analyzers on changed files. The seven deterministic analyzers scan each changed file for violations.
-
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).
-
Score new violations. Every violation that exists in the PR but not in the baseline gets its point value added to the delta.
-
Apply fix credits. Every violation that existed in the baseline but is resolved in the PR gets its fix credit subtracted from the delta.
-
Sum the delta. The final
debt_delta_scoreis 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:
| Finding | Type | Points |
|---|---|---|
New: fs.readFileSync in utility function (not in handler) | Runtime Risk Warning | +3 |
New: Code duplication with utils/format.ts | Maintainability | +2 |
Fixed: Removed direct PrismaClient import from domain layer | Architecture Fix Credit | -5 |
Fixed: Added pagination to order.findMany() on XL table | Performance 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:
| Finding | Type | Points |
|---|---|---|
| New: Domain service imports from infrastructure | Architecture Violation | +5 |
New: Circular dependency between orders and billing | Circular Dependency | +10 |
New: findMany() on XL table without pagination | Performance Critical | +8 |
| New: Missing try/catch in use case | Reliability 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 Type | Credit | Description |
|---|---|---|
| Architecture violation fixed | -5 | Removed a layer or module boundary violation |
| Runtime risk fixed | -8 | Removed a critical event-loop blocker |
| Reliability issue fixed | -3 | Added error handling, timeout, or null guard |
| Complexity reduced | -1 per unit | Reduced 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.