I spent a day reading six repositories instead of six READMEs. The gap between those two things turned out to be the entire point of the exercise, and three days later a seventh tool showed up and tested every conclusion I’d drawn from the first six.

The question I was trying to answer was ordinary enough. I want two capabilities working together on a developer workstation: a code graph that knows the structure of the repository, and a memory of what agents have done and decided in it, in an environment where a security reviewer signs off before anything gets installed. They’re separate jobs with separate failure modes; the outcome I’m after comes from running them side by side, not from either one alone. Several tools in that space already exist and most of them are good. So the honest first move is to work out whether one of them can just be adopted, and if not, exactly which parts are worth copying and which problems nobody has solved yet.

Six candidates: openwiki, claude-mem, deja-vu, code-review-graph, codegraph and graphify. I cloned all of them on the same day and read them at source level. For each one I traced the entry points, the storage layer, the ingestion pipeline, the retrieval path, the MCP surface and wherever an LLM got called; then I grepped for network endpoints, environment variables, on-disk writes and secrets handling, and read the CI and release workflows. Where a README made a number-shaped claim, I went and checked it against the code.

Several of those claims did not survive.

Two families, one outcome

The tools don’t draw that line as cleanly as their READMEs suggest. Most of them market themselves as covering the whole problem, and reading the code shows each one committing to one half and treating the other as an afterthought, if it touches it at all. Lumping them into a single category is what makes people pick the wrong one.

openwiki, claude-mem and deja-vu are memory tools. They answer: what did the agent learn, decide or do, and how does that get back in front of it next time? code-review-graph, codegraph and graphify are structure tools. They answer: what calls what, what breaks if I change this, and can you tell me in four thousand tokens rather than making me read the corpus? Only graphify straddles both, and it does so by running two entirely separate pipelines that happen to meet in one graph, which is the right instinct built the hard way. The two capabilities are complementary rather than interchangeable: structure tells an agent what a change will touch, and memory tells it why the code is the way it is and what went wrong the last time somebody tried. Neither can answer the other’s question.

  openwiki claude-mem deja-vu code-review-graph codegraph graphify
Family Wiki Memory Memory Code graph Code graph Both
Language TypeScript TypeScript Go, stdlib only Python TypeScript + Rust Python
Parsing None Minimal None tree-sitter, 39 langs tree-sitter, 49 grammars tree-sitter, 31 langs
Storage Markdown + JSON SQLite + Chroma Custom binary SQLite + BLOB vectors SQLite FTS5 NetworkX JSON
LLM required Yes Yes No No No Docs only
Telemetry default On On None None On None
MCP tools 6 14 1 30 8 10

The split matters more than it looks, because the two families have opposite security profiles. Memory tools ingest transcripts and tool output, which is precisely where credentials and customer data end up during an agent session. Structure tools ingest source code, which is proprietary but rarely contains live secrets. Running both, which is the whole point, needs deja-vu’s redaction discipline on one side and graphify’s provenance discipline on the other, and right now no single project has both halves.

The claims that didn’t survive the read

codegraph’s README says “100% Local. No data leaves your machine.” I use codegraph every day; it’s indexed across my whole workspace and I wrote about teaching it to read my infrastructure a few weeks ago. It ships with telemetry on by default, a daily GitHub update check inside the MCP server, and an installer that prompts you for a waitlist signup. Three outbound connections in a tool whose front page promises none.

None of that is malicious and all of it is trivially disabled. But “no data leaves your machine” is a claim a security reviewer will test, and when it fails on the first grep for fetch(, the conversation about the rest of the tool becomes much harder. Absent by construction and off by default are not the same property, and only one of them survives contact with someone whose job is to assume the worst.

code-review-graph claims “~65x token reduction”. The figure is real arithmetic: it’s the size of five search responses measured against the whole corpus at four characters per token. The trouble is the denominator, because no agent has ever read an entire corpus to answer one question; the alternative to a graph query is a handful of greps, not a full-repository read. Its own README half-concedes this. Measuring against a baseline nobody pays produces a number that is technically true and practically meaningless.

deja-vu is the interesting counter-example. Its benchmark claims 85.3%, which is genuinely accurate, and it’s session-level hit@1. The per-evidence figure is 55%, and that appears only on a docs page rather than in the README. The tool also states plainly that its lexical retrieval tops out around 33.3% hit@1 on paraphrase-style questions. Writing down your own ceiling is rare enough that it changed how I read everything else in that repository.

Five things worth stealing

There were about fifteen ideas I wanted to keep. These five are the ones I’d argue for hardest.

Relocatable evidence hashing, from openwiki. Every generated statement is pinned to a SHA-256 of the exact source line range it came from. Move the code without changing it and the claim relocates and keeps its version; change the code and the claim gets flagged for re-verification. This is the single most original idea in the six repositories, and it’s the mechanism that separates documentation which is auditable from documentation which is merely plausible. Anyone who has watched a wiki rot for eighteen months while confidently asserting things that stopped being true in the second month will recognise the problem it solves.

Edge-level provenance and confidence, from graphify and codegraph independently. Every relationship in the graph records whether it was extracted, inferred or guessed, with a confidence score and a source location. None of these tools uses LSP or a compiler front end, so type resolution is heuristic everywhere; the difference is that two of them say so per edge rather than in a caveat paragraph. An agent can then discount a guessed dynamic-dispatch edge instead of treating it as fact.

Recall at the point of action, from deja-vu. Most memory tools inject context at session start, which is when the agent knows least about what it’s going to need. deja-vu hooks PreToolUse on edits to surface that file’s prior decisions, PreToolUse on shell commands to surface the last invocation that worked, and PostToolUse on a failed command to surface what fixed it last time. Same data, radically better timing.

Redaction at ingest, with counters and a written limit statement. deja-vu again: regex for known token shapes, JWTs, PEM blocks and user:pass@host URLs, plus a gated entropy pass that excludes hex, UUIDs and paths, with the count of redactions surfaced per source. What makes it credible is the accompanying SECURITY-MODEL.md, which states outright that pattern matching is not secret detection. Every other tool either does nothing here or relies on a prompt instruction, which is not a control.

Honest staleness signalling. codegraph prepends a banner to any response that references files edited since the last index sync, telling the agent to read those files directly. code-review-graph attaches a short sentence to every empty result explaining whether the symbol was never indexed, the graph is behind HEAD, or a known analysis gap applies. Both are small and both prevent the failure where an agent confidently reports nothing found and the human believes it.

What none of them do

Here is where the exercise stopped being a shopping trip.

No tool has authentication beyond a loopback bind or a single shared static key, and none has a per-user identity, which means none of them can produce an audit trail naming who asked what. Nothing encrypts at rest. They lean on full-disk encryption instead and most of them say so plainly, which is a defensible position on a laptop and an untenable one anywhere a second person has access. Not one of the six keeps a tamper-evident log of queries and responses. Only deja-vu has a data-retention concept at all, and even that quietly extends the life of transcripts past the harness’s own cleanup window; only graphify has project isolation on a shared server, and its version is broken, because any authenticated client can pass an arbitrary project_path and read someone else’s graph. Classification labels are absent everywhere, so there’s no way to keep sensitive material out of an index that might later feed a model. And nothing signs or attests its own index, so a consumer cannot prove which source revision it was built from.

Read that list as a feature wishlist and it’s unremarkable; every young project has gaps. Read it as a requirements list for somewhere a reviewer signs off before installation and it’s disqualifying, all of it, for all six. That was the finding that turned an adoption question into a build question.

To be fair to the six: three of them are effectively single-author projects moving at twenty to a hundred commits a day, and two are open-core funnels for a hosted service. None of them is trying to be the thing I need. All six are worth reading, which is not the same as being worth adopting, and the distinction is one I’d like to see made more often.

The language answer I got wrong

When the scoping questions came round, I answered TypeScript without much hesitation. The MCP reference SDK is TypeScript. The hook contracts are TypeScript. All six tools ship TypeScript installers regardless of what they’re written in. It’s the obvious answer and I gave it in about four seconds.

Then I scored four candidates properly against what the work actually needs, and reversed myself the same afternoon.

TypeScript’s problem is distribution and supply chain, and both are worse than they feel from inside the ecosystem. Getting a Node application onto a managed fleet means shipping a runtime or building a Single Executable Application; neither is terrible, both are more moving parts than a static binary. The dependency posture is the bigger issue. codegraph is a disciplined project by TypeScript standards and still resolves twelve runtime packages; the ecosystem norm is hundreds. Python is worse on both counts, with graphify carrying 29 runtime dependencies and code-review-graph 45 transitive.

Rust wins the parsing criterion outright, which is exactly why codegraph put its kernel there. It also has the steepest learning curve and the smallest local hiring pool, and a two-language codebase is a maintenance cost a small team shouldn’t take on for a fast path it can get another way.

Go wins on the criteria that actually bind: a static binary with no runtime, and a dependency surface a security reviewer can read in an afternoon. deja-vu is the existence proof, having built an entire memory tool, transcript parsers for 22 agent harnesses, a hand-rolled BM25 implementation and a custom binary index format, on the standard library and nothing else. Zero dependencies. 1,586 test files.

The one place Go can’t stay pure is parsing, and that’s a real decision rather than a detail. The official tree-sitter bindings are CGO, with each grammar published as its own module carrying generated C. There’s a purego path that loads grammars from shared objects at runtime, but shipping loose .so files to a managed fleet puts back exactly the loading and integrity problem the static binary was meant to remove. So: accept CGO for the parser only, link every grammar statically, pin the grammar modules by checksum, and cross-compile with zig cc for Linux while building macOS natively. A spike confirmed darwin can’t be cross-linked without Apple’s SDK, which is the sort of thing you find out by trying rather than by planning.

That arrangement is structurally the same as codegraph’s Rust kernel: one process, no marshalling across a language boundary, no throughput cliff. It gets there without a second language.

The other consequence of choosing Go is a constraint I’ve come to like. Direct dependencies are capped at six, enforced in CI by a check over go.mod: the MCP SDK, the SQLite driver, tree-sitter and its grammars counted as one, and no more than three others. Command-line parsing uses flag and a dispatch table rather than a framework. That cap forces net/http, encoding/json and crypto to do work I’d reflexively install a package for in any other ecosystem, and so far the code has been better for it.

Three days later, a seventh tool

I had the design settled and had started building. Then redhat-et/ripwire turned up at v0.4.0 with 546 stars, positioning itself as “the ripgrep of AI context”. Ignoring it would have been the comfortable option, so I read it the same way I’d read the other six.

It turned out to be the strongest test those conclusions could have had, because its author independently reached almost all of them. Tree-sitter driven by a per-language table rather than hand-written walkers. No model, no embeddings, no daemon, and no database at all. A name-based resolver that reports how sure it is on every edge; where several candidates survive, it emits an edge to each at 1/k confidence tagged split instead of quietly picking one. An output budget with truncation disclosed in the answer. Determinism as a stated contract, gated by running the binary twice and diffing the bytes.

Every one of those was a bet I’d made three days earlier on the evidence of the six. Someone reaching the same conclusions independently, in a different language, and shipping them, is worth considerably more than agreement from the tools I’d been reading.

Two of its ideas I simply hadn’t had.

The first is task-seeded ranking. Every graph tool in the original six makes you name a symbol before it can tell you anything, which is awkward, because an agent arrives holding a task rather than an identifier. ripwire’s --for takes the top twenty lexical hits for a natural-language query, uses them as the teleport vector of a personalised PageRank over the call graph, and blends the graph score with the lexical one. It’s also the mechanism behind the one retrieval result of theirs that’s genuinely held out.

The second is better, and it’s a vocabulary rather than an algorithm. Its answers carry total, shown, truncated, an estimated token count, a list of file extensions in the tree that no grammar covers, and, best of all, counts_floor="1" on caller, callee and impact counts. A name-based resolver can only ever report a floor. Saying so in the response is what lets an agent distinguish “none found” from “none exists”, and I had been planning to explain empty results in a sentence, which is strictly worse than labelling every count.

A third thing is worth taking and reads as unglamorous until you notice what it implies. ripwire puts ceilings on pathological input: files over 4 MB skipped, a NUL sniff over the first 4 KB, JSON rejected past 256 KB or 512 levels of nesting, and a YAML indent prescan that exists specifically because a vendored scanner writes past a 1,024-byte buffer at around 254 levels. The author also carries four one-line patches to vendored tree-sitter scanners, fixing out-of-bounds writes he found himself. Those are guards you write after something has already crashed, and anyone linking the same C scanners over untrusted repositories has inherited the same exposure whether they’ve noticed or not.

Then the same gaps as the other six, without a single exception. No memory of what an agent did. No principal attached to a query, so no audit trail can name who asked for what. No classification, no deny list, no redaction at ingest, and no untrusted-content framing beyond XML escaping. Releases ship a .sha256 sidecar from the same origin as the tarball and nothing else: no signature, no SBOM, no provenance, and the Linux build uses profile-guided optimisation so it isn’t reproducible in any case. The installer pipes curl into bash. Three of its MCP tools write files, enabled by default over stdio, and a request may name any path on the machine; --run-trace executes an arbitrary shell command; a positional argument that looks like a URL gets cloned with git. Its own security policy tells you to run it only on code you trust, which is the precise inverse of the trust boundary the work needs.

The numbers follow the pattern as well, and by that point I was expecting them to. The headline retrieval figure is 58.3% strict file@10 on an all-Python benchmark with n=60, which says nothing at all about Go, C# or Ruby. The head-to-head win against a competing tool is self-judged over 48 hand-written questions, a third of them about ripwire’s own repository, with no harness committed anywhere. The token-saving claim is measured against an idealised worst-case reader across twelve questions, on which ripwire strictly answered five and the reader answered eleven. What makes this one interesting rather than annoying is that the methodology documents are more rigorous in form than anything else in the set, with pre-registered bands and placebo arms; the execution is single-party throughout, the same operator running and judging every round.

Seven projects in six weeks, then, and not one of them closes the control gaps. That’s no longer a coincidence I can explain by any of them being young.

The part I have to sit with

One thing about ripwire stayed with me after I’d closed the tab, and it isn’t a criticism of the tool.

153,000 lines of C++23, 96.7% of it in headers, tested by 556 shell scripts and roughly nine actual unit tests, with no coverage measurement anywhere. 1,817 commits in 40 days from a single pseudonymous account, 1,336 of them carrying a Claude co-author trailer, peaking at 133 commits in one day. The result is consumable as a binary and not forkable by a small team, and its own architecture document had already drifted behind the code in three places I found in a single reading. The redhat-et organisation name looks like a governance signal and isn’t one; the licence names an individual, there’s no CLA, and Red Hat appears nowhere in the code.

I wrote that up as a risk. It is also a fair description of how I am building, and pretending otherwise would be dishonest. One person and an agent produce a great deal of working code and a maintenance liability at the same time, and only the first of those announces itself. The only answer I’ve got is constraints that actually bite: the six-dependency cap, a file-size cap, a coverage floor, a decision record for every choice worth arguing about, and a language another team can pick up on a Monday morning. Hold those and the difference is real. Relax them the first time they’re inconvenient and I’ve written the same tool in a friendlier language.

What came out of it

The evaluation ran for a day and produced six source-level reviews, a comparative document, and a decision to build rather than adopt, which is not the decision I expected to reach when I started. The seventh review, three days later, was the one that could have overturned it and instead sharpened it: ripwire demonstrates that a deterministic, model-free, tree-sitter graph is enough to be genuinely useful, which was the central bet, while demonstrating nothing whatsoever about the controls.

I’ve been calling it the shadow, which on a site named after Jekyll seemed only fair. Hyde was Jekyll’s shadow self, the half that went out at night and did what the respectable one wouldn’t be seen doing; this one is better behaved, though it does follow the developer and the agent everywhere on the machine and never once leaves it. It keeps a shadow copy of the code’s structure without keeping the code itself, and a shadow brain of what earlier sessions decided, with anything that looks like a credential taken out before it’s written down. It’s Go, and it’s at the stage where the interesting problems are still ahead of it rather than behind. How it’s put together, what the first weeks turned up, and which of ripwire’s ideas made it in, is the next post.

If you take one thing from this: read the source before you trust the README, particularly when the README makes a claim with a number in it. Three of the seven had headline claims that didn’t hold up, and I found each one within an hour of grepping. That ratio has changed how I evaluate tools generally, and it has cost me a great deal less than adopting the wrong one would have.