Beamscope.Repo (Beamscope v0.1.0)

Copy Markdown View Source

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

Shortest call path between two functions, if one exists, each hop enriched with its definition location like callers/3/callees/3.

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

repo_path()

@type repo_path() :: String.t()

Functions

call_path(repo_path, from_module, from_function, to_module, to_function)

@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.

callees(repo_path, module, function)

@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.

callers(repo_path, module, function)

@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.

indexed?(repo_path)

@spec indexed?(repo_path()) :: boolean()

Whether repo_path has a cached call graph.

reindex(repo_path, opts \\ [])

@spec reindex(
  repo_path(),
  keyword()
) :: :ok | {:error, String.t()}

Rebuilds both the call graph and, if the optional embedding deps are installed, the search index for repo_path.

search(repo_path, query, opts \\ [])

@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.