Mira delivers parcels in a big city. One evening her dispatcher messages her: "The bridge on Harbour Road closes tomorrow. Which of your deliveries break?"
She could search her address list for "Harbour Road" and find the three shops on that street. But the real damage is the depot across the river, whose only way out is that bridge, and the forty customers who get their parcels from that depot. None of them has "Harbour Road" in their address. To find them she needs a map, not a search box.
I keep running into the same problem every time I change a function in my own code. So I tried a code knowledge graph, and this post is what I learned. I'll explain it with Mira's map, show why it answers "what breaks if I change this?" better than vector search, and run Graphify on one of my own repos so you can see the actual output.
So what is it?
A code knowledge graph is a map of your codebase. Every file, function and class is a node, and every relationship between them (contains, calls, imports, inherits) is an edge you can follow.
Instead of searching for code that looks like your question, you walk the connections. You can ask who calls a function, how two parts connect, or what a change will break.
| City map | Code knowledge graph |
|---|---|
| A neighbourhood | A community: a cluster of tightly connected code, like everything around storage |
| A building | A file (storage.js) |
| A room inside a building | A function or class (parseFrontmatter) |
| A road between buildings | An import (api.js imports storage.js) |
| A corridor between rooms | A function call (fromMarkdown calls parseFrontmatter) |
| A busy junction everyone drives through | A "god node": the most connected function |
| Mira's route planner | A graph query: path, explain, affected |
| Closing a bridge | Changing a function |
The picture breaks in one place. Real roads go both ways, but code edges have a direction. "A calls B" is not "B calls A". When you ask what breaks, you walk the arrows backwards, from the function you changed to everything that depends on it.
The map is just files, functions and the roads between them
Mira's map doesn't care what the buildings look like inside. It only cares which rooms are in which building and which roads connect them. A code graph does the same: it records structure, not style.
Buildings hold rooms (files contain functions), rooms connect by corridors (calls), and buildings connect by roads (imports).
In Graphify's output every node has a type, a source file and a line number. Every edge has a relation like contains, calls, imports, imports_from or inherits. Those were the edge types in the graph.json I got, plus a few extras like re_exports and dynamic_import.
It also groups tightly connected nodes into communities using the Leiden algorithm, which is how you get "neighbourhoods" without naming them yourself. The most connected nodes become god nodes, the junctions everything flows through.
How the map gets drawn
Nobody walks the city with a notebook. A survey plane takes photos and software turns them into roads and buildings. For code, the plane is a parser.
The parser reads the structure of the code, so the map comes from facts in the source, not guesses.
Graphify uses tree-sitter, which turns source code into a syntax tree for about 40 languages. A function_declaration becomes a node. A call_expression inside it becomes a calls edge. An import becomes an imports edge. Here's real code from the repo I mapped:
// src/storage.js (tubelog)
export function parseFrontmatter(text) { // → node: parseFrontmatter() at L59
// ...
data[key] = coerce(line.slice(index + 1).trim()); // → edge: parseFrontmatter calls coerce
// ...
}
export function fromMarkdown(text, filePath = "") { // → node: fromMarkdown() at L130
const { data, body } = parseFrontmatter(text); // → edge: fromMarkdown calls parseFrontmatter
// ...
}A few things I liked:
- It runs locally and it's free for code. The README says code is parsed with tree-sitter, deterministically, with no LLM and nothing leaving your machine. Docs, PDFs and images need a model for a separate pass.
- Every edge says how it was found.
EXTRACTEDmeans it's written in the source.INFERREDmeans Graphify worked it out, for example a call on an object whose type it resolved. On my repo, 100% of edges wereEXTRACTED. - There are no embeddings and no vector store. It's a graph you walk.
Why a map beats a search box
Back to the bridge. Searching addresses for "Harbour Road" finds what mentions the road. A map finds what depends on it. Vector search is the address search: it finds code that looks like your question.
Similarity finds the code next to your question. The graph finds the code connected to it.
Embeddings are great at "find code about uploading files" or "where do we handle dates". I wrote about how that works in vector search using vector embeddings. But "what breaks if I change this?" isn't a similarity question. The function that breaks may share no words with the one you changed. It just calls something that calls something that calls it.
I tried it on the real repo. As a stand-in for embeddings I wrote a quick TF-IDF search that ranks 30-line chunks of every JavaScript file by how close they are to the query:
$ python3 similar_chunks.py repo "parseFrontmatter frontmatter yaml"
0.05 src/storage.js:L31
0.04 src/storage.js:L121
0.00 tubelog.config.js:L91
0.00 tubelog.config.js:L61
0.00 tubelog.config.js:L31It finds the two chunks near the definition and then nothing. Plain grep parseFrontmatter does the same: two lines, the definition and one call. Neither ever reaches sync.js, which is where the real breakage would show up. Real embeddings would rank differently, but they'd still rank by resemblance, not by call path.
Running Graphify on my own repo
I ran it on Tubelog, my open-source YouTube-to-Markdown tool. It's 100 files of JavaScript and React with a Hono API.
Install it and build the map
pip install graphifyy # or: uv tool install graphifyy / pipx install graphifyy
git clone https://github.com/shreyvijayvargiya/tubelog.git && cd tubelog
graphify update . # code-only build: tree-sitter, no LLM, no API keyAST extraction: 100/100 uncached files (100%)
Rebuilt: 749 nodes, 1760 edges, 77 communities
graph.json, graph.html and GRAPH_REPORT.md updated in graphify-outThat took 2.7 seconds and cost zero tokens. Inside your AI assistant the equivalent is graphify install once, then /graphify ., which also runs the semantic pass over docs.
GRAPH_REPORT.md opens with the busiest junctions. For Tubelog the top god nodes were fail() with 43 edges, api() with 38 and VideoTable() with 37. That's a fair summary of the repo: an error helper, the API client and the main table component.
What breaks if I change parseFrontmatter?
parseFrontmatter reads the YAML block at the top of every saved video file. Graphify's affected command walks the calls edges backwards:
graphify affected "parseFrontmatter()" --relation calls --depth 6Affected nodes for parseFrontmatter()
Relations: calls
Depth: 6
- fromMarkdown() [calls] src/storage.js:L131
- readVideoFile() [calls] src/storage.js:L313
- withCounts() [calls] src/storage.js:L520
- deleteVideo() [calls] src/storage.js:L504
- listVideos() [calls] src/storage.js:L558
- readVideo() [calls] src/storage.js:L337
- writeVideo() [calls] src/storage.js:L357
- getChannel() [calls] src/storage.js:L549
- listChannels() [calls] src/storage.js:L540
- searchVideos() [calls] src/storage.js:L574
- fillMissingBlogs() [calls] src/sync.js:L119
- archiveVideo() [calls] src/sync.js:L67
- generateVideoBlog() [calls] src/sync.js:L105
- syncChannelNow() [calls] src/sync.js:L207
- attachBlog() [calls] src/sync.js:L60
- syncChannel() [calls] src/sync.js:L276Sixteen functions across two files. The ones that matter most are the six in sync.js, which the search box never found. Change how frontmatter is parsed and you can break channel syncing, blog generation and archiving.
Close the bridge at parseFrontmatter and the traffic jam reaches syncChannel, five hops away in another file. This is a subset of the 16, drawn from the real edges in graph.json.
Two more queries worth knowing:
$ graphify explain "fromMarkdown()"
Node: fromMarkdown()
Source: src/storage.js L130
Community: storage.js
Degree: 5
Connections (5):
<-- storage.js [contains] [EXTRACTED] src/storage.js:L130
<-- readVideoFile() [calls] [EXTRACTED] src/storage.js:L313
<-- withCounts() [calls] [EXTRACTED] src/storage.js:L520
--> parseFrontmatter() [calls] [EXTRACTED] src/storage.js:L131
--> section() [calls] [EXTRACTED] src/storage.js:L136
$ graphify path "[[...route]].js" "parseFrontmatter()"
Shortest path (3 hops):
[[...route]].js --imports_from [EXTRACTED]--> src/api.js --imports_from [EXTRACTED]--> storage.js --contains [EXTRACTED]--> parseFrontmatter()Everything above was run on 29 Oct 2026 with graphifyy 0.9.97 against Tubelog commit 94393f9.
One thing didn't work as well as I hoped. The plain-language graphify query "what breaks if I change parseFrontmatter?" gave me a fuzzy 67-node neighbourhood that also pulled in unrelated pages like the changelog. For impact questions, affected and path are the precise tools. I'd use query for open questions like "what connects auth to the database?"
How affected works, in about 30 lines
The idea is small enough to read in one go. This script loads graph.json, flips every calls edge, and does a breadth-first walk from your function:
"""blast_radius.py: who calls this function, directly or through other functions?
Usage: python3 blast_radius.py graphify-out/graph.json "parseFrontmatter()"
"""
import json, sys
from collections import deque
graph_path, target = sys.argv[1], sys.argv[2]
g = json.load(open(graph_path))
label = {n["id"]: n["label"] for n in g["nodes"]}
where = {n["id"]: f'{n.get("source_file","")}:{n.get("source_location","")}' for n in g["nodes"]}
callers = {} # callee id -> set of caller ids
for e in g["links"]:
if e["relation"] in ("calls", "indirect_call"):
callers.setdefault(e["target"], set()).add(e["source"])
start = [i for i, l in label.items() if l == target]
if not start:
sys.exit(f"No node called {target!r}")
seen, queue = {start[0]: 0}, deque([start[0]])
while queue: # breadth-first walk up the call graph
node = queue.popleft()
for c in callers.get(node, ()):
if c not in seen:
seen[c] = seen[node] + 1
queue.append(c)
print(f"Changing {target} can affect {len(seen) - 1} functions:")
for nid, hops in sorted(seen.items(), key=lambda x: x[1])[1:]:
print(f" {hops} hop{'s' if hops > 1 else ' '} {label[nid]:<28} {where[nid]}")$ python3 blast_radius.py graphify-out/graph.json "parseFrontmatter()"
Changing parseFrontmatter() can affect 16 functions:
1 hop fromMarkdown() src/storage.js:L130
2 hops readVideoFile() src/storage.js:L311
2 hops withCounts() src/storage.js:L513
3 hops deleteVideo() src/storage.js:L501
...
5 hops syncChannel() src/sync.js:L275It returned the same 16 functions as graphify affected, with the hop count added. The line numbers differ a little because the script prints where each function is defined and affected prints the line of the call.
Handing the map to your coding agent
A map in a drawer doesn't help Mira. It helps when it's on the dashboard. Same for agents: Claude Code, Cursor or Codex should check the graph before they grep their way through files.
There are two ways in:
- The skill.
graphify installregisters a/graphifyskill for your assistant (Claude Code, Cursor, Codex, Gemini CLI, Copilot and more). The assistant then runsgraphify query,pathandexplainagainst the local graph. - The MCP server. Install the extra with
pip install "graphifyy[mcp]", then runpython -m graphify.serve graphify-out/graph.json. It's a stdio MCP server with tools includingquery_graph,get_node,get_neighbors,get_community,god_nodes,graph_statsandshortest_path. For Claude Code:
claude mcp add graphify -- python3 -m graphify.serve "$PWD/graphify-out/graph.json"I haven't connected the MCP server to a live agent yet. The tool names come from Graphify's serve.py and its export docs, so check them against your version.
Keep the map fresh. Run graphify update . after a pull, or install the git hook with graphify hook install so it rebuilds on commit and branch checkout. A stale map is worse than none, because it's confidently wrong.
If you want the bigger picture of how agents use structured context like this, graph engineering for AI agents covers the agent side. The GitHub repository analysis agent we built is the "explain this repo in prose" cousin of this idea.
Watch: a short walkthrough of why agents need a codebase map.

Everything together
Build the map once, keep it fresh, and let both you and your agent read it before anyone closes a bridge.
Other tools like Graphify
Graphify isn't the only way to turn a codebase into a knowledge graph. I checked each repo's licence and last commit on 29 Oct 2026.
| Tool | What it is | Licence | Last commit |
|---|---|---|---|
| Graphify | tree-sitter graph of code plus docs, PDFs and media; skill and MCP; graph.json you can query | Apache-2.0 (earlier contributions MIT) | 2026-10-27 |
| CodeGraph | Local code graph built for coding agents, used through MCP | MIT | 2026-10-28 |
| Code-Graph-RAG | tree-sitter graph in an embedded graph database, with CLI and MCP | MIT | 2026-10-29 |
| Serena | Symbol-level code tools over MCP, IDE-style, rather than a graph you export | MIT | 2026-10-28 |
| GitNexus | Graph of symbols, calls and execution flows, with MCP, CLI and web explorer | PolyForm Noncommercial (not open source for commercial use) | 2026-10-29 |
| SCIP | Sourcegraph's code-indexing protocol for go-to-definition and find-references; Graphify can ingest SCIP indexes | Apache-2.0 | 2026-10-28 |
How I'd pick: for one repo and an agent, Graphify or CodeGraph is the quickest start. If you want the agent to edit by symbol instead of reading a graph, try Serena. If you want the graph in a database you query yourself, try Code-Graph-RAG. Check the licence before you build a product on GitNexus.
Try it in five minutes
pip install graphifyycdinto a repo you know well and rungraphify update .- Open
graphify-out/GRAPH_REPORT.mdand see if the god nodes match your gut feeling about the busiest code. - Pick a function you've been meaning to refactor and run
graphify affected "yourFunction()" --relation calls --depth 5. - Count how many of those callers you'd have remembered without the map.
Things people get wrong
- "A knowledge graph needs an LLM." Not for code. Graphify's code graph comes straight from the parser, so it's fast, deterministic and free. The LLM only helps with docs, images and naming communities.
- "It replaces vector search." It doesn't. Use embeddings for "find code about X" and the graph for "what connects to X". Good agent setups use both.
- "The graph knows what happens at runtime." It knows what the code says. Dynamic calls, reflection, string-built imports and calls across services can be missing or end up as
INFERREDguesses. Treat the graph as a strong first answer, then confirm with tests. - "Build it once and you're done." Code changes daily. Rebuild on pull or with the git hook, and check that
built_at_commitingraph.jsonmatches yourHEAD. - "Names are unique." Two
parse()functions in different files are different nodes. When a name is ambiguous, Graphify refuses and lists the candidates instead of guessing, so pass the exact label.
Quick quiz
- In the city map, what is a function call?
- Why can't vector search reliably answer "what breaks if I change this?"
- Which Graphify command walks edges backwards to find everything a change could affect?
- What does an
EXTRACTEDedge mean? - What file should you check to know whether your graph is stale?
Answers:
- A corridor between two rooms. A road between buildings is an import.
- It ranks code by how similar it looks, and the code that breaks often shares no words with the code you changed.
graphify affected "X()" --relation calls.- The relationship is written directly in the source, not inferred.
graph.json(itsbuilt_at_commit) or the "Graph Freshness" section ofGRAPH_REPORT.md. Compare it withgit rev-parse HEAD.
FAQ
What is a code knowledge graph? A map of a codebase where files, functions and classes are nodes and relationships like calls, imports and inheritance are edges. You query it by following connections instead of searching text.
How do I convert a codebase to a knowledge graph?
Install a tool like Graphify (pip install graphifyy) and run graphify update . in the repo. It parses the code with tree-sitter and writes graph.json, an interactive graph.html and a GRAPH_REPORT.md.
How do I use a code knowledge graph with Claude Code?
Run graphify install to add the /graphify skill, or run Graphify's MCP server and add it with claude mcp add. Claude can then query the graph before reading files.
Is there a code knowledge graph MCP server?
Yes. Graphify (python -m graphify.serve), CodeGraph, Code-Graph-RAG and GitNexus all offer MCP access, and Serena offers symbol-level MCP tools.
Is Graphify free and open source? The Graphify CLI is Apache-2.0 and the code graph runs locally at no cost. Graphify Labs also sells a hosted Graphify Cloud for always-on, cross-repo graphs.
What are the alternatives to Graphify? CodeGraph, Code-Graph-RAG and Serena (all MIT), GitNexus (noncommercial licence) and Sourcegraph's SCIP for code-navigation indexes. See the table above.
Read next
- Vector search using vector embeddings: the "address search" half of this story
- GitHub repository analysis agent with DeepWiki, Hono and OpenRouter: explaining a repo in prose
- Graph engineering for AI agents and multi-agent systems: graphs on the agent side
Mira and her city are made up. The repo, the commands and the output are real.