Originally published on Zenn (Japanese). Cross-posted here.
The problem where Claude Code re-reads files every time
When you work on a largish project with Claude Code, the same thing happens every session. "Where was that file again" → grep, Read the whole thing, Read another file… The codebase is too big for grep and too expensive to load whole into context.
What solves this is graphify (the PyPI package name is graphifyy with a doubled y; the CLI command is graphify). It turns an entire folder into a knowledge graph, and instead of Claude re-reading files, it queries the graph. It claims "up to 71.5× token reduction per query."
What a knowledge graph is, in one line. It parses code and docs and extracts them as a directed graph of "functions / classes / concepts = nodes" and "calls / depends on / references = directed edges." When you ask a question, instead of re-reading everything it returns only the relevant nodes as a subgraph. That's why it's light. graphify parses code locally with tree-sitter (AST, no LLM), and uses a model for semantic extraction only on the docs.
On an M1 Max (Apple Silicon), I tried to graph my own project — a set of shell orchestrators, a Swift menu-bar app, and design docs — all at once. And it broke completely, right away. This article is that record.
Troubleshooting
Total failure: incompatible architecture
I installed it with uv tool install graphifyy, ran AST extraction, and the moment I did, this appeared.
ImportError: dlopen(.../tree_sitter/_binding.cpython-313-darwin.so, 0x0002):
tried: '.../tree_sitter/_binding.cpython-313-darwin.so'
(mach-o file, but is an incompatible architecture
(have 'x86_64', need 'arm64e' or 'arm64'))
The native binding (.so) for tree_sitter is x86_64, and arm64 Python can't load it. graphify parses code with tree-sitter's AST, so if this dies, code extraction dies entirely.
A classic Apple Silicon issue, but the tricky part was that it was "an x86_64 dependency graphify itself carried." Not my code, not a model — the tool's native dependency was installed assuming x86_64. The cause is simple: arm64 Python can't dlopen an x86_64 .so and crashes. uv tool's resolution had grabbed an x86_64 wheel.
Fix ①: reinstall tree-sitter for arm64
The uv tool environment has no pip, so you force a reinstall by targeting that environment with uv pip.
PY=/Users/you/.local/share/uv/tools/graphifyy/bin/python
uv pip install --python "$PY" --reinstall-package tree-sitter --no-cache 'tree-sitter>=0.23.0'
tree-sitter 0.26.0 (arm64) went in, and the dlopen screaming stopped. …but the AST result was still 0 nodes.
Fix ②: the language grammars simply weren't installed
On retry, this came up next.
warning: 32 .swift file(s) contributed nothing to the graph
because a dependency is missing: tree_sitter_swift not installed.
The tree-sitter core was fixed, but each language's grammar (tree_sitter_swift, etc.) is a separate package and wasn't installed. Install them all at once here. In particular, installing tree-sitter-bash lets you AST-parse the shell orchestration set too — which was the real prize in my environment.
uv pip install --python "$PY" --no-cache \
tree-sitter-bash tree-sitter-swift tree-sitter-python \
tree-sitter-typescript tree-sitter-javascript tree-sitter-json tree-sitter-go
All seven languages import OK. This tree-sitter-bash install was what did it: the 13 shell scripts and 32 Swift files that had been 0 nodes became parseable. Re-retry:
Swift AST: 323 nodes, 759 edges
It worked. Fix the arch, install the grammars — it comes back in these two stages. With only one of them it goes silent at 0 nodes, so both are required. The overall flow looks like this.
| Stage | State |
|---|---|
| Initial | Total failure at dlopen incompatible architecture
|
| After fix ① (arm64 tree-sitter) | Arch error gone, but 0 nodes |
| After fix ② (language grammars) | 332 nodes / 653 edges |
Building the code graph: bash + Swift in one graph
Code → 332 nodes
First the code assets via AST (no LLM, completely free). 13 shell orchestrators (GPU arbitration, dispatcher, monitoring, etc.) + 32 Swift app files = 45 files in one graph.
Graph: 332 nodes, 653 edges, 23 communities
God node: core ViewModel ← correctly detects the app's hub
One best practice here. Exclude tests, include infra/docs. Tests bloat the node count 3–4× and muddy the clustering (a rule of thumb noted even in graphify's official guide). Conversely, infra config and docs tell you "how the system is wired (what depends on what)."
+ docs → 629 nodes
Next I added 60 docs — CLAUDE.md and design docs — to the graph via semantic extraction (parallel subagents). Result:
ENRICHED Graph: 629 nodes, 986 edges, 76 communities
Code structure and design knowledge now co-reside in one graph. Diagrammed, the build flow looks like this.
Actually querying it
Ask "what is the GPU-arbitration area connected to?" and it returns this via BFS (identifiers genericized; format is as-is from real output).
$ graphify query "gpu arbitration freeze thaw"
Traversal: BFS depth=2 | Start: [GpuMonitor] | 13 nodes found
NODE GpuMonitor [src=app/services/gpu_monitor.swift L9 community=8]
NODE MemSwitchScript [src=scripts/mem_switch.sh L1 community=29]
NODE thaw_ollama() [src=scripts/mem_switch.sh L38 community=29]
NODE LocalLLMRuntime [src=docs/env_snapshot.md — community=6]
EDGE GpuMonitor --shares_data_with--> CoreViewModel [INFERRED 0.85]
EDGE MemSwitchScript --references--> LocalLLMRuntime [EXTRACTED 1.0]
EDGE thaw_ollama() --calls--> LocalLLMRuntime [EXTRACTED 1.0]
A Swift type (code), a shell script and function (code), and the design note's "local LLM runtime" (doc) come out mixed into a single answer. And it attaches the relationship type and confidence too, like shares_data_with (INFERRED 0.85) and references (EXTRACTED 1.0). It returns "the implementation" and "why / where it connects" at once — that's the effect of co-residing code and docs. graphify explain "<node>" lays out Type / Community / Degree and a list of connections in plain terms.
Performance measurement: ~215× token reduction
Here's the measured output of graphify benchmark.
Corpus: 31,450 words → ~41,933 tokens (naive)
Graph: 629 nodes, 986 edges
Avg query cost: ~195 tokens
As a table:
| Method | Cost per query | Ratio |
|---|---|---|
| Load the whole corpus (naive) | ~41,933 tokens | 1× |
| graphify query | ~195 tokens | ~215× reduction |
(Corpus 31,450 words / graph 629 nodes, 986 edges / measured with graphify benchmark.)
There are two numbers. The claimed "up to 71.5×" is graphify's official benchmark, and my own measured value on this corpus is ~215× (the output of graphify benchmark). It varies with the corpus content and the question — the more you ask about structure and dependencies, the better it works; it doesn't help with edits themselves or with files outside the graph. But the common result is that "the tax of re-loading the codebase every time" disappears by an order of magnitude.
The byproducts are all tasty too
graphify emits a pile of artifacts in one build. Using them all pays off.
-
HTML (
graph.html): an interactive graph you open in a browser. No server needed. - Obsidian vault: bidirectional links, one md per node. Turns into a "second brain."
-
Wiki: per-community articles. Agents navigate from
wiki/index.md. -
MCP server:
python -m graphify.serve graph.json. Exposesget_node/get_neighbors/shortest_pathas tools. Agents can query the graph live (requires themcppackage). -
Always-on integration:
graphify claude installputs rules and a PreToolUse hook into CLAUDE.md, so from then on it references the graph automatically before codebase questions, no explicit/graphifyneeded.
The generated Obsidian notes are one node = one memo, with frontmatter and [[…]] backlinks (identifiers genericized). First, the GpuMonitor note:
---
source_file: "app/services/gpu_monitor.swift"
type: "code"
community: "Community 8"
---
# GpuMonitor
## Connections
- [[CoreViewModel]] - `shares_data_with`
- [[MemSwitchScript]] - `conceptually_related_to`
Open the connected CoreViewModel note and the reverse link is drawn automatically (bidirectional):
---
source_file: "app/models/core_view_model.swift"
type: "code"
community: "Community 1"
---
# CoreViewModel
## Connections
- [[GpuMonitor]] - `shares_data_with`
- [[MetricsProbe]] - `calls`
- [[SessionProbe]] - `calls`
Open it in Obsidian's graph view and these bidirectional links become a node web as-is, letting you trace "which module connects to what" without reading a single line of code.
Takeaways (for anyone running graphify on Apple Silicon)
-
Fix the arch error in two stages: reinstall
tree-sittercore for arm64 → install the language grammars (tree-sitter-bash/swift/ …). With only one, it goes silent at 0 nodes. - Install the bash grammar: shell-centric orchestration is exactly where graphing pays off most.
- Cut tests, include infra/docs: the graph's stars are cluster purity and "wiring."
- Use the model API for doc semantics: code is local AST (free); docs/images cost tokens via model calls. If you're cost-conscious, narrow the scope (images are especially expensive via vision — and unnecessary for a dependency graph, so excluding them is the right call).
Once you're past the arch trap, graphify runs perfectly on Apple Silicon. And once you've turned things into a graph, the AI starts working with "the full context of the project in hand" — the biggest takeaway was that it's not just about speed.


Top comments (0)