The dependency graph is the foundation everything else sits on. Risk analysis, dead-code detection, blast radius, even the wiki itself: all of it walks the graph repowise builds at index time.
Two tiers, one graph
Tree-sitter parses every file in the repo into:
- File nodes: one per source file, with imports / imported-by edges.
- Symbol nodes: one per function, class, method, with caller / callee edges.
Both tiers live in a single NetworkX graph. You can traverse from file to file, file to symbol, or symbol to symbol in a single query.
Call resolution: a closed vocabulary, not three tiers
Naive call resolution is "the function name matched a definition
somewhere, so link them." That approach is wrong a meaningful fraction
of the time on real codebases (overloads, name collisions, dynamic
dispatch). Repowise instead stamps every calls edge with a
resolution origin drawn from a closed vocabulary of 29 values in
ResolutionOrigin, and every origin carries exactly one fixed
confidence, from 0.95 (same_file, defined in the calling file) down
to 0.50 (global_unique, the name is unique repo-wide, and the source
itself calls this one a guess).
A call on a variable (user.save()) resolves by reading the
receiver's declaration and typing it, rather than matching save
against every method with that name in the repo. Receiver typing ships
for Java, C#, Python, Go, Kotlin, and Swift, each gated on a
measured precision audit before it shipped.
The MCP get_context tool filters callers/callees to confidence
≥ 0.7 by default: high-precision over recall.
What the resolvers handle
- Import aliases (
import foo as bar) - Barrel re-exports (
export * from "./x") - Namespace imports (
import * as Foo) - Path-mapped imports (TypeScript
tsconfig.jsonpaths, C#.csproj, Gogo.modreplace directives, RustCargo.tomlworkspace members) - Heritage: extends, implements, trait impls, derive macros, mixins, Swift extension conformance
- Framework-aware edges (Django routes, FastAPI dependency injection,
Spring
@Componentgraphs, Express routers, Rails Zeitwerk)
Per-language detail: Language support.
On top of the graph
The graph alone isn't intelligence; it's data. Repowise computes:
- PageRank: global importance ranking.
get_context(include=["metrics"])surfaces this per file. - Betweenness centrality: files on many critical paths.
- In/out degree: direct dependents and dependencies.
- Strongly connected components: circular import groups.
- Leiden community detection: logical modules even when your directory structure doesn't reflect them. Each community gets a cohesion score and a label drawn from its most-common path segment.
- Execution flow tracing: from each natural entry point, follow the call graph and rank what's reached.
All of these are exposed through get_overview,
get_context, and get_risk.
See it on a file
From the CLI, pull PageRank, betweenness, and degree for one file with
--include metrics, or the callers/callees the resolver linked with
--include callers:
repowise context src/auth.py --include metrics
repowise context src/auth.py --include callersFrom an agent:
get_context(targets=["src/auth.py"], include=["metrics", "callers"])Refreshing
repowise update rebuilds only the slice of the graph affected by
changed files (incremental). A typical commit affects 3 to 10 nodes and
takes under 30 seconds.