Project Configuration (radar.yml)
Complete reference for radar.yml — the project configuration file that tells Technical Debt Radar about your stack, architecture, modules, and data volumes.
Project Configuration: radar.yml
The radar.yml file lives at the root of your repository and describes what your project is. It declares your tech stack, architectural layers, module boundaries, and data volume expectations. The policy engine reads this file on every scan to understand your codebase structure before analyzing it.
# radar.yml — minimum viable configuration
stack:
language: TypeScript
framework: NestJS
orm: Prisma
layers:
- name: api
path: "src/**/controllers/**"
- name: domain
path: "src/**/domain/**"
modules:
- name: billing
path: "src/billing/**"
Tip: Run
radar initto auto-detect your stack and generate bothradar.ymlandrules.ymlwith sensible defaults.
Configuration Modes
Technical Debt Radar supports two configuration modes:
| Mode | Files | When to use |
|---|---|---|
| Two-file (recommended) | radar.yml + rules.yml | Production projects. Project config and enforcement rules live separately. |
| Single-file (legacy) | radar.yml only | Quick prototypes. All config including rules, gates, and exceptions in one file. |
In two-file mode, radar.yml declares what your project is while rules.yml declares what to enforce. When both files exist, rules.yml takes precedence for all enforcement-related fields.
Full Schema Reference
stack (required)
Declares your technology stack. The policy engine uses this to select the right analyzers and apply framework-specific detection rules.
stack:
language: TypeScript
framework: NestJS
orm: Prisma
build_tool: Nx
structure: modular-monolith
runtime: node
| Field | Type | Required | Description |
|---|---|---|---|
language | string | Yes | One of: TypeScript, JavaScript, Java, Python, Go |
framework | string | Yes | Framework name: NestJS, Express, Fastify, Next.js, Spring, etc. |
orm | string | Yes | ORM/ODM name: Prisma, TypeORM, Sequelize, Mongoose, Drizzle, MikroORM |
build_tool | string | No | Build tool: Nx, Turborepo, Lerna, Rush, etc. |
structure | string | No | One of: monolith, modular-monolith, microservices |
runtime | string | No | One of: node, jvm, python, go |
Note: The
ormfield drives volume auto-detection. Technical Debt Radar parses Prisma schemas, TypeORM entities, Sequelize models, Mongoose schemas, Drizzle table definitions, and MikroORM entities to estimate data volumes automatically.
architecture (optional)
Declares which architecture preset(s) to use for rule generation. When set, radar init generates architecture-specific rules in rules.yml.
# Single preset
architecture: ddd
# Multiple presets (combined rules)
architecture: [ddd, event-driven]
| Type | Values |
|---|---|
string or string[] | ddd, hexagonal, clean, layered, mvc, event-driven, feature-module |
When using multiple presets, rules from all presets are merged and deduplicated. If the same dependency path has both a deny and an allow rule, the deny rule wins (security-first).
See Architecture Presets for full details on each preset.
Warning: Some preset combinations are unusual and may generate conflicting rules. The following combinations are flagged:
ddd+mvc,hexagonal+mvc,clean+mvc,clean+layered.
layers (required)
Defines the architectural layers in your project using glob patterns. Layer names are referenced in dependency rules.
layers:
- name: api
path: "src/**/controllers/**"
- name: application
path: "src/**/use-cases/**"
- name: domain
path: "src/**/domain/**"
- name: infrastructure
path: "src/**/infra/**"
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique identifier for the layer. Referenced in rules. |
path | string | Yes | Glob pattern matching files in this layer. |
Layer names must be unique. Glob patterns support standard syntax: * (any file), ** (any directory depth), ? (single char), {a,b} (alternatives).
How layers work: When analyzing a file, the policy engine matches its path against each layer's glob pattern. A file at src/billing/controllers/invoice.controller.ts matches the api layer pattern src/**/controllers/**. Dependency rules then govern which layers can import from which.
modules (required)
Defines bounded modules in your codebase. Modules represent feature areas or domains that should maintain clear boundaries.
modules:
- name: billing
path: "src/billing/**"
- name: identity
path: "src/identity/**"
- name: orders
path: "src/orders/**"
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique identifier for the module. |
path | string | Yes | Glob pattern matching files in this module. |
Module names must be unique and cannot overlap with layer names. Cross-module dependency rules (like deny: cross-module direct imports) use these definitions to detect boundary violations.
data_volumes (optional in two-file mode)
Maps database entities to expected row count tiers. This drives volume-aware severity scaling for performance rules.
data_volumes:
orders: L
events: XL
users: M
companies: S
measurements: XXL
| Size | Row estimate | Effect on performance rules |
|---|---|---|
S | < 10K | Most performance warnings suppressed |
M | 10K -- 100K | Warnings shown |
L | 100K -- 1M | Severity elevated to Critical |
XL | 1M -- 50M | Blocks merge |
XXL | > 50M | Blocks merge |
Tip: If you skip
data_volumes, runradar initto auto-detect volumes from your ORM schema. The estimator uses entity naming patterns (e.g.,Event*defaults to XL,Config*defaults to S) and relation counts to produce reasonable defaults.
See Volume Configuration for detailed volume behavior.
rules (optional in two-file mode)
Dependency rules that govern allowed and forbidden imports between layers and modules. In two-file mode, define these in rules.yml under architecture_rules instead.
rules:
# Shorthand syntax
- deny: domain -> infrastructure
- deny: domain -> api
- deny: api -> infrastructure
- deny: cross-module direct imports
- allow: cross-module through "src/**/contracts/**"
# Structured syntax
- type: deny
source: domain
target: infrastructure
description: "Domain must not depend on infrastructure"
Two syntaxes are supported:
Shorthand syntax:
deny: source -> target-- forbids imports from source layer/module into targetallow: source -> target-- permits imports (overrides a deny)deny: source <- target-- reverse direction (target cannot import from source)deny: cross-module direct imports-- forbids all cross-module importsallow: cross-module through "glob"-- permits cross-module imports through specific paths
Structured syntax:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | allow or deny |
source | string | Yes | Source layer/module name, or cross-module |
target | string | Yes | Target layer/module name, or cross-module |
through | string | No | Glob pattern for allowed import path |
description | string | No | Human-readable description |
The special target __db__ represents direct database/ORM package imports (e.g., importing @prisma/client directly).
runtime_rules (optional)
Controls which Node.js runtime safety patterns are blocked or warned about.
runtime_rules:
block:
- sync-fs-in-handler
- sync-crypto
- sync-compression
- redos-vulnerable-regex
- busy-wait-loop
- unhandled-promise
warn:
- unbounded-json-parse
- large-json-stringify
- cpu-heavy-loop-in-handler
- unbounded-array-operation
- dynamic-buffer-alloc
Available runtime rules:
| Rule | Description | Default |
|---|---|---|
sync-fs-in-handler | Synchronous fs.*Sync() calls in request handlers | Block |
sync-crypto | Synchronous crypto.pbkdf2Sync(), scryptSync(), etc. | Block |
sync-compression | Synchronous zlib.*Sync() calls | Block |
redos-vulnerable-regex | Regular expressions vulnerable to ReDoS attacks | Block |
busy-wait-loop | while(true) or busy-wait spin loops | Block |
unhandled-promise | Promise without .catch() or await in try/catch | Block |
unbounded-json-parse | JSON.parse() on untrusted input without size limits | Warn |
large-json-stringify | JSON.stringify() on potentially large objects | Warn |
cpu-heavy-loop-in-handler | CPU-intensive loops inside request handlers | Warn |
unbounded-array-operation | .map(), .filter(), .reduce() on unbounded arrays | Warn |
dynamic-buffer-alloc | Buffer.alloc() with dynamic/untrusted size | Warn |
reliability_rules (optional)
Controls which reliability patterns are blocked or warned about.
reliability_rules:
block:
- unhandled-promise-rejection
warn:
- missing-try-catch
- external-call-no-timeout
- retry-without-backoff
- empty-catch-block
- missing-error-logging
- transaction-no-timeout
- missing-null-guard
Available reliability rules:
| Rule | Description | Default |
|---|---|---|
unhandled-promise-rejection | Promises that can reject without a handler | Block |
missing-try-catch | Async operations without error handling | Warn |
external-call-no-timeout | HTTP/gRPC calls without timeout configuration | Warn |
retry-without-backoff | Retry logic without exponential backoff | Warn |
empty-catch-block | catch blocks that swallow errors silently | Warn |
missing-error-logging | Error paths without logging | Warn |
transaction-no-timeout | Database transactions without timeout | Warn |
missing-null-guard | Nullable values accessed without null checks | Warn |
gates (optional in two-file mode)
Merge gate conditions that determine whether a PR is blocked or warned.
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
Each condition follows the format metric operator value. Supported operators: >, >=, <, <=, ==.
Available gate metrics:
| Metric | Description |
|---|---|
architecture_violations | Count of layer/module boundary violations |
circular_dependencies_introduced | New circular dependency chains detected |
runtime_risk_critical | Count of critical runtime risk findings |
reliability_critical | Count of critical reliability findings |
critical_performance_risk | Count of critical performance findings |
debt_delta_score | Net change in technical debt score |
complexity_increase | Cyclomatic complexity increase |
coverage_drop | Test coverage percentage decrease |
maintainability_violations | Count of maintainability issues |
runtime_risk_warning | Count of warning-level runtime risks |
reliability_warning | Count of warning-level reliability issues |
exceptions (optional)
Temporary exemptions for specific rules on specific files. Every exception requires an expiration date and a reason.
exceptions:
- rule: "domain -> infrastructure"
file: "src/billing/domain/legacy-adapter.ts"
expires: "2026-06-01"
reason: "Legacy migration — tracked in JIRA-1234"
| Field | Type | Required | Description |
|---|---|---|---|
rule | string | Yes | The rule being exempted (e.g., domain -> infrastructure) |
file | string | Yes | File path the exception applies to |
expires | string | Yes | Expiration date in YYYY-MM-DD format |
reason | string | Yes | Reason for the exception (include ticket reference) |
Expired exceptions are automatically ignored. The dashboard shows upcoming expirations.
Warning: Exceptions without an
expiresdate will fail validation. This is intentional -- every exception must be temporary.
standards (optional)
Declarative coding standards that the AI analysis engine evaluates against. These are natural-language descriptions of your team's conventions.
standards:
error_handling:
- all use-cases must have try/catch with typed errors
- no swallowed exceptions
logging:
- all controllers must log request entry/exit
- no console.log in production code
naming:
- "use-cases: <Verb><Noun>UseCase"
- "repositories: <Entity>Repository"
transactions:
- Prisma transactions only in infrastructure layer
dto_mapping:
- no Prisma types in application or domain layer
Standards are free-form string arrays grouped by category. The AI engine uses them as context when analyzing the top 10 suspect functions in a PR.
scoring (optional)
Override default scoring weights for debt calculation. Each finding type adds or subtracts from the debt delta score.
scoring:
architecture_violation: 5
circular_dependency: 10
runtime_risk_critical: 8
runtime_risk_warning: 3
performance_risk_critical: 8
performance_risk_warning: 3
reliability_critical: 5
reliability_warning: 3
All scoring keys and defaults:
| Key | Default | Description |
|---|---|---|
architecture_violation | 5 | Points per layer/module boundary violation |
circular_dependency | 10 | Points per circular dependency chain |
runtime_risk_critical | 8 | Points per critical runtime risk |
runtime_risk_warning | 3 | Points per warning-level runtime risk |
performance_risk_critical | 8 | Points per critical performance risk |
performance_risk_warning | 3 | Points per warning-level performance risk |
reliability_critical | 5 | Points per critical reliability issue |
reliability_warning | 3 | Points per warning-level reliability issue |
complexity_point | 1 | Points per unit of complexity increase |
missing_tests | 3 | Points per file without tests |
coverage_drop_per_pct | 2 | Points per percentage of coverage drop |
ai_concern | 2 | Points per AI-identified concern |
violation_fixed | -5 | Credit for fixing a violation |
runtime_risk_fixed | -8 | Credit for fixing a runtime risk |
complexity_reduced | -1 | Credit per unit of complexity reduced |
reliability_fixed | -3 | Credit for fixing a reliability issue |
Negative values are credits -- they reduce the debt delta score when a PR fixes existing issues.
Complete Examples
NestJS + Prisma + DDD
stack:
language: TypeScript
framework: NestJS
orm: Prisma
build_tool: Nx
structure: modular-monolith
runtime: node
architecture: ddd
layers:
- name: api
path: "src/**/controllers/**"
- name: application
path: "src/**/use-cases/**"
- name: domain
path: "src/**/domain/**"
- name: infrastructure
path: "src/**/infra/**"
modules:
- name: billing
path: "src/billing/**"
- name: identity
path: "src/identity/**"
- name: orders
path: "src/orders/**"
data_volumes:
orders: L
events: XL
users: M
measurements: XXL
Express + Sequelize + Layered
stack:
language: TypeScript
framework: Express
orm: Sequelize
runtime: node
architecture: layered
layers:
- name: routes
path: "src/routes/**"
- name: controllers
path: "src/controllers/**"
- name: services
path: "src/services/**"
- name: repositories
path: "src/repositories/**"
- name: models
path: "src/models/**"
modules:
- name: core
path: "src/**"
data_volumes:
users: M
orders: L
products: M
Fastify + Prisma + Clean Architecture
stack:
language: TypeScript
framework: Fastify
orm: Prisma
runtime: node
architecture: clean
layers:
- name: domain
path: "src/domain/**"
- name: use-cases
path: "src/use-cases/**"
- name: adapters
path: "src/adapters/**"
- name: infrastructure
path: "src/infrastructure/**"
- name: routes
path: "src/routes/**"
modules:
- name: core
path: "src/**"
data_volumes:
orders: L
users: M
Validation
Run the validator to check your configuration before committing:
radar validate
The validator checks:
- YAML syntax is valid
- All required fields are present
stack.language,stack.structure, andstack.runtimevalues are from allowed sets- Layer and module paths are valid glob patterns
- No duplicate layer or module names
- Rule sources/targets reference defined layers or modules
- Scoring keys are recognized
- Exception dates are in
YYYY-MM-DDformat
File Resolution
Technical Debt Radar looks for configuration files in this order:
radar.ymlin the repository root.radar/radar.ymlin the repository root- Path specified via
--configCLI flag
For rules:
rules.ymlin the same directory asradar.yml.radar/rules.ymlin the repository root- Path specified via
--rulesCLI flag - Falls back to rules embedded in
radar.yml(single-file mode)