| name | graphlint |
| description | Load for codebase understanding, exploration, and modification — maps the real dependency graph to separate live, reachable code from dead code, unused imports, and circular references, so refactors, cleanups, and deletions are safe. |
graphlint — Dead-Code & Dependency Analysis
Static analysis of a project's dependency graph: finds components unreachable from any entry point (dead code), plus circular references, unused imports and other warnings.
Languages: Python (built in) · Rust (pip install graphlint[rust]) · C# (pip install graphlint[csharp]) · C/C++ (pip install graphlint[c]) · TypeScript/JavaScript (pip install graphlint[typescript])
When to use
- When understanding or exploring a codebase: see how a feature is wired in and which code is actually reachable.
- Before modifying, refactoring, or deleting code: confirm a symbol is truly live before you change or remove it.
- After modifications: verify your edits left no dead or redundant code behind.
Core commands
graphlint query
graphlint query --json
graphlint query -g <id> -d full
graphlint config show
Run query normally — it keeps the index up to date by itself (automatic incremental build). Use graphlint build --force only when query results look wrong or stale; a full rebuild is slow on large codebases.
Key parameters
-g, --graph-id <id> — detail on one graph
-j, --json — structured output
-w, --warn-types <list> — filter, e.g. dead_code,circular_ref,unused_import
-C, --exclude-clean — only graphs with issues
-R, --reachability — only graphs reachable from entry points
--public-as-entry — treat public items (Rust pub, C# public, C external-linkage symbols) as entry points (library analysis)
-t, --include-tests — include test files
--dead-code-tests — find tests that reference suspected dead code
--sort-by <warnings|nodes|edges|name> · --min-nodes N · --max-nodes N · -n, --max-results N
-r, --root-dir <path> — project root (default .)
--fail-on <types> — exit non-zero when matching warnings exist (CI gates)
- build:
-f, --force · -P, --parallel N
graphlint query -h lists everything.
Custom entry rules
Dead-code results depend on correct entry points. If graphlint flags code your framework invokes dynamically (routers, plugins, DI containers, getattr/importlib), add entry rules matching your project's conventions instead of deleting that code:
graphlint config add-entry-rule --rule-json '{"name":"my_service","ast_pattern":"class_instantiation:FastAPI","file_pattern":"**/service.py"}'
graphlint config remove-entry-rule --name my_service
graphlint config add-exclude --exclude-pattern "*/generated/*"
Examples
graphlint query -C --json
graphlint query -w dead_code --sort-by warnings
graphlint query -g 5 -d full
graphlint query --fail-on dead_code,circular_ref
Limitations
- Static analysis only — dynamic references (
getattr, importlib, reflection, DI containers) can yield false positives. Verify before deleting and add custom entry rules (above) for your conventions.
- C/C++ family — a single unified C/C++ analyzer owns
.c/.h/.cpp/.cc/.cxx/.hpp/.hh/.hxx. Routing is compiler-faithful: .c uses tree-sitter-c, the C++ suffixes use tree-sitter-cpp, and a .h is routed by the language of the TU(s) that include it (never by content-sniffing). The preprocessor is treated as pure AST (#if is never evaluated, macros are not expanded); function-pointer call targets are not traced; member calls resolve type-aware across translation units (namespace- and template-qualified receiver types are normalised to the class's simple name), but there is no dynamic dispatch / virtual / template instantiation. A C++-defined class with no main and only headers needs custom entry rules (file_match:**/*.h); C projects enter library mode automatically (c_library_mode, default auto) — external-linkage symbols become entries. (Known, pre-existing C flat-name limitation: function parameters are not materialised as nodes, so a parameter named like a caller's local can resolve to that local by flat name and add a spurious read edge.)
- Build cost — a full rebuild of a large codebase (~700 files) takes ~200 s; small projects (~60 files) take ~1 s. Regular
query runs are incremental and cheap — avoid build --force mid-refactoring.