Unified per-repo entry point over call-graph navigation and semantic
search — the single interface MCP tools (and any other caller) should
use rather than reaching into Beamscope.Callgraph.Store or
Beamscope.Search.Store directly. Both stores already cache/persist per
repo_path on their own; this module adds no state of its own, just one
API surface over both.
Summary
Functions
Every function module:function calls, each enriched with its definition
location like callers/3.
Every function that calls module:function, each enriched with its
definition location (file_path/start_line/end_line) when known —
see Beamscope.Callgraph.Graph.callers_with_locations/2.
Whether repo_path has a cached call graph.
Rebuilds both the call graph and, if the optional embedding deps are
installed, the search index for repo_path.
Two independent kinds of results for a natural-language or exact-name
query: exact_matches (a literal, in-process grep for identifier-like
terms in the query — see Beamscope.Search.LexicalSearch, works with no
ML deps installed) and semantic_matches (top-K similar chunks via
Beamscope.Search.Store.search/3). Returned as two separate lists rather
than one blended ranking, since an exact match and a cosine-similarity
score aren't the same kind of signal. semantic_error is set (and
semantic_matches is []) when the embedding side fails or the optional
ML deps aren't installed — that no longer fails the whole call.
Types
@type repo_path() :: String.t()
Functions
@spec call_path(repo_path(), String.t(), String.t(), String.t(), String.t()) :: {:ok, %{from: String.t(), to: String.t(), path: [map()] | nil}} | {:error, String.t()}
Shortest call path between two functions, if one exists, each hop
enriched with its definition location like callers/3/callees/3.
@spec callees(repo_path(), String.t(), String.t()) :: {:ok, %{qualified_name: String.t(), callees: [map()]}} | {:error, String.t()}
Every function module:function calls, each enriched with its definition
location like callers/3.
@spec callers(repo_path(), String.t(), String.t()) :: {:ok, %{qualified_name: String.t(), callers: [map()]}} | {:error, String.t()}
Every function that calls module:function, each enriched with its
definition location (file_path/start_line/end_line) when known —
see Beamscope.Callgraph.Graph.callers_with_locations/2.
Whether repo_path has a cached call graph.
Rebuilds both the call graph and, if the optional embedding deps are
installed, the search index for repo_path.
@spec search(repo_path(), String.t(), keyword()) :: {:ok, %{ exact_matches: [map()], semantic_matches: [map()], semantic_error: term() | nil }} | {:error, String.t()}
Two independent kinds of results for a natural-language or exact-name
query: exact_matches (a literal, in-process grep for identifier-like
terms in the query — see Beamscope.Search.LexicalSearch, works with no
ML deps installed) and semantic_matches (top-K similar chunks via
Beamscope.Search.Store.search/3). Returned as two separate lists rather
than one blended ranking, since an exact match and a cosine-similarity
score aren't the same kind of signal. semantic_error is set (and
semantic_matches is []) when the embedding side fails or the optional
ML deps aren't installed — that no longer fails the whole call.
exact_matches is ranked using the call graph beamscope already builds: a
match whose line is a known function's definition line is tagged
match_kind: :definition and sorted ahead of everything else (tagged
:reference — call sites, specs, comments, unrelated same-name hits in
other files), then capped at :limit. Ranking before capping matters —
scanning the whole repo but capping before ranking is exactly how a real
definition previously got crowded out by less-relevant matches that
happened to come first in file-walk order.