Scoring

Gate Rules

How Technical Debt Radar decides whether to block or pass a pull request, including the debt delta threshold, category-specific gates, and configuration options.

Gate Rules

Technical Debt Radar enforces merge gates on every pull request. A gate is a condition that must be satisfied for the PR to pass. If any gate condition fails, the PR status check is set to failure and the merge is blocked.

Gates operate at two levels: the debt delta score threshold and category-specific conditions that block independently of the score.


The Default Gate

Out of the box, Radar blocks a PR when any of the following conditions are true:

gates:
  block_merge:
    - architecture_violations > 0
    - circular_dependencies_introduced > 0
    - runtime_risk_critical > 0
    - reliability_critical > 0
    - critical_performance_risk > 0
    - debt_delta_score > 15
  warn:
    - complexity_increase > 5
    - coverage_drop > 2%
    - debt_delta_score > 8

Block Conditions (PR Cannot Merge)

ConditionMeaning
architecture_violations > 0Any layer or module boundary violation blocks merge, regardless of score
circular_dependencies_introduced > 0Any new circular dependency chain blocks merge
runtime_risk_critical > 0Any critical event-loop blocker in a request handler blocks merge
reliability_critical > 0Any unhandled promise rejection blocks merge
critical_performance_risk > 0Any unbounded query on an XL/XXL table blocks merge
debt_delta_score > 15Net debt increase above 15 points blocks merge

Warn Conditions (PR Can Merge)

ConditionMeaning
complexity_increase > 5Warns if cyclomatic complexity increases by more than 5
coverage_drop > 2%Warns if test coverage drops by more than 2 percentage points
debt_delta_score > 8Warns when score is elevated but below the block threshold

Key insight: A PR with a debt_delta_score of 12 will pass the score gate (12 is not greater than 15) but will still be blocked if it contains even one architecture violation. Category-specific gates and the score gate are evaluated independently.


The Debt Delta Threshold

The debt_delta_score > 15 rule is the primary numerical gate. It works as follows:

  • A score of 15 or below passes the gate.
  • A score of 16 or above blocks the merge.

The threshold is intentionally set at 15 to allow small amounts of new debt while blocking PRs that introduce significant risk. A single architecture violation (+5) and a missing try/catch (+3) will not block on score alone, but three architecture violations (+15) plus a coverage drop (+3) will.

Why 15?

The threshold is calibrated so that a developer can introduce minor warnings in a PR without being blocked, but cannot introduce multiple serious issues in a single PR. At the default weights:

ScenarioScoreGate
1 architecture violation + 1 reliability warning8Pass
3 runtime risk warnings9Pass
2 architecture violations + 1 performance warning13Pass
3 architecture violations15Pass
3 architecture violations + 1 dead code16Block
1 circular dependency + 1 architecture violation15Block (arch gate)

Notice that the last row is blocked by architecture_violations > 0 even though the score equals 15, which would pass the score gate. Category-specific gates always take precedence.


Category-Specific Gates

Five categories have independent block conditions that fire regardless of the debt delta score.

Architecture: Zero Tolerance

- architecture_violations > 0
- circular_dependencies_introduced > 0

Architecture violations always block because they compound exponentially. One layer violation is a 5-minute fix. After 50 files depend on the wrong abstraction, it is a multi-week refactoring project. The zero-tolerance policy prevents this drift entirely.

Circular dependencies always block because they prevent independent deployment, create initialization-order bugs, and make the codebase impossible to reason about in isolation.

Runtime Risk: Critical Only

- runtime_risk_critical > 0

Only critical runtime risks block. Warning-level runtime issues (like unbounded-json-parse) add to the score but do not independently block the merge. Critical means the pattern will block the event loop under production load --- fs.readFileSync, crypto.pbkdf2Sync, or a busy-wait loop inside a request handler.

Reliability: Critical Only

- reliability_critical > 0

Unhandled promise rejections block because they crash the Node.js process in --unhandled-rejections=throw mode (the default since Node 15). Other reliability issues like missing try/catch or empty catch blocks warn but do not independently block.

Performance: Volume-Aware

- critical_performance_risk > 0

Performance issues only block when they target XL (1M+ rows) or XXL (50M+ rows) tables. The same findMany() without pagination on a 5K-row table is a warning, but on a 2M-row table it blocks. This is controlled by the data_volumes section of your radar.yml.


Configuring Gate Thresholds

Override the default gates in the gates section of your radar.yml or rules.yml:

gates:
  block_merge:
    - architecture_violations > 0
    - circular_dependencies_introduced > 0
    - runtime_risk_critical > 0
    - reliability_critical > 0
    - critical_performance_risk > 0
    - debt_delta_score > 20          # relaxed from 15 to 20
  warn:
    - complexity_increase > 10       # relaxed from 5 to 10
    - coverage_drop > 5%            # relaxed from 2% to 5%
    - debt_delta_score > 12

Available Gate Metrics

MetricTypeDescription
architecture_violationscountLayer/module boundary violations in changed files
circular_dependencies_introducedcountNew circular dependency chains
runtime_risk_criticalcountCritical event-loop blockers in handlers
runtime_risk_warningcountWarning-level runtime risks
reliability_criticalcountCritical reliability violations
reliability_warningcountWarning-level reliability issues
critical_performance_riskcountUnbounded queries on XL/XXL tables
debt_delta_scorenumberNet debt score change
complexity_increasenumberCyclomatic complexity increase
coverage_droppercentageTest coverage decrease
maintainability_violationscountMaintainability warnings

Supported Operators

OperatorMeaning
>Greater than
>=Greater than or equal
<Less than
<=Less than or equal
==Equal to

Removing Category-Specific Gates

If you want architecture violations to add to the score but not independently block, remove the condition from block_merge:

gates:
  block_merge:
    # architecture_violations removed — now score-only
    - circular_dependencies_introduced > 0
    - runtime_risk_critical > 0
    - reliability_critical > 0
    - critical_performance_risk > 0
    - debt_delta_score > 15

Warning: Removing architecture_violations > 0 from your gate config is strongly discouraged. Architecture drift is the single highest-cost form of technical debt because it compounds silently. Consider using exceptions for specific files instead.


Override Options

Per-File Exceptions

Exempt specific files from specific rules using the exceptions section:

exceptions:
  - rule: "domain -> infrastructure"
    file: "src/billing/domain/legacy-adapter.ts"
    expires: "2026-06-01"
    reason: "Legacy migration — tracked in JIRA-1234"

Exceptions require an expiration date and a reason. Expired exceptions are automatically ignored. This is the recommended way to handle legitimate violations during migrations or legacy integrations.

Relaxed Packs

Install a relaxed rule pack for gradual enforcement:

radar pack install nestjs-prisma-ddd-relaxed

Relaxed packs raise the debt_delta_score threshold, reduce some weights, and convert some block conditions to warnings. They are designed for teams adopting Radar on an existing codebase.

The --no-gate Flag

In the CLI, you can skip gate enforcement entirely:

radar scan . --no-gate

This runs the full analysis and outputs the report but always exits with code 0, regardless of violations. Useful during initial adoption when you want to see results without blocking merges.


CI vs CLI Gate Behavior

Gates behave slightly differently depending on where Radar is running.

In CI (GitHub Action / GitLab CI)

  • Gate result is posted as a GitHub status check or GitLab commit status.
  • A failure status prevents merging when branch protection rules require the Radar check to pass.
  • The PR comment includes the full score breakdown and lists every violation.
  • The report-url output links to the full dashboard report.

In CLI (radar scan / radar run)

  • radar scan prints the report to stdout and exits with code 0. It does not enforce gates.
  • radar run prints the report and exits with code 1 if any block condition is met. This is the CI-friendly command.
  • Both commands print the gate result at the bottom of the output:
Gate: PASS | Score: +8 (threshold: 15)
Gate: BLOCKED | Score: +26 (threshold: 15)
  → architecture_violations: 2 (threshold: 0)
  → debt_delta_score: 26 (threshold: 15)

In the Dashboard

The dashboard shows gate results for every PR scan with a visual timeline. Failed gates are highlighted in red, passed gates in green. The trend chart shows how your team's debt delta is changing over time.


Plan-Based Gate Behavior

Gate behavior varies by plan:

FeatureFreeSoloTeamEnterprise
Score gate (debt_delta_score)Default only (15)ConfigurableConfigurableConfigurable
Category gatesDefault onlyConfigurableConfigurableConfigurable
Custom gate metricsNoNoYesYes
Gate override via APINoNoNoYes
Exception managementIn YAML onlyIn YAML onlyYAML + DashboardYAML + Dashboard + API

On the Free plan, the default gate configuration is enforced and cannot be changed. Upgrading to Solo or higher unlocks full gate customization.

Technical Debt Radar Documentation