When an AI coding agent needs to change one method, it usually reads the whole file first. Then the next file. Then it greps, and reads a few more.
Most of those tokens are spent on finding the way, not on the task.
I built callgraph to give the agent a map instead: the structure of a file, every call labelled by kind, who calls a method across the project, and only then the one method body it actually needs.
What the agent sees
Take a 400-line Laravel service. Instead of reading all of it, the agent asks for the outline:
$ callgraph.sh app/Services/OrderService.php
class OrderService L30-412
constants (1):
MAX_ITEMS = 100
properties (7):
OrderRepository $orders [readonly]
PaymentService $payments [readonly]
…
updateOrders() L44-109
framework_use Illuminate\Support\Facades\DB::transaction
internal_helper updateOrder
model_use App\Models\Order::query
static_call App\Values\OrderUpdateResult::make
💬 A single-order update keeps the old values of empty optional fields.
…
Every method with its line range, and every call grouped by what it is: ORM / model use, framework facade, internal helper, static call. The comments that explain intent stay. If the agent needs the code of one method, it asks for exactly that: callgraph.sh app/Services/OrderService.php::updateOrders --body.
"Who calls this?"
Before changing updateOrders, the agent wants to know what depends on it:
$ callgraph.sh app --grep '(^|::|\.|->)updateOrders$'
app/Http/Controllers/OrderController.php
class OrderController L18-80
update() L45-66
L52 │ $result = $this->orderService->updateOrders($request->orders, $request->toDto());
The caller, its line, and the call itself — no file read needed. --depth 2 follows the chain one level further (impact analysis), and calls through interfaces, traits and parent classes are found too.
"Where do I start?"
For a task description, locate takes the names and words from it and ranks the few files worth opening first, each with the reason:
$ callgraph.sh locate CheckoutController applyCoupon coupon
1. app/Http/Controllers/CheckoutController.php ★★★
class CheckoutController L16-74 ← "CheckoutController" (exact class)
apply() L38 calls CouponService::applyCoupon ← "applyCoupon" (call)
2. resources/js/types.d.ts ★
interface Coupon L120-131 ← "coupon" (exact interface)
…
And for an unfamiliar project, callgraph.sh profile describes the architecture in about 15 lines: which folder holds which layer (controllers, services, models, Vue components, stores…), the patterns in use, and the direction the layers call each other.
How it works
- AST analysis, not regex: PHP (Laravel, Yii), JavaScript / TypeScript, Vue, Rust and Swift, plus CSS / SCSS, JSON and Markdown outlines.
-
A project map in
.callgraph/, updated file by file, so "who calls this?" across a large project is answered from one read. - Local and read-only. Your code never leaves your machine. No PHP, Node or Rust toolchain needed: it is one static binary for macOS, Linux and Windows.
- Three ways to use it: a Claude Code skill, plain CLI from any terminal, or an MCP server for Cursor, VS Code, Claude Desktop and any MCP client.
- Honest about gaps: a file with a syntax error is still outlined up to the broken part, and the output says so instead of silently returning nothing.
Try it
# download the package for your OS from https://callgraph.eu/ , unzip, then:
bash callgraph/install.sh
bash ~/.claude/skills/callgraph/callgraph.sh --version
The installer can also register the MCP server for Claude Code. Setup recipes for Cursor, VS Code and Claude Desktop are on callgraph.eu/mcp.
Free and open source (MIT): github.com/GeRiY/callgraph
How does your agent find its way around a large codebase today? I'd love to hear what works for you — and which languages or frameworks you'd want next.
Top comments (2)
Agreed on where the tokens go: 412 lines read versus a 12-line outline is the right trade. The catch I'd watch is staleness, since once the agent edits OrderService mid-session the line ranges in the map drift. Does callgraph re-index on file change, or on each query?
Good catch — staleness is the first thing that bites with any code map. Both, actually:
callgraph.sh refreshdoes the whole project after a git pull.A file with a syntax error mid-edit is still outlined up to the broken part, and the output says so ("⚠ syntax error at L…") instead of silently returning stale or empty results.