AI Features

AI Cross-File Analysis

How Technical Debt Radar uses reverse import graph BFS and Claude Sonnet to trace indirect violations across file boundaries.

AI Cross-File Analysis

Cross-file analysis traces violations through the import graph to find risks that per-file analysis misses. A readFileSync call in a utility function is harmless on its own --- but when that utility is imported by a service that is called by a request handler, it becomes a critical event-loop blocker in production.

Plan requirement: AI Cross-File Analysis requires the Pro plan or higher.

The Problem: Hidden Indirect Violations

Per-file analysis detects violations within the scope of a single file. It catches readFileSync inside a @Get() handler because both the sync call and the handler decorator are in the same file. But production codebases rarely work that way.

src/utils/template.util.ts    →  fs.readFileSync('./template.html')
src/orders/order.service.ts   →  import { loadTemplate } from '../utils/template.util'
src/orders/order.controller.ts →  import { OrderService } from './order.service'
                                   @Get('export')
                                   async export() { this.orderService.render() }

In this example, the controller's export() handler indirectly calls readFileSync through two layers of abstraction. Per-file analysis sees no violation in any individual file:

  • template.util.ts has a sync call, but it is not inside a handler
  • order.service.ts calls loadTemplate(), but that looks like a normal function call
  • order.controller.ts calls this.orderService.render(), which appears safe

Cross-file analysis connects the dots.

How It Works

Step 1: Build the Import Graph

During Phase 1, the import graph analyzer builds a complete directed graph of every import and require statement across all analyzed files. This graph maps which files import which other files.

Step 2: Identify Candidate Patterns

After per-file analysis completes, Radar identifies candidate patterns --- violations detected outside of handler scope. These are sync operations, unbounded queries, fire-and-forget promises, and other risky patterns that exist in utility files, services, or libraries.

Step 3: Reverse Import Graph BFS

For each candidate pattern, Radar performs a reverse BFS (breadth-first search) through the import graph:

  1. Start at the file containing the candidate pattern
  2. Find all files that import this file (reverse edges)
  3. For each importing file, check if it contains a request handler (NestJS decorator, Express route, Fastify handler, etc.)
  4. If a handler is found, the candidate is promoted to an indirect violation with the full call chain
  5. If no handler is found, continue BFS to the next level of importers
  6. Stop at a configurable depth limit (default: 5 levels)
readFileSync in template.util.ts
  ← imported by order.service.ts
    ← imported by order.controller.ts (has @Get handler)
      = INDIRECT VIOLATION: sync-fs reaches handler through 2 hops

Step 4: AI Enhancement

When AI is enabled, Claude Sonnet receives the full call chain and determines:

  • Reachability confidence --- Is the risky function actually called in the code path that reaches the handler, or is it in a different method of the imported class?
  • Severity assessment --- How likely is this to cause production impact given the call frequency and data volumes?
  • Fix recommendation --- Where in the chain should the fix be applied (at the source, at the call site, or both)?

The AI step reduces false positives from the BFS traversal. The BFS finds all possible paths; the AI determines which paths are actually exercised.

Example Output

CROSS-FILE VIOLATION: sync-fs-in-handler (indirect)
Severity: Critical
Chain:
  1. src/utils/template.util.ts:12      → fs.readFileSync('./template.html')
  2. src/orders/order.service.ts:45     → loadTemplate() call
  3. src/orders/order.controller.ts:23  → @Get('export') handler

AI Assessment:
  Confidence: HIGH — loadTemplate() is called unconditionally in the
  render() method, which is the direct handler body of @Get('export').

  Impact: Every request to GET /orders/export blocks the event loop for
  ~5ms (small template). At 200 req/s, this creates 1 second of cumulative
  blocking per second — effectively single-threading the server.

  Fix: Replace fs.readFileSync in template.util.ts with
  fs.promises.readFile and make loadTemplate() async. Update callers
  to await the result.

Supported Indirect Violation Types

PatternDirect DetectionIndirect Detection
Sync file operationsreadFileSync in handlerreadFileSync in utility called by handler
Sync cryptopbkdf2Sync in middlewarehashPassword helper using sync crypto
Unbounded queriesfindMany() in handlerRepository method without pagination called from handler
Fire-and-forgetUnawaited promise in handlerService method with unawaited call, invoked from handler
ReDoS regexVulnerable regex in validatorValidator used in route middleware
Missing timeoutHTTP call in handlerAPI client without timeout, used in handler

Cost

Each cross-file analysis consumes 5 credits, approximately $0.03--0.08 per scan depending on the number of candidate patterns and chain depth. The BFS traversal is free (deterministic). Credits are consumed only for the AI enhancement step.

Configuration

# radar.yml
ai:
  cross_file: true          # Enable cross-file analysis (default: true on Pro+)
  cross_file_depth: 5       # Maximum BFS depth (default: 5)
  cross_file_min_severity: warning  # Minimum severity to trace (default: warning)

Without AI

Cross-file analysis works without AI credits, but with reduced accuracy. The deterministic BFS finds all possible call chains, but cannot determine whether the risky function is actually called in the code path that reaches the handler. This may produce false positives where the import exists but the dangerous function is never called from the handler's execution path.

To run cross-file analysis without AI:

radar scan . --cross-file --no-ai
Technical Debt Radar Documentation