結論

ローカル LLM でコーディングエージェントを動かすとき、コンテキストの消費効率が実用性を左右する。Serena のようなシンボル解析ツールは精度が高いが、最初からすべてのシンボルを読み込むとコンテキストが膨張する。

そこで、自分がよく使う言語だけを対象に、AST ベースでコードベース全体を浅く俯瞰するオンデマンド解析器 ctree を Rust で自作した。コードベース全体のスナップショットをハッシュベースで管理し、実装の変化と依存の変化をコンパクトに追跡する。フォーカスする関心に応じて実装者が任意でスコープを調整できる仕組みにした。

ctree で浅いヒントを得て、そこから Serena の find_symbol で解像度を上げる — これが設計の核心だ。pathfinder と組み合わせることで、ファイル操作ツールの精度向上とコンテキスト節約を同時に実現する。

GPU のコンテキスト単価を考えれば、ソフトウェア側でのパフォーマンス改善は費用対効果が高い。

Zed Editor の MCP Servers 画面: ctree (5 tools), pathfinder, serena (22 tools) が並んでいる
Zed Editor の MCP Servers 画面 — ctree, pathfinder, serena が独立した MCP ツールとして動作する

動機: Serena のコンテキストコストを補完する

Serena はシンボルレベルの精密な解析ができるが、「まずコードベース全体を浅く把握する」用途には向いていない。全シンボルを読み込めばコンテキストが膨張し、ローカル LLM の限られたコンテキストウィンドウを圧迫する。

必要だったのは、Serena の前段に置く「浅い俯瞰」レイヤーだ。

ツール役割
ファイル操作の精度向上pathfinderパス解決の失敗をリカバリし、コンテキスト膨張を防ぐ
コードベースの浅い俯瞰ctree実装と依存の変化をハッシュベースでコンパクトに提供
シンボルの精密な解析Serenafind_symbol で特定シンボルの定義・参照を取得

ctree は pathfinder と合わせて設計した。pathfinder がファイルパスの精度を担保し、ctree がコードベースの構造的なヒントを提供し、Serena が最終的な解像度を上げる。三層それぞれが独立した MCP ツールとして動作する。


CLI

  $ ctree --help
Generate annotated tree + symbols + compact ctx

Usage: ctree [OPTIONS] [COMMAND]

Commands:
  init
  reset
  check
  clean
  help   Print this message or the help of the given subcommand(s)

Options:
      --mcp                    Run as MCP stdio server
      --config <CONFIG>        Config file path (default: .ctree.toml if exists)
      --root <ROOT>            Scan root path [default: .]
      --syntax <SYNTAX>        [possible values: go, rust, python, typescript,
                                csharp, dart, lua, awk, shell, kotlin, swift,
                                markdown, html]
      --include <INCLUDE>      Include globs
      --exclude <EXCLUDE>      Exclude globs [default: **/tmp/**,**/testdata/**,
                                **/.git/**,**/vendor/**]
      --strong <STRONG>        Strong scope globs
      --weak <WEAK>            Weak scope globs
      --sw <SW>                Max annotation words per strong file line [default: 12]
      --ww <WW>                Max annotation words per weak/symbol-only file line [default: 3]
      --reasoning <REASONING>  Reasoning level [possible values: high, medium, low]
      --template <TEMPLATE>    Output template [possible values: plain, hugo, jinja]
  -h, --help                   Print help
  

設計: AST ベースのオンデマンド解析

対応言語

自分がよく使う言語に絞って tree-sitter ベースの AST パーサーを実装した。

Go, Rust, Python, TypeScript/JavaScript, Dart, Kotlin, Lua, Shell, Awk, C#, Swift, Markdown, HTML

アプリケーション本体だけでなく、シェルスクリプトやドキュメントまで同じフレームワークで扱う。実際のリポジトリでは主要言語だけ見ても全体像が分からないことが多い。

strong / weak スコープ

コードベース全体を均一に解析するのではなく、ファイルごとに strong(詳細)と weak(概要)のスコープを分ける。

  [ctree]
watch = "rust"
root = "."
include = ["src/**/*.rs", "tests/**/*.rs"]
exclude = ["**/tmp/**", "**/testdata/**", "**/.git/**", "**/vendor/**", "**/target/**"]
strong = ["src/**"]
weak = ["tests/**"]
sw = 24
ww = 5
reasoning = "medium"
  

strong に指定したファイルは詳細なシンボル要約を持ち、weak は軽い概要だけを持つ。フォーカスする関心が変われば、この設定を変えるだけでスコープが切り替わる。

この分離が ctree の核心的な設計判断だ。コードベース全体を重く持つのではなく、「どこを厚く見るか」を実装者が能動的に決める。

reasoning プリセット

要約の詳細度は reasoning = "high" | "medium" | "low" の 1 フィールドで制御する。

reasoning用途
high小規模プロジェクト、詳細な解析が必要な場面
medium通常の開発作業(デフォルト)
low大規模モノレポ、トークンを最小限に抑えたい場面

ハッシュベースのスナップショット

スコープ設定(strong/weak パターン + reasoning レベル)から決定論的にハッシュを生成し、スナップショットディレクトリを分離する。

  .ctree/
  rust/
    e83adb52/          ← スコープ設定のハッシュ
      snapshots/
        .baseline.txt
        rev/
          0001.txt
          0002.txt
  

スコープを切り替えても過去のスナップショットは保持される。戻せば再生成なしに復元できる。ブランチ切り替えやフォーカス変更を低コストにする設計だ。


MCP ツール

ctree は 5 つの MCP ツールを公開する。

ツール役割
checkスナップショットの生成・更新。最新の差分を返す
get_baselineコードベース全体のアノテーション付きツリー + シンボル要約を返す
get_revsリビジョン履歴(何が変わったか)を返す
get_textハッシュからシンボルや依存のテキストを取得する
get_dependsシンボル間の依存関係を探索する

実際の出力

ctree check — スナップショット生成と差分検出

  $ ctree check --syntax rust
frequency=always
action=generate
latest_before=0001
latest_after=0001
(no new rev)
  

変更がなければ (no new rev) で即座に返る。変更が検出されると新しいリビジョンが生成され、変化したシンボルのハッシュが返る。

初回生成時の出力:

  $ ctree check --syntax rust
frequency=always
action=generate
latest_before=
latest_after=0001
rev_file=.ctree/rust/{scope_hash}/snapshots/rev/0001.txt
hashes={hash1},{hash2},{hash3},...

{hash1}
source=symbol kind=module name=... scope=strong path=src/.../main.rs line=2
mod ...;
--{hash1}
{hash2}
source=symbol kind=function name=... scope=strong path=src/.../mcp.rs line=164
fn ...(args: ...) -> Result<...> {
--{hash2}
  

各ハッシュはシンボルの識別子で、get_text に渡すと本文を取得できる。

get_baseline — コードベースの浅い俯瞰

get_baseline が返すのは、アノテーション付きファイルツリーとシンボル要約だ。ソースコード全文ではない。baseline にはトークン推定値が含まれ、ここから関心のあるシンボルを特定して Serena の find_symbol で定義を取得する。

出力フォーマットの詳細は非公開。自社の LLM パイプライン基盤に組み込んで運用しているため。

get_revs — リビジョン差分

シンボルの追加・削除と依存の追加・削除をコンパクトに表現するリビジョン差分を返す。ハッシュを get_text に渡すとシンボルの本文や依存の詳細を取得できる。

出力フォーマットの詳細は非公開。


pathfinder + ctree + Serena の三層連携

pathfinder — パス解決の失敗をリカバリする

LLM がディレクトリ名を typo した場合でも、pathfinder が正しいパスを解決する。

  path_resolve:
  check_path: "content/ja/docs/tech/infrastrcture/podman-quadlet-systemd-ubuntu.md"
                                      ^^^^^^^^^^^ typo
  →  resolved: "content/ja/docs/tech/infrastructure/podman-quadlet-systemd-ubuntu.md"
  
  $ pathfinder --help
pathfinder — semantic path finder & MCP resolution server

USAGE
    pathfinder [OPTIONS]          Interactive semantic directory finder (default).
    pathfinder --mcp [OPTIONS]    Start as an MCP server.

FINDER OPTIONS
    --include-builds    Include build/artifact dirs (target, dist, …).

MCP OPTIONS
    --root <PATH>       Add a project root directory to watch and index.

GENERAL OPTIONS
    -h, --help          Print this help message and exit.
    -V, --version       Print version, model, and PCA config.

MCP TOOLS
  1. path_resolve        Resolve a failed file path to the best match.
  2. tool_retry_with_resolve  Resolve + retry the operation in one call.
  3. roots_list          Return configured root directories.
  4. reindex_paths       Force a full index rebuild.

ENVIRONMENT VARIABLES
    PF_MCP_INFERENCE    Inference mode: "general" (default) or "code".
    Models (both INT8 quantized):
      general → mxbai-edge-colbert (17M, 48-dim)
      code    → lateon-code-edge (17M, 48-dim)
  
pathfinder CLI: pf コマンドでセマンティック検索し、ディレクトリを選択して cd する
pathfinder CLI — セマンティック検索でプロジェクトを横断的に俯瞰し、選択結果のディレクトリに cd する

想定ワークフロー

  1. pathfinder  → LLM のパス typo を解決(ENOENT → tool_retry_with_resolve)
2. ctree check → スナップショット更新、変更されたシンボルのハッシュを取得
3. get_baseline → コードベースの浅い俯瞰を得る
4. 関心のあるシンボルを特定 → Serena の find_symbol で定義・参照を深掘り
5. get_revs + get_text → 変更されたシンボルの詳細を取得
  

この構成の要点は、各層が独立した MCP ツールとして動作し、必要な情報を段階的に取得する点にある。最初から全部を読み込むのではなく、浅い俯瞰から始めて必要に応じて深掘りする。

GPU でのコンテキスト処理は高コストだ。8K〜32K のコンテキストウィンドウで動作するローカル LLM では、ソフトウェア側でコンテキスト効率を改善することが直接的なコスト削減になる。


注意事項

  • ctree は自分がよく使う言語に限定した AST パーサーであり、汎用の静的解析ツールではない
  • get_baselineget_revs の出力フォーマット詳細は非公開。自社の LLM パイプライン基盤に組み込んで運用しているため
  • ML 推論(ONNX、ColBERT 等)の組み込みは検討して見送った。strong/weak スコープ制御で十分にモノレポのコンテキスト膨張問題を解決できるため、決定論的な設計を維持した
  • MCP プロトコル(2024-11-05)に準拠。Claude Code、Zed Editor 等の MCP クライアントで利用可能