No signup, no config. We pointed one command at a fresh clone of Express.js:
npx -y @kage-core/kage-graph-mcp scan
0.3 seconds of analysis later (142 files, 3,160 symbols, full git history), it printed this:
Kage Truth Report - express
0 duplicate clusters - 0 ghost exports - 0 bus-factor-1 hot files - 7 knowledge voids
KNOWLEDGE VOID - high churn, zero memory
lib/response.js - 390 commits of accumulated decisions,
149 graph edges depending on it - and zero memory packets
or doc mentions. Agents and new hires fly blind here.
lib/application.js - 179 commits, 77 edges. Same story.
lib/request.js - 175 commits, 58 edges. Same story.
What is a knowledge void?
Take every file in a repo and ask two questions: how often has it changed (churn), and how much depends on it (centrality in the import/call graph)? Now ask a third: where is the accumulated knowledge about it written down?
For lib/response.js the answers are: 390 commits of decisions, 149 dependents, and... nowhere. Not the README, not the docs, not any structured memory. Every one of those 390 commits had a reason. The reasons live in the heads of whoever wrote them.
That is fine until an AI agent (or a new teammate) touches that file. They fly blind - and on high-centrality files, blind edits are how regressions are born.
What else the scan checks
- Duplicate implementations - same-name, same-signature functions in different packages, flagged when they appeared recently (the AI-era copy-paste signature)
- Ghost exports - exported symbols with zero call edges, verified against raw source so CommonJS property-requires don't false-positive
- Bus factor 1 - hot files only one person has ever committed to, ranked by graph centrality
- Doc lies - README claims checked against the code graph: paths that don't exist, npm scripts that aren't in package.json, CLI flags that appear nowhere
Every finding comes with file:line evidence. Nothing is a vibe.
How it works under the hood
The scan is built on a local code graph: TypeScript compiler AST for TS/JS, tree-sitter (pure WASM, no native deps) for Python/Go/Rust/Java/Ruby, import-aware call resolution so a call edge means the import actually resolves there - plus one pass over git history for churn and authorship. All local, nothing leaves your machine.
The same engine powers the rest of Kage: verified memory for coding agents. Memory that cites a file that doesn't exist is rejected at write. Memory whose cited code changed is withheld from recall and flagged at the exact moment your diff invalidates it:
Your changes invalidated 1 team memory:
- Payment validation rule - cites src/payments.ts
(content changed since this memory was verified)
And it prints receipts, because tools should show their value instead of asserting it:
This week Kage saved you ~845K tokens (~$12.67),
blocked 0 stale memories, answered 5 recalls.
Try it on your repo
npx -y @kage-core/kage-graph-mcp scan
If it finds nothing interesting, you have an unusually well-documented repo - genuinely, congratulations. If it finds a void on your most-depended-on file, that is the file your next agent session will touch blind.
Open source (GPL-3.0), works with Claude Code, Codex, Cursor, Windsurf, any MCP client: https://github.com/kage-core/Kage
I would love to know what the scan finds on YOUR repo - drop the weirdest finding in the comments.
Top comments (2)
This is exactly right, and it's the design bet behind the capture side: the scan can only diagnose what's already lost. Why-nots are unrecoverable from the tree, so Kage treats rejected approaches as first-class memory — there's a
negative_resultpacket type specifically for "we tried X, it failed because Y" — and capture is wired to where decisions actually happen: the diff. When you change code that existing memory cites,kage pr checkflags it at PR time, which is usually while the why-not is still fresh in someone's head. Capture at decision time or lose it permanently, exactly as you said.Express is a strong test subject because it's the opposite of an under-documented mess: 14 years old, thousands of contributors, every design argument preserved in issues. If knowledge voids survive there, they survive anywhere.
The voids that matter most in old repos are the why-nots: approaches tried and rejected before the current shape settled. No tool recovers those from code alone, because the rejected branches never made it into the tree. Which points at the real fix for newer projects: capture rejection rationale at decision time, or lose it permanently.