Rust dependency graph tool — read a Cargo workspace, build its dependency graph, and run queries on top: cycles, reachability, impact, layer rules.
The design in one sentence: the graph is the artifact; everything else is a query over it.
Sibling projects: cartograph (Swift) · gartograph (Go) · kartograph (Kotlin/Android) · dartograph (Dart/Flutter) · schemagraph (databases) · isthmus (cross-language joins).
Rust already has cargo-modules, cargo deps, and a wave of syn/tree-sitter
call-graph tools — but they either stop at module level or guess call edges by
name without saying so. rustograph instead builds one versioned graph at
four levels (crate/module/type/symbol) and expresses every analysis as a query
over it, with a deterministic JSON contract meant for coding agents:
- Tentative edges are labeled, not hidden. A method call
x.m()has no type information in syntax — rustograph emits speculative fan-out edges markedtentative: true, uses them for reachability (erring toward "alive"), and excludes them from cycle and rule evidence with a counted report. - Limitations are measured, not boilerplate. Unresolved paths, macro
bodies,
#[cfg]items, and orphan files are counted per project and travel with every answer. - No delete verdicts.
deadreportsunreachable— a graph fact — never "safe to delete".
brew install ictechgy/tap/rustograph
# or
cargo install --git https://github.com/ictechgy/rustograph --tag v0.3.0Or from source:
git clone https://github.com/ictechgy/rustograph.git
cd rustograph
cargo build --release
# binary at target/release/rustographFor type-resolved analysis, build with the optional semantic feature
(rust-analyzer ra_ap_* crates — heavy, opt-in):
cargo build --release --features semantic
rustograph graph --level symbol --semantic# Emit the dependency graph (deterministic JSON)
rustograph graph # module level
rustograph graph --level crate # packages + dependencies
rustograph graph --level crate --deps # include external crates
rustograph graph --level symbol # call/references/implements/signature
rustograph graph --level symbol --semantic # + type-resolved calls (feature build)
rustograph graph --level type --format mermaid
rustograph graph --out .rustograph/graph.json # persist, then reuse:
rustograph cycles --graph .rustograph/graph.json --strict
# Detect dependency cycles (tentative edges excluded, count reported)
rustograph cycles --level symbol --strict
# Report symbols unreachable from retention roots (fn main, #[no_mangle])
rustograph dead # symbol level, always
rustograph dead --retain-public # libraries: keep exported API
rustograph dead --tests # also retain #[test]/#[bench]
rustograph dead --root mycrate::setup # extra retention root
rustograph dead --explain mycrate::f # why alive? show a reachability path
# Check layer rules from .rustograph.yml
rustograph rules --strict
rustograph rules --format sarif # GitHub code scanning ready
rustograph rules --write-baseline # freeze current violations
rustograph rules --baseline base.txt # only *new* violations fail
# Ask about one symbol (agent-oriented JSON)
rustograph query mycrate::module::f --depth 2
rustograph impact mycrate::Type --depth 3 # reverse transitive closure
rustograph paths mycrate::a mycrate::b # bounded paths between two ids
rustograph search entry # exact > suffix > substring
# partial ids are refused with candidates — retry with an exact id
# Crate-level dependency health (declared vs actually referenced)
rustograph deps # unused deps + duplicate versions
rustograph deps --strict # exit 1 when findings exist
# Trim the document before analysis
rustograph graph --focus mycrate::sub # keep one subtree only
rustograph dead --exclude-tests # drop #[cfg(test)] subtrees
rustograph graph --target x86_64-pc-windows-msvc # evaluate cfg(triple)
rustograph dead --semantic --no-cache # bypass the semantic cache
# Serve the graph over MCP (stdio JSON-RPC) for coding agents
rustograph mcp # harvests once, serves a snapshot
rustograph mcp --graph .rustograph/graph.json
# Emit bridge-facts for isthmus' persistence join (SQL relation uses)
rustograph schema --dir . --out schema-facts.jsonExit codes: 0 ok · 1 strict violation/finding · 2 usage or analysis
error.
Vertices: crate, module, struct, enum, trait, union, typealias,
fn, method, const, static, macro. Edges: depends, uses,
contains, call, references, implements, signature.
containsis ownership, not a dependency — it never feeds cycles or rules.- Levels are projections: the symbol graph is the source of truth; shallower levels fold edges onto owning modules/crates.
signatureedges record types leaked through a function's public signature — they power thesignaturerule and reachability, and let a rules file say "public API may not mention component X".#[cfg]conditions travel as metadata: a vertex'scfgholds the tokens of its own#[cfg(...)](e.g.feature = "x"), and an edge'scfgmarks dependencies that only exist under that condition —usestatements,containsof gated items, bodies of gated functions.unsafemarks the boundary: vertices that areunsafe fn/unsafe traitor contain anunsafe {}block carryunsafe: true, and an edge made inside anunsafe {}block is an entry edge —unsafe implmarks itsimplementsedge too.
rustograph mcp speaks newline-delimited JSON-RPC 2.0 on stdio and serves
nine tools — rustograph_summary, rustograph_query, rustograph_impact,
rustograph_paths, rustograph_search, rustograph_cycles,
rustograph_dead, rustograph_rules, rustograph_deps. The document is
harvested once at startup (or loaded via --graph), so every call answers
over the same snapshot. Partial ids are refused with a candidate list —
call rustograph_search or retry with an exact id.
Semantic-mode documents are cached under .rustograph/semantic-cache.json,
keyed by a fingerprint of workspace sources and manifests — a stale or
corrupt cache silently falls back to a fresh harvest.
components:
core: ["mycrate::core/**"]
ui: ["mycrate::ui/**"]
deps:
ui: [core]
core: [] # allowlist — anything not listed is a violation
deny:
core: [ui] # deny beats allow
signature:
ui: [ui, core] # exported API signatures may only mention these
baseline: .rustograph/rules-baseline.txt # frozen violations (optional)Unmapped modules are reported separately — a rule's blind spot is not a pass.
A baseline file freezes violations that existed when the rules were adopted:
rules --write-baseline records the current set (one rule|from|to|kind
key per line, # comments allowed), and later runs suppress matching
violations while still reporting baselined/stale_baseline counts —
stale entries mean the code improved and the file can be regenerated.
rustograph schema emits an isthmus bridge-facts v1 document
(platform: "rust", target: "persistence") describing how the code
references SQL relations — isthmus joins it with schemagraph facts
output to report missing/unused schema objects and column drift.
Extracted references:
- SQL-looking string literals anywhere (also inside
format!-style macros), scanned forFROM/JOIN/INTO/UPDATE/TABLE/TRUNCATErelations —schema.tablequalifiers and quoted identifiers preserved sqlx::query*macros and functions (query!,query_as!,query_scalar!, …) — literal SQL scanned, non-literal arguments kept asdynamicfacts so isthmus can count the gapsqlx::query*_file!— SQL lives in a file, reported asdynamicdiesel::table!macro bodies — relation plus column uses#[diesel(table_name = …)],#[sea_orm(table_name = "…")]structs and their field/column_name/sqlx::renamecolumns- diesel DSL paths —
users::table,users::dsl::id,users::columns::name— matched against the workspace's declaredtable!names; same-shaped paths that match nothing staydynamic
Unqualified names (query!, sql_query, table!) count only when the
file imports them from sqlx/diesel. Unparseable files, off-grammar
table! bodies,
and column attributes without a table binding surface as limitations,
not silence. The name-based scan never guesses: what cannot be resolved
statically is counted, not invented.
query/impact JSON reports level, depth, truncated, all edge kinds
between neighbors, and per-project limitations. Optional fields that have
no value are omitted — an absent tentative means confirmed, an absent
truncated means complete.
cargo build
cargo test # unit + fixture integration tests
scripts/coverage.sh # tests + coverage gate (90%)
scripts/verify-cli-contract.sh # exit-code contract against the real binary
# dogfooding — analyze this repository with itself
cargo run -- rules --strict
cargo run -- cycles --level symbol --strict
cargo run -- dead --retain-publicCoverage needs cargo-llvm-cov; the script auto-detects llvm-tools from
rustup or the active sysroot.
Syntactic analysis (syn) cannot see through macros, dyn dispatch, or
generics — every such gap is counted in limitations. The optional
semantic feature (cargo build --features semantic, then --semantic)
augments body harvesting with rust-analyzer (ra_ap_*) semantics: method
calls resolve by receiver type instead of name fan-out, macro expansions are
walked, and dyn/generic trait calls expand to workspace impl candidates
(still tentative — the real impl is a runtime fact). The graph contract —
vertices, edge kinds, tentative, limitations — is unchanged; only
accuracy improves. Bodies the semantic engine cannot see (cfg-disabled,
macro-generated) fall back to the syntactic path with measured counters.
MIT — see LICENSE.