Code is naturally a graph — imports, calls, doc references. The obvious tool is Neo4j. Here's why I built a Cypher subset on SQLite instead, and the prior art that told me I'm not wrong to.
Code is naturally a graph. Files import files. Functions call functions. Docs reference symbols. The moment you want to ask a question like "what would break if I removed this export" or "which functions reach this one without going through the auth layer," you're asking a graph question, and a relational WHERE clause starts feeling like the wrong tool almost immediately.
So when I started building IntentWeave's evidence index — a local representation of a repo's AST, docs, and git history — the obvious instinct was: use a real graph database. Neo4j, Cypher, the whole stack. It's mature, the query language is genuinely pleasant to write, and there's a decade of tooling built around it.
I didn't do that for the core engine. Here's why, and why I don't think it was a compromise.
The problem with "just run Neo4j"
IntentWeave's core promise is that the index is local, free, and requires nothing beyond npm install. iw init && iw index build gets you a queryable representation of your repo in under three seconds, with zero servers and zero accounts. That constraint rules out a lot of otherwise reasonable architecture decisions, and a graph database with its own server process is one of them — the moment indexing your codebase means also running (and keeping alive, and upgrading, and securing) a database server, you've changed what the tool is. It stops being something you run in CI as a throwaway step and becomes infrastructure someone has to own.
That's not a knock on Neo4j — it's an excellent database for what it's for. It's a mismatch with "single SQLite file, works in a stateless CI runner, gone when the job ends."
I'm not the first to hit this wall
The most convincing evidence that this wasn't just me being stubborn came from a tool I didn't build: Joern, the code analysis platform used heavily in security research. Joern represents code as a Code Property Graph and, in earlier versions, actually ran on Neo4j with Gremlin-style querying. They moved away from it — replacing both the storage backend and the query language with their own embedded graph database, because the generic graph-database
approach hit real limits as the project matured. If a tool built specifically for deep, security-grade code graph analysis concluded that a general-purpose graph database wasn't the right long-term foundation, that's a strong signal for anyone building something adjacent.
What we built instead: CypherLite
IntentWeave's index is a single SQLite file. Nodes and edges are just tables:
-
Node labels:
FILE,SYMBOL,DOCSPAN,TODO,RATIONALE,SEMANTIC -
Relationship types:
IMPORTS,DEFINES,CALLS,ANNOTATED_BY,HAS_TODO,HAS_RATIONALE,SUMMARIZED_BY,CO_OCCURS,CO_CHANGES
On top of that, we wrote CypherLite — a hand-rolled subset of Cypher that parses a query like:
MATCH (a:SYMBOL)-[:CALLS]->(b:SYMBOL)
RETURN a.name, b.name
LIMIT 10
and compiles it down to SQL against those tables. No server, no driver, no separate process — it runs directly against .iw/index.db in the same process as everything else iw does. iw index schema prints the exact node labels, relationship types, and a set of named templates (@:callers-of, and others) so you're never guessing at property names before you write a query.
This is deliberately a subset. I didn't try to reimplement the full Cypher specification — things like arbitrary variable-length path patterns or the full aggregation surface aren't there.
What I optimized for is the 90% of ad hoc questions that don't fit any of our ~30 built-in query modes (retrieve, connections, context-pack, and so on) but also don't need the full power of
a general-purpose graph query language. "Find every function that calls validateToken without going through rateLimiter" is a completely reasonable one-off question, and it shouldn't require
standing up a database to ask it.
Where this ends up mattering
The unexpected payoff showed up somewhere I didn't originally design for: AI coding agents. I recently shipped an Agent Skill for
Claude Code, Copilot, and Cursor that teaches the agent to ground itself in a repo's real architecture — running iw index context-pack before touching unfamiliar code, checking iw intent check before finishing a change, and, when neither covers the question, falling back to a raw CypherLite query. Because there's no server to configure and no credentials to manage, an agent with ordinary shell access can just... run it. That's a much lower bar than "first, set up an MCP connector to a graph database," and it only works because the whole thing lives in one file.
The trade-off, honestly
You give something up here. If you need arbitrary, unbounded graph traversal at scale — the kind of workload Neo4j is actually built for — SQLite-backed CypherLite is not that tool, and I'm not claiming it is. What you get in exchange is a graph query layer that installs in three seconds, runs in a GitHub Actions job with no extra service, and an AI agent can invoke directly without any infrastructure conversation happening first. For a local-first dev tool, that trade
was an easy one to make once we actually weighed it — and Joern's own history made me a lot more confident we weren't missing something obvious by making it.
IntentWeave is open source. The CLI, the schema, and CypherLite itself are all in the [GitHub repo (https://github.com/intentweave/intentweave) if you want to see how the query compiler works.
Top comments (0)