Why a Codebase Needs a Graph, Not Just a Search Box

Code-Graph-RAG is a developer tool for asking structural questions about a real, often multi-language repository and then acting on the answers. The question in this series is not whether a language model can produce fluent explanations of code. It is whether the system can connect an answer to the code relationships, observations, and constraints that make the answer worth trusting. That distinction matters whenever a reader needs to understand impact, not just find a familiar word.

A repository can look searchable while remaining difficult to understand. Text search can find every appearance of UserService, but it does not by itself tell us which appearance defines the class, which modules import it, which functions call a particular method, or which implementation satisfies an interface. It can show a configuration key and a logging statement near one another, but proximity does not establish that the value travels from the key to the log. It can find a function named authenticate, while missing a differently named function that performs the same role. The difficulty is not a lack of text. It is the absence of relationships and scope.

The problem is not finding words; it is preserving relationships

Consider a seemingly simple maintenance question: “If I change the authentication service, what might break?” A useful answer could involve callers, imported symbols, subclasses, overrides, tests that reach the changed code, and perhaps values that flow into a security-sensitive sink. These are different relationships. A file name, a matching line, or a language model’s general knowledge cannot reliably substitute for them.

The same issue appears in a polyglot monorepo. A Python service may call into a generated client; a TypeScript front end may depend on an API contract; a Rust component may expose a trait implementation; documentation may explain an operational boundary. The repository is one system from the maintainer’s point of view, even though its parts use different grammars and conventions. A useful code-intelligence tool therefore needs a representation that keeps the parts distinct while making their connections queryable.

This is the role Code-Graph-RAG assigns to a knowledge graph. A graph is not merely a database containing snippets. It represents entities such as modules, functions, methods, classes, files, and external dependencies as nodes, and it represents relations such as “defines,” “imports,” “calls,” “inherits,” or “references” as edges. The difference is practical: a question about impact becomes a walk through relationships rather than a guess assembled from nearby text.

That does not mean a graph makes every answer correct. It means the system has a place to store the structure that an answer must respect. The graph can preserve where a call occurred, how a name was resolved, and which source file contains the definition. Those details allow a reader to inspect the evidence instead of accepting a polished sentence as proof.

What Code-Graph-RAG puts between source and answer

The project has two closely connected parts. Its multi-language parser uses Tree-sitter to read source files and build abstract syntax trees—structured representations of code in which declarations, expressions, and nested scopes have recognizable relationships. From those trees, the parser extracts definitions and relationships and ingests them into Memgraph, the graph database used by the project’s default local stack.

The second part is the RAG system in codebase_rag/. It provides the interactive command-line experience and the agent interfaces that ask questions of the stored graph. In the open-ended path, a configured AI model translates a natural-language question into a Cypher query, the graph query returns candidate results, and the system uses those results to form a response. The model helps formulate and explain the question; the graph is the structural substrate that the response is supposed to consult.

That division changes how the tool should be judged. A model can be useful at understanding a vague request such as “find the code responsible for database connections,” but the graph determines whether the response can be tied to actual modules, functions, and relationships. Conversely, a fixed query for a known symbol does not need a model to rediscover the relationship. The project therefore also exposes deterministic tools for resolving names and locations, retrieving definitions, finding callers and callees, locating implementors and overrides, and identifying importers or tests that reach a symbol.

The graph-centered design also gives the project a path from understanding to action. Code-Graph-RAG documents natural-language editing, AST-based targeting, structural search and replacement, optimization suggestions, dead-code candidates, and duplicate detection. These capabilities are not separate from the graph. They depend on the graph to identify what a change touches and to provide context for a proposal. They still require review: a candidate for dead code is not a guaranteed deletion, and a structurally similar function is not automatically a safe merge.

The resulting operating model is compact enough to keep in mind:

flowchart LR A["Source repository"] --> B["Tree-sitter and AST analysis"] B --> C["Memgraph structural graph"] C --> D["Natural-language or deterministic retrieval"] C --> E["Optional semantic, runtime, and data-flow evidence"] D --> F["Human verification or safe action"] E --> F

The arrows are not a promise that every path runs in every installation. Semantic search is optional. Runtime tracing is an additional observation layer. Data-flow relationships belong to an opt-in capture group. The diagram instead shows the project’s central separation: source is first transformed into a structural model, then different interfaces and evidence layers use that model for questions and decisions.

A graph adds grounding, not omniscience

The most important consequence of this design is a change in what counts as a good answer. A good answer is not simply one that sounds specific. It is one whose specificity matches the evidence available in the graph and the source.

For example, Code-Graph-RAG records call and import relationships with site information when the analyzer can locate the producing expression or statement. A call edge may carry a line and column, argument information, and a label describing whether the relationship was resolved exactly, through an overload, by a heuristic, or from a runtime trace. That makes it possible to ask not only “does this function call that one?” but also “where is the call, and how was it bound?” When the interactive documentation warns that generated query results are candidates and should be checked against the source, it is recognizing the gap between retrieval and proof.

Coverage creates another gap. The project supports many languages through a common schema, but “common schema” does not mean “identical analysis.” Some languages have full structural support; some receive additional compiler-backed facts; the ast-grep tier provides basic definitions and imports without a call graph; Markdown contributes document sections rather than functions and methods. A grammar can tell the system what a declaration looks like, but it cannot by itself tell the system the meaning of every inheritance form, import alias, or qualified name. A partially supported language can therefore produce a graph that looks healthy while lacking a relationship a particular question needs.

Freshness matters for the same reason. A graph is a model of a source state, not a permanent truth about all future commits. Code-Graph-RAG includes incremental and real-time update paths because a useful answer depends on keeping the model aligned with the repository. Even so, a narrow re-ingestion updates changed files and relevant dependents; it does not magically turn every repository-wide analysis into a fresh result. A reader must know whether the graph was built with the current parser mode, exclusions, and capture settings.

These are not footnotes to the project. They define its responsible use. A rich graph makes relationships more visible, and the project exposes metadata intended to make uncertainty visible too. The reader’s task is to keep those two facts together. When the question is about a known definition, inspect the definition and its fixed graph relationships. When it is about behavior that may pass through reflection or a registry, ask what static analysis can see and whether a runtime trace adds relevant observation. When it is about whether a value reaches an external sink, check whether the data-flow layer was enabled and whether the relevant modules are covered.

The practical judgment this series will develop

Code-Graph-RAG is best understood as a layered code-intelligence system. The structural graph supplies a durable map of the repository. Deterministic tools make precise lookups reproducible. A language model helps interpret open-ended questions and coordinate actions. Semantic embeddings help discover code by what it does rather than what it is called. Runtime traces can add calls that static analysis missed, while data-flow analysis can connect resources through modeled value paths.

Each layer answers a different kind of question, and each has a different failure mode. Semantic similarity is useful for discovery but does not prove a dependency. A runtime edge shows that a traced workload exercised a path but does not show that unobserved paths are impossible. A flow path can show that a modeled source reaches a modeled sink, but an uncovered file changes the meaning of an empty result. An edit can parse successfully while still changing behavior in a way that tests must catch.

That is why the project’s most durable idea is not “ask your code questions in plain English.” It is the combination of a shared representation and explicit evidence boundaries. The model can help a reader navigate a large codebase, but the graph, the recorded relationship, the source location, the coverage state, and the resulting verification step determine how far the conclusion can travel.

The next step is to look closely at how Code-Graph-RAG builds this shared structural model. Once the reader can see what the parser preserves and what the graph means by a relationship, later questions about retrieval, editing, runtime behavior, and data provenance become concrete. The system’s value begins there: not with an answer that merely resembles understanding, but with a model that gives understanding somewhere inspectable to stand.

References