codemap report dead-code flags private functions with no incoming resolved call
— triage, never proof (call resolution is partial by design; dynamic dispatch, CLI
entry points and test targets are blind spots). What sets codemap apart from a
call-only tool (vulture) is that its cross-root graph lets it grade each candidate
and say why, so you can tell a real corpse from a framework-wired function.
| Level | Meaning | Typical reason |
|---|---|---|
| high | No inbound edge of any kind, no decorator, no registry — and it overrides nothing | no inbound calls, references, or decorators |
| medium | A decorator or registry membership could invoke it implicitly; or it overrides a base method that is itself uncalled here | decorated by @route — may be invoked implicitly · overrides Base._open, which is itself uncalled here — dead only if the base is |
| low | Something references it (re-export, a name in a list, a registration), or it overrides a called base → likely alive | referenced (references) by tests×3 · overrides PIL.ImageFile.ImageFile._open, which has 1 inbound call(s) — reached by dispatch, not by name |
A method that overrides an ancestor's method is not called by its own name — the base is called and dynamic dispatch lands in the override. So "no inbound calls" is a statement about the name, not about the body, and it cannot carry the strongest grade.
Measured on Pillow, where the shape is the whole architecture: 40 of 63 high
candidates were _open implementations overriding ImageFile.ImageFile._open, which the
same graph records as called from ImageFile.__init__ (self._open()). 63 % of the band a
reader acts on, wrong — on the most ordinary shape in object-oriented Python. After the fix
that package's high band is 15. On codemap's own tree and on the second dogfood target the
band is unchanged, which is precisely why a month of dogfooding never produced it: neither
tree has a private override of a called base.
The walk is transitive and prefers the ancestor that is actually called: in a three-deep chain the middle link is an override too, so it has no inbound call of its own, and naming it would report "the base is itself uncalled" while the call sits one level further up.
The low tier is the false-positive cut: a private helper that a test, a re-export, or a registry points at is referenced, so codemap says so and grades it down instead of listing it as dead. The reason names who references it, across roots (core / tests / docs / …) when the graph was built repo-scoped.
Grading is only as good as the edges it can see, and three source-visible forms used to produce none — so a live function landed in high, the band a reader acts on (#7; measured at 20 of 51 candidates across two real packages, codemap's own included). All three are modelled now:
| Form | Example | Edge |
|---|---|---|
| function as a value | PANELS = {"a": _panel_a}, json.dumps(o, default=_json_default) |
references, extras.resolution="name" |
| type annotation | def render(...) -> Report: |
references, extras.resolution="annotation" |
| module-level call | _register_all_indicators() at import time |
calls, sourced from the module node |
| call inside a closure | a call in a nested def or a dynamically-built class body |
calls from the innermost enclosing definition, extras.via="nested" |
| used through a function-local import | def go(): from .leaf import helper; return helper(x) |
calls / references, extras.resolution="imported" (R1-C30) |
The last one is the youngest, and it is the form most likely to hide a live symbol: a lazy
import is what a developer writes when the dependency is awkward, which usually means it is
load-bearing. Before R1-C30 the fast tier could not see it, so such a symbol could be graded
dead while being called on every run. Measured when it landed, the honest version: on both
dogfood trees the banded lists did not change — but three symbols went from zero inbound
edges to one, among them codemap's own DebouncedPoller, which the whole codemap watch
loop is built on and which its own graph showed as used by nothing.
Two of those are missing call edges, so this was never only a dead-code question:
impact, callers and flows were reading a call graph with import-time work and
closure bodies cut out of it.
Annotations are labelled apart from values on purpose: a dispatch table implies the
function runs, an annotation implies a contract. Both keep a symbol out of high, and
the distinction is on the edge if you want to weigh them differently. extras.via="nested"
marks the one approximation here — the call's existence is certain, only the definition
it is attributed to is inferred (a closure that is built and returned but never invoked
still yields an edge from its enclosing function).
codemap report dead-code --build ./pkg # all tiers, grouped
codemap report dead-code --build ./pkg --min-confidence high # only the strongest signalsA name defined twice in one scope — a method written twice in a class, a function repeated at module level — is listed in its own section ahead of the graded candidates:
## Shadowed definitions — certain: 1
- `pkg.Thing.get` — defined at line 11 shadows the definition at line 8; the earlier body can never run.
It is not a candidate because it is not a heuristic: Python binds the later body and the
earlier one cannot run whatever calls it. The surviving node carries extras.shadows
(the shadowed line numbers), and the extractor walks only the body that can run — before
this, both bodies' calls were attributed to the one node, so the graph claimed a call
from code that never executes (R1-C46, found by a consumer as a contains record that
appeared twice).
Three idioms rebind a name on purpose and are exempt by decorator, never by guess:
@overload before the implementation, @x.setter / .getter / .deleter, and a
@f.register (or the singledispatch _) after the base. A definition under if or
try — if TYPE_CHECKING:, an except ImportError fallback — neither shadows nor is
shadowed; both bodies keep being walked.
Some functions are wired by machinery codemap's static edges can't see — argparse
set_defaults(func=...), a hand-rolled dispatch dict, a plugin loader. Suppress them
in codemap.toml (exact id or fnmatch glob):
[dead_code]
whitelist = [
"pkg.cli._cmd_*", # argparse-wired subcommands
"*.Session._op_*", # dispatch-dict methods
"pkg.plugins._register_*",
]Whitelisted candidates are dropped from the report (the header shows how many patterns
applied). The whitelist is read relative to the report's root (cwd, or the serve
source_root). Same surface over MCP/serve: the report op takes kind: "dead-code"
plus an optional min_confidence, and reads the whitelist from [dead_code].