skip to content
coograph 17

Code graph

The code graph is a SQLite database — .code-graph/graph.db — that holds your repository’s structural relationships. Definitions, calls, imports, inheritance, file → file edges. Your AI agent queries it through MCP tools instead of re-grepping for every question.

What’s in the graph

Two tables, as sqlite3 .code-graph/graph.db ".schema" prints them:

CREATE TABLE nodes (
    id         TEXT PRIMARY KEY,   -- content hash, not an autoincrement integer
    kind       TEXT NOT NULL,      -- file | function | method | class | interface | enum | table
    name       TEXT NOT NULL,
    file       TEXT NOT NULL,      -- repo-relative path
    start_line INTEGER,
    end_line   INTEGER
);
CREATE INDEX idx_node_file ON nodes(file);

CREATE TABLE edges (
    src  TEXT NOT NULL,            -- always a nodes.id
    dst  TEXT NOT NULL,            -- a nodes.id, or a raw name (see below)
    kind TEXT NOT NULL             -- imports | contains | calls | inherits
                                   -- | implements | depends_on | tests_for
);
CREATE UNIQUE INDEX uq_edges  ON edges(src, dst, kind);
CREATE INDEX idx_edge_dst ON edges(dst);

There is no language column — language is inferred from the file extension at parse time and not stored.

dst is not always a node id. contains, tests_for and depends_on resolve to a row in nodes. imports and inherits store the raw text the parser saw — '../api/metricsClient', 'argparse', a base-class name — because resolving a module specifier to a file needs resolution rules the parser does not run. Join on dst for the first group; match on it for the second. The examples below do one of each.

You can query all of this directly with sqlite3. The MCP server is a typed convenience layer over the same file.

Languages

Tree-sitter parsers for: Python, TypeScript, JavaScript, Go, Rust, Java, C#, Ruby, and more. Languages without a tree-sitter package fall back to regex parsers automatically — coverage is degraded but the build doesn’t fail.

Full list: see .github/code-graph/requirements.txt.

MCP tools

The MCP server exposes the following queries to your agent:

ToolPurpose
get_minimal_context(task)Default first call. Resolves the symbols named in the task and returns files_to_read: the defining files, what they depend on, then one tier of what depends on them, ranked and capped at 6. Also graph size, risk of the uncommitted changes, and the tool to use next. Under ~150 tokens, so there is no per-file token map — pass files_to_read to get_review_context for that. An empty list always carries a files_reason.
query_graph(pattern, node_name)Named patterns: callers_of, callees_of, imports_of, importers_of, tests_for, file_summary.
get_impact_radius(files)Takes a list of repo-relative paths, not a symbol. Returns the files reachable from them.
get_review_context(files, budget_tokens?)For review tasks: the files plus their immediate neighbours, ranked and trimmed to a token budget.
find_large_functions(min_lines=50)Functions over N lines — debt-finder.
detect_changes(base="HEAD")Files whose SHA-1 changed since the given ref.
update_graph()Incremental re-parse of changed files.
build_graph()Full rebuild from scratch.
graph_stats()Node + edge counts, last-update time, file count by language.
visualize_graph()Renders an interactive HTML graph at .code-graph/graph.html.

Visualize

uv run -p 3.12 --with-requirements .github/code-graph/requirements.txt \
  .github/code-graph/server.py --visualize

Outputs .code-graph/graph.html — a standalone D3 force-directed view (no server needed). Open in any browser. Drag nodes, hover for symbol details, filter by language or kind. Useful for sanity-checking a fresh build, spotting orphan modules, and showing teammates what the agent actually sees.

Coograph code-graph visualization — interactive D3 force-directed view of nodes and edges

Why SQLite

Direct SQL

If MCP isn’t available, the database is just a file:

# every definition inside one file, in source order
sqlite3 -header -column .code-graph/graph.db "
  SELECT child.kind, child.name, child.start_line
  FROM edges e
  JOIN nodes parent ON parent.id = e.src
  JOIN nodes child  ON child.id  = e.dst
  WHERE parent.file = 'src/i18n/t.ts' AND e.kind = 'contains'
  ORDER BY child.start_line;
"
kind      name                    start_line
--------  ----------------------  ----------
function  localizedPath           43
function  detectLocaleFromHeader  49

Because an imports target is a raw string, the importers of a module are matched rather than joined:

sqlite3 -header -column .code-graph/graph.db "
  SELECT DISTINCT n.file
  FROM edges e
  JOIN nodes n ON n.id = e.src
  WHERE e.kind = 'imports' AND e.dst LIKE '%metricsClient'
  ORDER BY n.file;
"

This is the documented fallback when the MCP server isn’t registered. Agents in Coograph projects know to try MCP first, then fall back to sqlite3, then — only if both fail — to grep.

Auto-update

The git hooks in .github/code-graph/ (post-commit, post-merge, post-rewrite) call --update after every change. Only files whose content SHA-1 changed are re-parsed. Files that import from changed files are also re-parsed so cross-file edges stay accurate.

An update keeps the call edges earlier runs resolved. Before 1.6.0 it re-resolved them by name and dropped every one of them, so a graph that had lost its calls edges after a commit was that bug, not your code: pull the fix and rebuild once with --build.

Limits