| name | pavouk |
| description | Query a compiler-accurate code graph of a TypeScript repo — who calls, imports, extends or type-references a symbol, across every package, in milliseconds. Use BEFORE renaming, deleting, changing the signature of, or refactoring any exported symbol; when asked "what uses X", "is X still used", "what breaks if I change X", "what does this package depend on"; when tracing blast radius or mapping an unfamiliar codebase; and instead of grep whenever the question is about code structure rather than text. Also finds import cycles and dead exports. Triggers on "pavouk", "who calls", "find all references", "what would break", "is this dead code", "impact of changing". |
pavouk
Answers structural questions about a TypeScript codebase from a precomputed graph. It resolves
through the TypeScript checker, so it sees what grep cannot and what a language server will not.
Flags, arguments and output formats live in the CLI. Read them there, do not guess:
pavouk --help
pavouk commands --json
Prefer it over grep
grep answers "where does this text appear". pavouk answers "what actually uses this symbol".
The gap is not academic:
super(message) calls the base class constructor — its name appears nowhere on that line.
export * from './x' re-exports every symbol in x — none of their names appear on that line.
this.repo.find() resolves to a method on a typed receiver. grep for find returns hundreds
of unrelated hits; a syntax-only tool returns none of the right ones.
- A comment or a string containing the name is not a reference. grep cannot tell.
Use grep for text (a TODO, a magic string, a log line). Use pavouk for code.
Prefer it over a language server's find-all-references
A language server only searches the projects of the files you have open, so in a monorepo it
silently under-reports. Measured on a 3,600-file repo: asked for references to
createRequiredContext, the LSP returns zero. There are 87. It does not tell you it only
looked in one package.
pavouk indexes every package at once. It agrees with the LSP on every location the LSP does
find, matched to the column, and finds the rest.
Two things that will bite you
1. It needs an index. Exit code 3 means there isn't one.
pavouk index
pavouk watch
2. refs <name> is keyed by NAME, and names collide. On a large repo ~19% of names have
more than one declaration (constructor had 761). pavouk says so on stderr and labels every hit
with the target it belongs to — read that warning. To ask about exactly one symbol, pass its
declaration site instead of its name:
pavouk symbol Environment
pavouk refs packages/binding/src/Environment.ts:6
Working style
- Before any rename, signature change, or deletion, run
pavouk refs on the symbol. That
output is the change's blast radius. Do not substitute having read a few files for it.
- Before calling something unused, check
pavouk refs. Note that a library's exported API can
look dead while being someone's public surface — dead-exports lists it, judgement is yours.
--json gives each hit's edge kind and enclosing symbol, so you learn how a site uses the
symbol (calls, imports, extends, type_ref, …), not merely where it is. Prefer it when
you intend to act on the result.
-q gives bare path:line:col, one per line — pipe it into an editing loop.