Phase 4: Call-Site Tracer
The tracer is the final phase. When the classifier identifies a breaking change, the tracer answers the question: "who is affected?". It scans the entire repository to find every file that imports the broken symbol, then counts arguments at each call site to determine if the call is already broken.
Two-tier architecture
The tracer uses a lazy, two-tier scanning strategy designed for speed in large repositories:
Uses git grep to find all files mentioning the symbol name. No AST parsing — pure text search on the git index. For a 50,000-file repo, this takes approximately 50ms.
AST-parses only the files identified by the scanner. Locates actual import statements, resolves aliases, and counts arguments at each call site. Typically 5-15 files per broken symbol.
JIT Scanner
The JIT Scanner is the "fast pass" — it narrows down the entire repo to just the files that reference the broken symbol.
Step 1: Git grep
Runs git grepon the git index at the head ref. This is extremely fast because it operates on git's internal data structures, not the filesystem.
# What the scanner runs internally:
git grep -n --word-regexp 'processPayment' HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx'
# Returns:
# HEAD:src/checkout/handler.ts:18:import { processPayment } from '../payments'
# HEAD:src/invoices/gen.ts:4:import { processPayment } from '@/api/payments'
# HEAD:src/subscriptions/renew.ts:9:processPayment(amount, currency);
# HEAD:src/payments/index.ts:2:export { processPayment } from './core';Key properties of this approach:
- Reads committed content — not the working tree. Consistent with git refs
- Respects
.gitignoreautomatically. node_modules excluded for free - Excludes binary files automatically
- Capped at
maxGrepResults(default 500) to prevent runaway scans
Step 2: Import classification
For each grep match, the scanner reads the file content and classifies it using language-specific import pattern detection. Each language provides aLanguageStrategy that knows its import syntax:
// TypeScript import patterns:
import { processPayment } from './payments'; // Named import
import { processPayment as pay } from './payments'; // Aliased import
import * as payments from './payments'; // Namespace import
const { processPayment } = require('./payments'); // CJS require
const pay = await import('./payments'); // Dynamic import
// Python import patterns:
from payments import process_payment
from payments import process_payment as pay
import payments # then: payments.process_payment()
// Go import patterns:
import "project/payments" // then: payments.ProcessPayment()
// Java import patterns:
import com.project.payments.ProcessPayment;
import static com.project.payments.ProcessPayment;
// Rust import patterns:
use crate::payments::process_payment;
use crate::payments::{process_payment, other_fn};Step 3: Barrel file walking
If a file re-exports the symbol (a barrel file), the scanner adds it to a BFS queue and scans its consumers recursively.
// src/payments/index.ts (barrel file)
export { processPayment } from './core';
// src/checkout/handler.ts imports from the barrel
import { processPayment } from '../payments';
// The scanner traces: handler.ts → payments/index.ts → payments/core.tsCycle detection prevents infinite loops in circular re-exports. A visitedSet tracks every file the scanner has seen. Depth is capped atmaxBarrelDepth (default 10) for deeply nested barrel architectures.
Call-Site Tracer
After the scanner identifies importer files, the Call-Site Tracer AST-parses each one and locates call expressions:
// For each confirmed importer file:
// 1. Parse the file into an AST
// 2. Find all call expressions matching the symbol name
// 3. Count arguments at each call site
// 4. Compare against the new signature's required parameter count
// Result per call site:
{
file: "src/checkout/handler.ts",
line: 18,
argumentCount: 3,
status: "broken" // provides 3 args, new signature needs max 2
}Tracer output
The tracer populates the callers array on each FunctionChange:
interface CallerInfo {
file: string; // "src/checkout/handler.ts"
line: number; // 18
column: number; // 4
argumentCount: number; // 3
importType: string; // "named", "namespace", "default"
localName: string; // "processPayment" or "pay" (if aliased)
status: 'broken' | 'ok' | 'indeterminate';
}Terminal output
The reporter formats tracer results with status indicators:
Affected call sites (3):
X src/checkout/handler.ts:18 -- provides 3 arg(s), needs max 2
X src/invoices/gen.ts:31 -- provides 3 arg(s), needs max 2
. src/subscriptions/renew.ts:9 -- 2 arg(s), OKX— broken: argument count does not match the new signature.— ok: argument count satisfies the new signature?— indeterminate: call uses spread or computed arguments
Performance characteristics
| Operation | Typical Time | Bound |
|---|---|---|
| git grep on 50K-file repo | ~50ms | O(repo size) |
| Import regex on 15 files | ~2ms | O(grep matches) |
| Barrel BFS (3 levels) | ~20ms | O(barrel depth x width) |
| AST parse + call count | ~5ms/file | O(importer count) |
| Total Phase 4 | < 200ms |
Configuration
| Option | Default | Effect |
|---|---|---|
enableTracer | true | Disable entirely for faster CI runs |
maxGrepResults | 500 | Cap grep output for common symbol names |
maxBarrelDepth | 10 | Prevent runaway barrel chains |
maxTracerFiles | 100 | Cap AST-parsed files per symbol |
Related
- Phase 3: Classifier Engine
- dg trace — standalone tracer command
- Configuration — tracer settings