Trace: Finding the Code That Matters Before Your LLM Sees It
Most LLM context is wasted on code that doesn't matter. cxpak's trace command walks your dependency graph and packs only the relevant paths — so your model debugs with a map, not a haystack.
You paste an error into your LLM. It asks for context. You paste more files. It hallucinates a fix based on a function signature it misread three files ago.
The problem isn’t the model. It’s the context.
The token budget problem
A mid-sized codebase — say, 50,000 lines across a few hundred files — can easily produce 500,000 tokens of raw source. Most models cap useful context at 100–200k. Even if you could fit it all in, you’d be paying for 490,000 tokens of code that has nothing to do with your bug.
The instinct is to hand-pick files. Open the one with the error, grab a couple of imports, maybe the test file. But you’re guessing — and the model is working with whatever you remembered to include. Miss one dependency and the suggestion is wrong. Include too much and the signal drowns in noise.
What you actually need is the dependency graph.
What trace does
cxpak trace takes a target — a function name, a type, an error message — and returns only the code paths that touch it.
cxpak trace --tokens 50k "handle_request" .
Under the hood, three things happen.
Symbol search. cxpak uses tree-sitter to parse your codebase into an index of every function, type, struct, and class across 12 languages. When you pass a target, it searches that index case-insensitively. If the target isn’t a symbol — say, an error message or a string literal — it falls back to content search across all files.
Graph walk. Once it knows which files contain the target, it walks the dependency graph in both directions: files that import the target’s file, and files that the target’s file imports. By default, it walks one hop — the target plus its direct dependencies and dependents. Pass --all for a full breadth-first traversal across the entire reachable graph.
Budget and pack. The results go through the same token budgeting pipeline as cxpak overview. You get the target’s full source, signatures from every relevant file, and the dependency subgraph — all within your token budget. Truncation is intelligent: signatures are preserved before bodies, public symbols before private ones.
The output is a single document your LLM can consume in one prompt. No manual file selection. No guessing.
When to use trace vs overview
overview gives your model a map of the entire codebase — structure, modules, public APIs, git context. It’s what you want when starting a conversation: “here’s the project, now help me think about X.”
trace gives your model a scalpel. It’s what you want when you already know the target — a broken function, a failing test, an error message in production — and you need the model to understand exactly what touches it, nothing more.
In practice, the workflow looks like this:
# Start a session — give the model the lay of the land
cxpak overview --tokens 80k . > context.md
# Hit a bug — switch to targeted context
cxpak trace --tokens 50k "ConnectionPool::acquire" . > trace.md
The overview costs you 80k tokens once. Each trace costs a fraction of that, because it only includes what matters.
The dependency resolution
The interesting technical detail is how cxpak resolves imports to files. A Rust use crate::scanner could map to src/scanner/mod.rs or src/scanner.rs. A Python from utils.helpers import parse could be utils/helpers.py or utils/helpers/__init__.py. A TypeScript import { foo } from './bar' could be bar.ts, bar/index.ts, or bar.js.
cxpak tries every reasonable permutation for each language. It’s heuristic, not perfect — but it covers the conventions that matter. When a resolution fails, the file is simply excluded from the graph. No hallucinated dependencies.
Trade-offs
One-hop is fast and usually sufficient — the target, its callers, and its callees. Full BFS with --all is more complete but noisier; it can pull in half the codebase if your dependency graph is more inter-connected. Start with the default. Widen if the model asks questions about code it can’t see.
The dependency graph is built from static imports, not runtime call sites. If your codebase uses heavy reflection, dynamic dispatch, or plugin loading, trace won’t find those connections. That’s a fundamental limitation of static analysis — and it’s an honest one. Better to miss a runtime-only dependency than to hallucinate one.
Give your model a map, not a haystack. It’ll return the favour.