🇯🇵 This post is written in Japanese. Originally published on Zenn(この記事はZennからの転載です)。
Claude Codeが毎回ファイルを読み直す問題
Claude Codeで大きめのプロジェクトを触っていると、毎セッション同じことが起きる。「あのファイルどこだっけ」でgrep、全文Read、また別のファイルをRead……。コードベースはgrepには大きすぎ、コンテキストに丸ごと積むには高すぎる。
これを解くのが graphify(PyPIパッケージ名はgraphifyyで yを2つ重ねる/CLIコマンドはgraphify)。フォルダを丸ごと知識グラフにして、Claudeがファイルを読み直す代わりにグラフを引く。公称「クエリあたり最大71.5xのトークン削減」。
知識グラフとは何かを一言で。コードとドキュメントを解析して「関数・クラス・概念=ノード」「呼ぶ・依存する・参照する=有向エッジ」の有向グラフとして抽出したもの。質問すると、全文を読み直す代わりに関連ノードだけを部分グラフで返す。だから軽い。graphifyはコードをtree-sitterでローカルAST解析(LLM不使用)し、ドキュメントだけをモデルで意味抽出する。
M1 Max(Apple Silicon)で、自分のプロジェクト——シェルのオーケストレーション群、Swiftのメニューバーアプリ、設計ドキュメント——を丸ごとグラフ化しようとした。そしていきなり全滅した。この記事はその実録。
トラブルシューティング
全滅:incompatible architecture
uv tool install graphifyy で入れて、AST抽出を走らせた瞬間これが出た。
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'))
tree_sitter のネイティブバインディング(.so)が x86_64 で、arm64のPythonがロードできない。graphifyはコードをtree-sitterでAST解析するので、ここが死ぬとコード抽出が全滅する。
Apple Siliconあるあるだが、厄介なのは「graphify自体が抱えていたx86_64依存」だったこと。自分のコードでもモデルでもなく、ツールのネイティブ依存がx86_64前提でインストールされていた。原因はシンプルで、arm64のPythonがx86_64の.soをdlopenできずに落ちている。uv toolの解決時にx86_64ホイールを掴んでいた。
修正①:tree-sitterをarm64で入れ直す
uv tool環境にはpipが無いので、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)が入り、dlopenの悲鳴は止まった。……が、まだAST結果は0ノードだった。
修正②:言語grammarが「そもそも」入っていなかった
再試行で今度はこう出た。
warning: 32 .swift file(s) contributed nothing to the graph
because a dependency is missing: tree_sitter_swift not installed.
tree-sitterのコアは直ったが、各言語のgrammar(tree_sitter_swift等)が別パッケージで、未導入だった。ここで一気に入れる。特に tree-sitter-bash を入れると、シェルスクリプトのオーケストレーション群もAST解析できるようになる——これが自分の環境では本命だった。
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
7言語すべてimportOK。このtree-sitter-bash導入が効いて、それまで0ノードだったシェル13本+Swift32本が解析対象になった。 再々試行:
Swift AST: 323 nodes, 759 edges
動いた。 archを直し、grammarを入れる——この2段で復活する。片方だけだと0ノードで沈黙するので、両方必須。全体の流れはこう。
| 段階 | 状態 |
|---|---|
| 初期 | dlopen incompatible architecture で全滅 |
| 修正①後(arm64 tree-sitter) | archエラーは消えるが 0ノード |
| 修正②後(言語grammar導入) | 332ノード / 653エッジ |
コードグラフ構築:bash+Swiftを1グラフに
コード → 332ノード
まずコード資産をASTで(LLM不使用・完全無料)。シェルのオーケストレーション13本(GPU調停・ディスパッチャ・監視系など)+Swiftアプリ32本=45本を1グラフに。
Graph: 332 nodes, 653 edges, 23 communities
God node: 中核ViewModel ← アプリの中枢を正しく検出
ここでベストプラクティスをひとつ。testは除外、infra/docsは含める。 testはノードを3-4倍に膨らませてクラスタリングを濁す(graphifyの公式ガイドでも指摘される経験則)。逆にインフラ設定やドキュメントは「システムがどう配線されているか(何が何に依存するか)」を教えてくれる。
+ドキュメント → 629ノード
次に、CLAUDE.mdや設計ドキュメント60本をsemantic抽出(並列サブエージェント)でグラフに足した。結果:
ENRICHED Graph: 629 nodes, 986 edges, 76 communities
コード構造と設計知識が1つのグラフに同居した。構築の流れを図にするとこう。
実際に引いてみる
「GPU調停まわりは何と繋がってる?」を聞くと、BFSでこう返る(識別子は汎用名にしてある。形式は実出力のまま)。
$ 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]
Swiftの型(コード)、シェルのスクリプトと関数(コード)、設計メモの「ローカルLLM基盤」(ドキュメント)が1つの答えに混ざって出てくる。shares_data_with(INFERRED 0.85)やreferences(EXTRACTED 1.0)のように、関係の種類と確信度まで付く。「実装」と「なぜ・どこに繋がるか」を一度に返す——これがコードとドキュメントを同居させた効果だ。graphify explain "<ノード>"ならType / Community / Degreeと接続先一覧を平易に出してくれる。
性能計測:約215xのトークン削減
graphify benchmarkの実測がこれ。
Corpus: 31,450 words → ~41,933 tokens (naive)
Graph: 629 nodes, 986 edges
Avg query cost: ~195 tokens
表にするとこう。
| 方式 | 1回のコスト | 倍率 |
|---|---|---|
| コーパス丸ごと積む(naive) | ~41,933 tokens | 1x |
| graphify query | ~195 tokens | 約215x削減 |
(コーパス 31,450 words / グラフ 629ノード・986エッジ/graphify benchmark実測)
数字は2つある。公称「最大71.5x」はgraphify公式のベンチ値、この自分のコーパスでの自測値は約215x(graphify benchmarkの出力)。コーパスの内容と質問で変わる——構造・依存を尋ねる質問ほど効き、編集そのものやグラフ外のファイルには効かない。ただ「毎回コードベースを積み直す税」が桁で消えるのは共通だ。
副産物も全部おいしい
graphifyは1回のビルドで大量の成果物を吐く。全部使うと効く。
-
HTML(
graph.html):ブラウザで開く対話グラフ。サーバ不要。 - Obsidian vault:1ノード1mdの双方向リンク。「第二の脳」化。
-
Wiki:コミュニティ別の記事。エージェントが
wiki/index.mdから辿る。 -
MCPサーバ:
python -m graphify.serve graph.json。get_node/get_neighbors/shortest_pathをツール露出。エージェントがグラフをライブ照会できる(要mcpパッケージ)。 -
常時ON統合:
graphify claude installでCLAUDE.mdにルールとPreToolUseフックが入り、以後は明示/graphifyなしでコードベースの質問前に自動でグラフを参照する。
生成されるObsidianノートは1ノード=1メモで、frontmatterと[[…]]のbacklinkが付く(識別子は汎用名)。まずGpuMonitorのノート:
---
source_file: "app/services/gpu_monitor.swift"
type: "code"
community: "Community 8"
---
# GpuMonitor
## Connections
- [[CoreViewModel]] - `shares_data_with`
- [[MemSwitchScript]] - `conceptually_related_to`
繋がった先のCoreViewModelノートを開くと、逆向きのリンクが自動で張られている(双方向):
---
source_file: "app/models/core_view_model.swift"
type: "code"
community: "Community 1"
---
# CoreViewModel
## Connections
- [[GpuMonitor]] - `shares_data_with`
- [[MetricsProbe]] - `calls`
- [[SessionProbe]] - `calls`
Obsidianのグラフビューで開けば、この双方向リンクがそのままノード網になり、コードを1行も読まずに「どのモジュールが何と繋がるか」を辿れる。
教訓(Apple Siliconでgraphifyを動かす人へ)
-
archエラーは2段で直す:
tree-sitter本体をarm64で再インストール → 言語grammar(tree-sitter-bash/swift/…)を導入。片方だけだと0ノードで沈黙する。 - bash grammarを入れる:シェル中心のオーケストレーションこそグラフ化の価値が高い。
- testは切り、infra/docsは入れる:グラフの主役はクラスタの純度と「配線」。
- docのsemanticはモデルAPIを使う:コードはローカルAST(無料)、doc/画像はモデル呼び出しでトークンを食う。コスト意識するならスコープを絞る(画像はvisionで特に高い=依存グラフには不要なので除外が正解)。
archの罠を抜ければ、graphifyはApple Siliconで完全に動く。そして一度グラフにすると、AIが「プロジェクト全体の文脈を持った状態」で動き始める——速度だけの話ではない、というのが一番の収穫だった。


Top comments (0)