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.tshas a sync call, but it is not inside a handlerorder.service.tscallsloadTemplate(), but that looks like a normal function callorder.controller.tscallsthis.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:
- Start at the file containing the candidate pattern
- Find all files that import this file (reverse edges)
- For each importing file, check if it contains a request handler (NestJS decorator, Express route, Fastify handler, etc.)
- If a handler is found, the candidate is promoted to an indirect violation with the full call chain
- If no handler is found, continue BFS to the next level of importers
- 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
| Pattern | Direct Detection | Indirect Detection |
|---|---|---|
| Sync file operations | readFileSync in handler | readFileSync in utility called by handler |
| Sync crypto | pbkdf2Sync in middleware | hashPassword helper using sync crypto |
| Unbounded queries | findMany() in handler | Repository method without pagination called from handler |
| Fire-and-forget | Unawaited promise in handler | Service method with unawaited call, invoked from handler |
| ReDoS regex | Vulnerable regex in validator | Validator used in route middleware |
| Missing timeout | HTTP call in handler | API 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