Recursive views over the intra-module call graph.
Two ops, both read-only:
ls_deps/2— DFS tree rooted at a{name, arity}definition. Each node is visited at most once across the whole tree: re-encounters (cycles or diamond joins) are markedrepeat: trueand not re-expanded. JSON output preserves the same shape so the full graph can be reconstructed by callers if needed.ls_extract/2— the intra-module-exclusive closure: the target plus everydefpreachable from the closure whose every caller (within the same module) is already in the closure. Suggestion only — used to scope whatextract!would later cut.
Public defs are never pulled into the closure: we can't see their callers outside this file, so we conservatively leave them put. Both ops dispatch per module that contains the definition, so a name+arity that exists in two sibling modules surfaces twice.
Summary
Types
@type definition() :: {atom(), non_neg_integer()}
Functions
@spec ls_deps(String.t(), definition(), keyword()) :: {:ok, map()} | {:error, term()}
@spec ls_deps_file(Path.t(), definition()) :: {:ok, map()} | {:error, term()}
@spec ls_extract(String.t(), definition(), keyword()) :: {:ok, map()} | {:error, term()}
@spec ls_extract_file(Path.t(), definition()) :: {:ok, map()} | {:error, term()}