calls:// — cross-file call graph queries for impact analysis, refactoring, and understanding code flow
There's a question that comes up constantly when working on a codebase:
"If I change this function, what breaks?"
The answer requires knowing every place that function is called — across all files, not just the current one. With grep you can get close, but you'll miss indirect calls, get false positives from comments and strings, and have to mentally filter what's actually a caller vs a usage.
Reveal's calls:// adapter builds a proper call graph index and answers the question directly.
That's not a search problem. It's a graph query.
reveal 'calls://src/?target=validate_token'
Output:
Callers of validate_token (4 found):
src/auth/middleware.py:23 authenticate_request
src/auth/middleware.py:89 refresh_session
src/api/routes/users.py:47 get_user_profile
src/api/routes/admin.py:156 check_admin_access
Next: reveal 'calls://src?target=validate_token&depth=2' # Callers-of-callers
reveal src/auth/middleware.py authenticate_request # Read a caller
Four callers, exact file and line numbers, no noise.
The three questions calls:// answers
calls:// answers questions in both directions — who calls a function, what it calls, and where coupling is highest:
graph LR
callers["authenticate_request<br/>get_user_profile<br/>check_admin_access"]
fn["validate_token"]
callees["decode_jwt<br/>check_expiry<br/>load_session"]
hotzone["Most-coupled<br/>functions"]
callers -->|"?target=fn ←reverse"| fn
fn -->|"forward→ ?callees=fn"| callees
fn -. "?rank=callers" .-> hotzone
style fn fill:#3b82f6,color:#fff,stroke:none
style callers fill:#f1f5f9,stroke:#94a3b8
style callees fill:#f1f5f9,stroke:#94a3b8
style hotzone fill:#fef9c3,stroke:#ca8a04
1. Who calls this? (impact analysis)
Before changing validate_token, you need to know its blast radius:
reveal 'calls://src/?target=validate_token'
If you get 12 callers across 8 files, that's a significant change. If you get 1, it's a targeted edit. Either way, you know before you start.
2. What does this call? (forward lookup)
Sometimes you want to trace down rather than up — understand what a function depends on:
reveal 'calls://src/?callees=validate_token'
Output:
validate_token calls:
src/auth/tokens.py:14 decode_jwt
src/auth/tokens.py:23 check_expiry
src/models/session.py:67 load_session
(3 builtins hidden — use ?builtins=true to include)
Python builtins (len, str, ValueError, etc.) are filtered by default — you almost never care that a function calls len. Add ?builtins=true to include them.
3. Which functions are most coupled? (architectural analysis)
Which functions in your codebase are called from the most places? High in-degree means high coupling — changes there have high blast radius.
reveal 'calls://src/?rank=callers&top=10'
Output:
Most-called functions: src/
Ranking by: caller count (in-degree)
Showing: top 10 of N unique callees
validate_item (18 callers)
src/validation.py:23 check_input
src/validation.py:67 validate_batch
src/api/routes.py:44 create_item
… and 15 more
get_db_connection (14 callers)
src/database.py:88 execute_query
src/models/user.py:34 get_user
src/models/item.py:21 save_item
… and 11 more
...
One thing to expect on real Python projects: builtins like get, append, join will appear at the top with hundreds of callers. Filter them out mentally or use ?top=20 to get past them to your domain functions.
The top domain functions in this list are your architectural hot zone — the ones worth abstracting, documenting carefully, and testing thoroughly.
How it differs from grep
grep -r "validate_token" src/ # Text search
vs.
reveal 'calls://src/?target=validate_token' # Call graph query
The difference:
- Grep finds text occurrences — strings, comments, variable names, imports
- calls:// finds actual function calls — only invocations, only within function bodies
You get fewer results with calls://, but all of them are real.
Like every Reveal adapter, calls:// exposes code as queryable structured data — the same URI syntax, the same composability, the same pipeline semantics. Impact analysis becomes a query, not an investigation.
Transitive callers (call chains)
Sometimes you need to know not just who calls your function, but who calls those callers:
reveal 'calls://src/?target=validate_token&depth=2'
Callers of validate_token (depth=2):
Level 1 (direct callers):
middleware.py:23 authenticate_request
routes/users.py:47 get_user_profile
Level 2 (callers of callers):
middleware.py:23 authenticate_request
← wsgi.py:15 dispatch_request
← wsgi.py:38 handle_error
routes/users.py:47 get_user_profile
← router.py:67 route_request
The tree is easier to read visually than the indented output:
graph TD
vt["validate_token"]
vt --> ar["authenticate_request"]
vt --> gup["get_user_profile"]
ar --> dr["dispatch_request"]
ar --> he["handle_error"]
gup --> rr["route_request"]
style vt fill:#3b82f6,color:#fff,stroke:none
style ar fill:#e0f2fe,stroke:#0284c7
style gup fill:#e0f2fe,stroke:#0284c7
Depth is capped at 5 to prevent combinatorial explosion on tightly-coupled codebases.
Visualizing call graphs
calls:// supports Graphviz dot output for documentation and presentation:
reveal 'calls://src/?target=validate_token&format=dot' | dot -Tsvg > call_graph.svg
Or PNG:
reveal 'calls://src/?target=main&format=dot' | dot -Tpng > architecture.png
Good for: architecture docs, onboarding materials, "what does this module touch?" diagrams.
Within a single file (the cheaper option)
For within-file call analysis, use ast:// — it doesn't build a project-wide index so it's faster:
# See what functions exist in a file and what they call
reveal 'ast://src/auth.py?show=calls'
# Find callers of a function within the same file
reveal 'ast://src/auth.py?calls=validate_token'
Use ast:// when you're looking within one file. Use calls:// when you need cross-file coverage.
Practical workflows
Before refactoring a function
# 1. How many callers?
reveal 'calls://src/?target=process_payment'
# 2. What does it call?
reveal 'calls://src/?callees=process_payment'
# 3. Now you have the full picture: blast radius + dependencies
Finding dead code
# Surface all uncalled functions across the project at once
reveal 'calls://src/?uncalled'
Output:
Dead code candidates: src/
Total defined: 847 functions/methods
Uncalled: 12
Note: excludes __dunder__ methods and @property/@classmethod/@staticmethod
src/legacy/sync.py:33 legacy_sync_handler (function)
src/utils/compat.py:91 _py2_fallback (function, private)
src/api/deprecated.py:14 old_create_endpoint (function)
...
This scans the entire project and lists every function with no callers — much faster than checking one at a time. Excludes dunder methods, properties, and classmethods automatically (they're called implicitly).
For targeted confirmation on a specific function:
reveal 'calls://src/?target=legacy_sync_handler'
# → No callers found.
Combine with reveal check src/ --select R (refactoring rules) to surface candidates systematically.
Understanding an unfamiliar codebase
# Start with the most-called functions — they're the core abstractions
reveal 'calls://src/?rank=callers&top=20'
# Then explore one by one
reveal 'calls://src/?callees=get_db_connection'
Architecture documentation
# Generate a call graph for your main entry point
reveal 'calls://src/?target=main&depth=3&format=dot' | dot -Tsvg > docs/call_graph.svg
Performance
calls:// builds an index the first time you run it on a directory and caches it by file modification time. Subsequent runs on unchanged code are fast. Large projects (10K+ Python files) may take a few seconds on first run; after that, cached.
The index is directory-scoped — calls://src/ and calls://src/auth/ build separate indexes.
Note:
calls://is marked experimental. It works well on Python codebases; coverage on other languages varies.
The broader picture
calls:// is one piece of Reveal's approach to treating code as structured data rather than text. Grep treats your codebase as a pile of strings. calls:// treats it as a graph of semantic relationships.
The difference shows up when you need answers that text search can't give you cleanly — impact analysis, dead code detection, coupling metrics, architectural understanding. For agents, it's even more direct: instead of reading files to infer structure, you query the graph for exactly what you need in a fraction of the tokens.
You don't ask "where is this used?" anymore. You query the graph and get the answer.
pip install reveal-cli
reveal help://calls # Full reference with examples
reveal 'calls://src/?target=YOUR_FUNCTION'
Part of the Reveal documentation series. See also:
- Your Project Has an API Now — the full query layer
- Structural Diffs, Not Line Diffs — change analysis with diff://
- Two Commands That Change How You Work With Code — pack and review
- Stop Reading Code. Start Understanding It. — the big picture
