About CodeGraph
What inspired me
I'm 15, and I keep running into the same problem when I read code.
I'll open a file, see a weird line like const retryLimit = 3, and ask: why 3? why not 5? why retry at all?
git blame tells me who changed it. GitHub search tells me where it appears. But nobody tells me why it was written that way.
The answer usually exists somewhere — buried in a PR comment from two years ago, an issue nobody reads anymore, or an ADR file that was never written. It's just not connected to the line of code it explains.
So I built CodeGraph: a tool that treats decisions as first-class citizens, not as lost chat history.
What I learned
This project taught me more than any tutorial.
Code is a graph, not text. Once I parsed a file with tree-sitter, I realized every uses, defined_by, and contains relationship is an edge. A codebase is a graph that happens to be written as text.
Time is a dimension. A code graph without history is only half the picture. Every node carries valid_from / valid_to derived from real commits, and codegraph graph --at returns the graph as it existed then.
Precision beats recall. My first extractor turned every commit subject into a "decision." That produced 174 decisions on Express — and 174 empty reasons. I rebuilt the pipeline into three tiers: decision (must have a real reason), change_event (raw commit history), and dependency_event (version bumps). The default view now shows only the ~20 decisions that actually explain why. A small number of trustworthy answers beats a large number of noise.
Not everything survives review. I originally built a Dgraph backend because "graph database" sounded like the right answer. A third-party review showed it added 500 lines with zero multi-hop queries using it. I deleted it and wrote ADR-006: drop Dgraph, keep SQLite. Learning to cut a feature I was proud of was harder than adding one.
How I built it
Stage 1 — CLI + real parsing. Typer CLI, pydantic models, tree-sitter for 7 languages (TypeScript, Python, Java, Go, Rust, C++, JavaScript), SQLite storage.
Stage 2 — Real Git history. Each CodeNode version gets valid_from / valid_to from actual commits. graph --at filters nodes and edges to a point in time. codegraph diff shows how decisions changed between two versions.
Stage 3 — Decision extraction. Rules first: rationale keywords in commit bodies, ADRs, CHANGELOG sections. Every extracted decision carries a traceable commit SHA. No LLM on the default path.
Stage 4 — Line-level binding. git log -p gives the changed line numbers of each commit. Those lines are linked to the CodeNode they fall inside, so codegraph why src/file.py:42 can answer at the exact line — not just the file.
Stage 5 — Interfaces. codegraph serve opens a Cytoscape.js graph with a timeline slider. A VSCode extension adds a right-click "CodeGraph: Show decision" command.
Challenges I faced
"Why" is not in the code. My first assumption was wrong: I thought I could extract decisions from code comments. I couldn't. The real "why" lives in commit bodies, ADRs, and sometimes nowhere at all. The tool must say "no record found" instead of guessing — roughly 40% of line queries return nothing, and that's the honest answer.
Rebuilding the graph destroyed it. My incremental indexer skipped already-indexed commits but still called clear_graph(). Running index twice shrank an Express graph from 7027 nodes to 1871. Every node became a WORKTREE snapshot. I fixed it with a merged-write path and a guard that refuses to write if the node count drops by half.
Review found what tests didn't. I had 136 passing tests, but a reviewer found 22 real problems in an hour: unreproducible uids, dead nodes answering why, category lost in the read path, a pip install command pointing at a package that didn't exist. Tests verify what you thought to test. Review finds what you didn't.
Cutting Dgraph. I wrote 500 lines of Dgraph client code. No command ever ran a multi-hop query. It existed because the original pitch said "graph database" and Dgraph was the most impressive-sounding one. Deleting it and writing ADR-006 was the right call, and it made the project smaller and more honest.
What's next
Publish to PyPI so pip install works without cloning Publish the VSCode extension to the Marketplace More precision work on decision extraction for projects without ADRs
CodeGraph is my attempt to give every line of code a memory. Not just what changed, but why it was allowed to change.
Built With
- cli
- code-analysis
- developer-tools
- dgraph
- graph-database
- python
- tree-sitter
- typer
- typescript
Log in or sign up for Devpost to join the conversation.