02 · Static-analysis engine
RepoGraph
A Rust engine that uses Tree-sitter to map the calls and imports in a Python or TypeScript repo, then tells you which tests a change can break, without guessing.
- cold index of Flask 3.1.2
- 362 ms
- cold index of Flask 3.1.2
- 83 files · 17,874 LOC
- incremental re-index
- 49 ms
- incremental re-index
- content-hash reuse
- dependency + call edges
- 14,954
- dependency + call edges
- peak memory (RSS)
- 42.5 MB
- peak memory (RSS)
Why
“Which tests does this change affect?” It should have a real answer.
In big codebases people either run every test or guess. RepoGraph parses a Python or TypeScript/JavaScript repo with Tree-sitter and builds an index of every definition, import, reference and call, with exact source locations. Then it answers graph questions from that index: callers, references, transitive impact and affected tests. It uses no LLM.
It's conservative on purpose. A call only gets linked when there is exactly one static target. Anything less certain is labelled, never quietly turned into a confident edge.
Try it
Impact analysis, live.
Below is RepoGraph's own 11-file Python + TypeScript test fixture. The symbols, locations and edge states come from running the real binary on it. Pick a symbol and you get the reverse-reachable set that repograph impact reports, plus the tests it reaches.
Click any symbol to see what a change to it can break.
tests/auth.test.tsts
src/auth.tsts
src/api.tsts
app.pypy
pkg/service.pypy
pkg/__init__.pypy
↳ re-exports Account
ambiguous.pypy
src/use-default.tsts
src/defaulted.tsts
src/dynamic.tsts
? value() — dynamic target
$ repograph impact src.auth.audit
SYMBOLS
- src.api.authFunctionapi.ts:7:14
- src.api.loginapi.ts:3:8
- src.auth.auditauth.ts:1:8
- src.auth.User.authenticateauth.ts:6:3
- tests.auth.test.testAuthenticationauth.test.ts:3:8
FILES
- src/api.ts
- src/auth.ts
- tests/auth.test.ts
AFFECTED TESTS
✓ tests/auth.test.ts
Architecture
Six layers, each with one job.
01
discover
walk the repo, respecting .gitignore
02
parse
Tree-sitter: defs, imports, refs, calls
03
analysis
resolve modules, bindings, self/this
04
graph
forward/reverse views, Tarjan SCC
05
query
impact, callers, affected tests
06
index
versioned JSON, atomic writes
Exactly one static target. These are the only edges impact analysis follows.
More than one possible definition. Every candidate ID is kept and none is guessed.
Dynamic or unknown target, like value(), getattr or a callback. Recorded, not invented.
Points outside the repo, such as a stdlib or package import.
Incremental indexing: on a re-index, every file is content-hashed. Unchanged records are reused, deleted files drop out, and only changed files get re-parsed. Global resolution is then rebuilt from cached and fresh records together. Every definition gets a stable ID made of its path, location, kind and qualified name, so duplicate or overloaded names never merge.
Benchmark
On a real codebase: Flask.
$ repograph benchmark ./flask { "files_indexed": 83, "loc": 17,874, "symbols": 1,882, "graph_edges": 14,954, "parse_errors": 0, "cold_index_ms": 362, "cached_index_ms": 49, "cached_reload_ms": 14, "peak_rss_bytes": 42,516,480 }
Correctness
Tested against a known graph and generated graphs.
11 files
A checked-in mixed-language fixture with a known graph. It covers import resolution, cross-file calls, self.validate, duplicate definitions becoming ambiguous, syntax-error recovery, affected tests and full cache reuse.
64 × 18
A property test generates 64 random directed graphs of 18 nodes each. For every one it checks that the SCC output partitions the node set exactly and that every pair in a component can reach each other.
$ cargo fmt --all -- --check $ cargo clippy --all-targets -- -D warnings $ cargo test --all-targets
Limits
Static analysis has edges.
- Python monkey-patching, getattr, runtime imports and wildcard imports usually stay unresolved.
- TypeScript path aliases, overload selection and inferred receiver types aren't type-checked yet.
- Calls through callbacks, dependency injection or reflection resolve only with a unique binding.
- Affected tests are a conservative graph-plus-naming heuristic. Tests chosen at runtime can be missed.
