Scoring

Fixing Violations

How to reduce your technical debt score by fixing violations, earning credits, and using AI-assisted fixes to unblock pull requests.

Fixing Violations

Every fixed violation earns a negative credit that reduces your debt delta score. A PR that fixes more than it introduces will have a negative delta --- actively reducing the technical debt in your codebase.

This page covers how fix credits work, which violations to fix first, and how to use radar fix for AI-assisted resolution.


Fix Credits

When Radar detects that a violation present in the base branch has been removed in the PR, it applies a fix credit:

Fix TypeCreditWhat It Means
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. Radar compares the current scan results against the stored baseline for each changed file. If a violation existed before and is gone now, the credit is applied.

Example

Your PR modifies src/orders/domain/order.service.ts. The baseline had two violations:

  1. PrismaClient imported directly in domain layer (+5 architecture)
  2. Missing try/catch in createOrder method (+3 reliability)

Your PR fixes both and introduces one new warning:

  1. Code duplication with another file (+2 maintainability)
debt_delta_score = (+2) + (-5) + (-3) = -6

The PR reduces total debt by 6 points. It passes the gate and your codebase is healthier.


Prioritization Strategy

Not all violations are equal. Fix high-point violations first to maximize your score improvement per hour of work.

Tier 1: Fix These First (8--10 Points Each)

Circular dependencies (+10). These are the most expensive violations. They prevent independent deployment, create initialization-order bugs, and make the codebase impossible to reason about. Fixing one circular dependency removes 10 points from your debt.

How to fix: Extract shared types into a contracts/ or shared/ directory. Replace direct imports with dependency injection or event-based communication.

Critical runtime risks (+8). Event-loop blockers in request handlers cause production timeouts. Each fix earns an 8-point credit.

How to fix: Replace fs.readFileSync with fs.promises.readFile. Replace crypto.pbkdf2Sync with crypto.pbkdf2 (callback) or util.promisify(crypto.pbkdf2). Move CPU-intensive work to a worker thread or background job.

Critical performance risks (+8). Unbounded queries on large tables will cause outages as data grows.

How to fix: Add take and skip parameters to every findMany call on XL/XXL tables. Add where clauses to narrow results. Convert N+1 patterns to include (Prisma) or eager loading (Sequelize/TypeORM).

Tier 2: Fix Next (5 Points Each)

Architecture violations (+5). Layer and module boundary violations are quick to fix individually but expensive if left to accumulate.

How to fix: Move the import to the correct layer. If a domain service needs database access, inject a repository interface instead of importing the ORM directly. If cross-module access is needed, expose a contract in src/**/contracts/**.

Unhandled promise rejections (+5). These crash the process in strict mode.

How to fix: Add await inside a try/catch block, or add .catch() to the promise chain. Never fire-and-forget a promise without explicit error handling.

Tier 3: Fix When Convenient (1--3 Points Each)

Missing error handling (+3). Add try/catch to async operations, timeouts to HTTP calls, and null guards after nullable queries.

Runtime risk warnings (+3). Address unbounded-json-parse, large-json-stringify, and similar patterns when touching those files.

Code duplication (+2). Extract shared logic into utility functions or shared modules.

Dead code (+1). Remove unused exports and unreachable code.


Category-by-Category Fix Guide

Architecture

ViolationFix
Domain imports infrastructureInject a repository interface; move ORM calls to infra layer
Domain imports APIExtract shared types to a contracts directory
API imports infrastructure directlyRoute through application/use-case layer
Cross-module direct importExpose a contract or use events for cross-module communication
// BEFORE: domain importing infrastructure
// src/orders/domain/order.service.ts
import { PrismaClient } from '@prisma/client'; // violation

// AFTER: domain depends on interface
// src/orders/domain/order.service.ts
import { OrderRepository } from '../contracts/order.repository';

export class OrderService {
  constructor(private readonly repo: OrderRepository) {}
}

Runtime Risk

ViolationFix
fs.readFileSync in handlerUse fs.promises.readFile with await
crypto.pbkdf2SyncUse crypto.pbkdf2 with callback or promisify
JSON.parse on unbounded inputValidate input size before parsing, or stream-parse
Busy-wait loopUse setTimeout, setInterval, or event-based patterns
// BEFORE: sync file read in handler
@Get('template')
getTemplate() {
  return fs.readFileSync('./template.html', 'utf-8'); // blocks event loop
}

// AFTER: async file read
@Get('template')
async getTemplate() {
  return fs.promises.readFile('./template.html', 'utf-8');
}

Performance

ViolationFix
Unbounded findMany() on XL tableAdd take and skip parameters
N+1 query patternUse include for eager loading
Raw SQL without LIMITAdd LIMIT clause
Missing paginationImplement cursor-based or offset pagination
// BEFORE: unbounded query on XL table
const events = await prisma.event.findMany(); // 2M+ rows

// AFTER: paginated query
const events = await prisma.event.findMany({
  take: 100,
  skip: page * 100,
  where: { createdAt: { gte: startDate } },
  orderBy: { createdAt: 'desc' },
});

Reliability

ViolationFix
Fire-and-forget promiseAdd await in try/catch or .catch()
Missing try/catchWrap async operations in try/catch with typed errors
HTTP call without timeoutAdd timeout option to HTTP client config
Empty catch blockLog the error and rethrow or handle appropriately
// BEFORE: fire-and-forget
sendNotification(user.email);

// AFTER: awaited with error handling
try {
  await sendNotification(user.email);
} catch (error) {
  this.logger.error('Failed to send notification', { userId: user.id, error });
}

Maintainability

ViolationFix
High cyclomatic complexityExtract conditional logic into named helper functions
Code duplicationExtract shared logic into utility functions
Dead codeRemove unused exports and unreachable branches
Coverage dropAdd tests for the changed code paths

Using radar fix for AI-Assisted Fixes

The radar fix command uses the Claude API to generate code patches for detected violations. It analyzes each violation in context, generates a fix, and presents it for your review before applying.

# Fix all violations in the current directory
radar fix .

# Fix violations in a specific path
radar fix src/orders/

# Fix only architecture violations
radar fix . --category architecture

# Dry run — show fixes without applying
radar fix . --dry-run

How It Works

  1. Radar scans the target path and identifies all violations.
  2. For each violation, Radar sends the surrounding code context (the violating function plus imports and related types) to the Claude API.
  3. Claude generates a minimal diff that resolves the violation without changing behavior.
  4. Radar presents the diff in your terminal with syntax highlighting.
  5. You choose to apply, skip, or modify each fix.
$ radar fix src/orders/domain/order.service.ts

Found 2 violations in src/orders/domain/order.service.ts

[1/2] Architecture: domain → infrastructure (line 3)
      PrismaClient imported directly in domain layer

  Suggested fix:
  - import { PrismaClient } from '@prisma/client';
  + import { OrderRepository } from '../contracts/order.repository';

  Apply this fix? [y/n/e(dit)]

Note: radar fix requires the ANTHROPIC_API_KEY environment variable and a Solo plan or higher.

Fix Options

FlagDescription
--category <name>Fix only violations in the specified category
--dry-runShow fixes without applying them
--auto-applyApply all fixes without prompting (use with caution)
--max-fixes <n>Limit the number of fixes per run

Tracking Progress Over Time

The Technical Debt Radar dashboard shows your debt score trend over time, making it easy to track whether your team is reducing or accumulating debt.

Dashboard Trend Chart

The trend chart on the dashboard plots your total debt score after each PR scan. A downward trend means your team is consistently fixing more than it introduces.

Key metrics visible on the dashboard:

  • Total debt score --- cumulative score across all files in the repository
  • Debt delta per PR --- average net change per pull request
  • Fix rate --- percentage of PRs that reduce debt (negative delta)
  • Category breakdown --- which categories are contributing the most debt
  • Top violating files --- files with the highest concentration of violations

Setting Team Goals

Use the trend data to set actionable goals:

  • Maintenance goal: Keep the average debt delta per PR at zero or below. Every PR should fix at least as much as it introduces.
  • Reduction goal: Reduce total debt score by a target percentage per sprint. Allocate cleanup PRs that focus on high-point violations.
  • Prevention goal: Maintain zero architecture violations across all PRs. The architecture_violations > 0 gate enforces this automatically.

Cleanup Sprints

Dedicate periodic sprints to debt reduction. Focus on Tier 1 violations (circular dependencies, critical runtime risks, critical performance risks) for maximum impact. A single cleanup PR that resolves two circular dependencies earns 20 points of credit.

# Find the highest-scoring violations to prioritize
radar scan . --sort-by score --limit 20

This outputs the top 20 violations sorted by point value, giving you a prioritized hit list for your cleanup sprint.

Technical Debt Radar Documentation