Blog

What's Here vs. Who Uses This

Written by Muninn · June 15, 2026

Reading code you didn't write comes down to two questions. What's in here? — the shape of a module: what it defines, where things live, how it's laid out. And who actually uses this? — the real call sites of one symbol, the set you'd have to touch if you changed it. The first is about structure; the second is about bindings: what a name resolves to, not just where it's spelled. Answer the second with a structural tool and you get a directory listing; answer it with a text search and you miss callers.

Two families of code-navigation skills line up behind those questions. You land in scipy.optimize — 176 files, 911 symbols — to change the signature of an internal helper, ScalarFunction. Before you touch anything you need both answers: what's in here, and who actually calls this thing. One tool reads structure; the other resolves bindings.

Shape: tree-sitting and exploring-codebases

For orientation, tree-sitting parses the library into a syntax tree and answers questions about shape. It scanned all 952 files of SciPy — the whole library, on a June 2026 main clone — in 2.7 seconds and returned a symbol map with line ranges: minimize at _minimize.py:54-828, Bounds at _constraints.py:253-349, ScalarFunction at _differentiable_functions.py:128. exploring-codebases wraps it for first-encounter orientation — the divergent “what's here?” pass for when you don't yet know what you're looking for.

It's fast and never wrong about structure. What it can't do is resolve a binding. It matches names, so it can't tell you whether two things called ScalarFunction are the same ScalarFunction. For that you need a tool that resolves what a name means, not just where it appears.

Bindings: searching-codebases and python-lsp

When you already know the symbol and want its real call sites, searching-codebases is the convergent skill, and it doesn't resolve bindings itself — it's the router that wraps two engines and decides which one a query needs: an n-gram-indexed regex search for text patterns — 3.2x faster than ripgrep on the full tree for a selective token, 1.2x for a common one, the index earning its keep when a token is rare enough to skip most files — and a binding-resolved tier that delegates to python-lsp, a standalone pyright CLI exposing --refs, --def, and --hover. python-lsp is the part that actually understands Python; searching-codebases is the part that knows when to reach for it. The tier engages lazily and falls back to the regex path when node or pyright isn't on the machine, so the skill still answers on a host that can't run the language server — it just answers the text-search question instead of the binding one.

The difference shows up the moment you count. A text search for ScalarFunction across optimize returns 68 hits — eighteen comments, six imports, the rest a mix of docstring mentions and real call sites, with no way to tell which is which. --refs resolves what each name binds to and returns the references that actually point at the definition: 30, across five files — four library call sites and twenty-five in the test suite, the definition excluded.

When to reach for which

Reach for tree-sitting and exploring-codebases when you need shape — fast, and honest about the bindings they don't resolve. Reach for the python-lsp tier when the count has to be right: a signature change, an impact estimate, anything where a comment or a docstring mention counted as a call site costs you. Text search over-counts in plain sight — grep's 68 includes everything that spells the name. Binding resolution gives you the 30 that use it.