2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI のコード探索を grep 頼みから一歩進めるための CRG 入門

2
Posted at

NTTデータCCS で部門横断での技術支援を担当している riue です。最近は関東でも少し田舎のほうに行くと行政が用意した「クマ出没注意」的な標識や案内を目にすることが多く、過剰にビビらされています。

はじめに

大きなコードベースを AI エージェントに調べさせると、エージェントが rg でファイル検索を繰り返し、関連するファイルを次々に読み始めることがあります。小規模なリポジトリなら問題になりませんが、規模が大きいと調査時間もコンテキストも増えます。そして大量に読ませても、そのすべてが判断に必要とは限りません。

実際に我々が知りたいのは、たとえば「認証処理はどこか」「この関数の呼び出し元は誰か」「このファイルを変えると、構造上どこまで影響するか」です。こうした情報が先に分かっていれば、コードベース全体ではなく、関係する関数やファイルに絞って読めます。

今回紹介する CRG は、そのための仕組みを提供します。CRG は事前にコードベースを Tree-sitter で解析し、関数やクラス、import などから構造グラフを作ります。グラフを使って呼び出し関係や変更範囲を調べられるほか、一定範囲で意味検索 (semantic search) も利用できます。CLI と付属 Skill を使って利用しますが、補助的なインターフェースとして MCP も用意されています。

CRG は code-review-graph のフォークで、元々は Better Code Review Graph という名前でした。そのため、現在でも better-code-review-graph という名前が随所に残っています。

Tree-sitter は、ソースコードを解析して構文木を作るためのパーサーです。たとえばコード中の「これは関数定義」「これはクラス」「これは関数呼び出し」といった構文上の構造を取り出せます。コンパイラのようにプログラム全体の型チェックや完全なシンボル解決を行うものではないため、コードがビルドできない状態でも解析できます。

CRG はコードを読む前に範囲を絞る

CRG の中心にあるのは、ソースコードから作られる構造グラフです。文字列検索が「この文字列はどのファイルにあるか」を探すのに対し、CRG では「この関数を呼ぶ関数は何か」「このファイルを import するファイルは何か」といった関係を調べられます。

たとえば、コードから次のような関係を得られます。

AuthService.login といったメソッドの呼び出し元を知りたい場合、CRG が呼び出し先を解決できていれば、コード全体から login という文字列を検索する必要はありません。AuthService.login を終点とする CALLS 関係を逆向きにたどれば、呼び出し元を調べられます。

ただし、CRG のいう「knowledge graph」は、コンパイラや Language Server が作る完全なシンボル解決済みのプログラム表現ではありません。CRG はこれを、Tree-sitter によって抽出された "structural graph of functions/classes/imports" と説明しています。この記事でも knowledge graph という語を使いますが、その実体は Tree-sitter による解析を基礎に構築したコードの構造グラフと考えるのが適切です。

また、CRG を使っても、ソースコードを読まずに済むわけではない点にも注意が必要です。CRG はあくまでも、コードを読む前に読む場所を絞るための道具です。

文字列検索 / グラフ探索 / 意味検索の違い

それぞれ、得意なことが分かれています。CRG の価値は検索を置き換えることではなく、検索で見つけたコードから依存関係をたどれることにあります。一方で、コードの構造グラフには登場しない文字列リテラル (プログラム中のメッセージなど) は、rg や grep のような文字列検索でないと見つけられません。

方法 得意な問い
rg などの文字列検索 createSession という文字列はどこにあるか
CRG の構造グラフ createSession を誰が呼び、何が依存しているか
CRG の意味検索 認証やセッション生成に関係しそうなシンボルはどこか

CRG が扱う二種類の情報

CRG が持つ情報は、構造グラフとベクトル埋め込みに大別できます。構造グラフは crg graph build、ベクトル埋め込みは crg graph embed で準備します。それぞれ目的が異なります。

構造グラフは「A が B を呼ぶ」「A が B を import する」といった関係を表します。ベクトル埋め込みは、シンボルと検索文の意味的な近さを使う検索に利用します。

CRG が効果を発揮する局面

CRG は、どんなコードベースでも有効なわけではありません。小規模なリポジトリや開発を始めたばかりのプロジェクトならコード全体を把握することも難しくなく、rg などの文字列検索だけでも十分に調査できます。このような場合、構造グラフの構築・維持のオーバーヘッドのほうが負担になります。

一方で、ある程度の規模があり、かつ構造を十分に把握していない既存のコードベースを調査する場合は大きな助けになります。初めて担当するリポジトリで機能の実装場所を探す、既存の関数を変更する前に呼び出し元や関連テストを調べるといった局面では、コードを端から読む前に構造グラフから調査範囲を絞れます。

全体を容易に見渡せるリポジトリなら rg で十分です。どこから読めばよいか分からない、あるいは変更の波及先を人間が頭の中だけでは追いにくくなったところから、CRG のような構造グラフを使う価値が大きくなります。

CRG のインストールとリポジトリの準備

では、実際に CRG を使うための方法について説明します。なお、本記事の説明は CRG 3.29.8 を対象にしています。

CRG のインストール

uv を使っていれば、以下のようにしてインストールできます。

uv tool install -p 3.13 'better-code-review-graph[security]'

[security] は、高度なセキュリティスキャンを行うために Semgrep エンジンもインストールすることを示すものです。この場合、事前に uv tool install semgrep で Semgrep もインストールしておく必要があります。

コンソールで crg --version が動作すればインストールは完了です。なお、単純に crg を実行すると CRG の MCP サーバが起動するので注意してください (Ctrl+C で終了させられます)。

crg graph build で構造グラフを作る

最初に、リポジトリ内のソースコードに対する構造グラフを構築します。リポジトリの最上位で、以下を実行してください。リポジトリの .crg ディレクトリ以下に graph.db という SQLite データベースファイルが作成されます。

crg graph build --full-rebuild --repo-root .

CRG は対象ファイルを解析し、関数やクラス、ファイルなどを Node として、包含、import、関数呼び出し、継承などを Edge として抽出します。構文解析に基づくため、対象プロジェクトをコンパイルできなくてもグラフを作成可能です。

初回実行時は全体のグラフを構築するために --full-rebuild オプションを指定する必要がありますが、以降はオプションなしの crg graph build を使った差分更新で充分です。CRG は前回の構築時点と現在の Git の状態から変更ファイルを調べ、既存グラフを使ってそれらに依存するファイルも確認し、ファイル内容のハッシュを比較して内容が変わったファイルだけを再解析するので、毎回リポジトリ全体を解析し直す必要はありません。

node_modules、.git、.venv、dist、build といったディレクトリは標準で除外した上で、基本的には Git が追跡しているファイルのみが対象として解析されます。加えて、.code-review-graphignoreで独自の除外対象も追加できます。

crg graph embed で意味検索を行う

上記の構造グラフを使うと、グラフに含まれた名前やキーワードに対する検索が可能になります。さらに意味検索を有効にすると、名前が完全には一致しない概念を検索する、あるいはモデルが対応する範囲で日本語の問い合わせから英語のシンボルを探すこともできます。

CRG は構造グラフの Node に含まれる名前、Node 種別、親シンボル、シグネチャに含まれる引数・返却値、言語などのメタデータをテキスト化し、その内容からベクトル検索用の埋め込みを作成します。たとえば、次のような情報が埋め込み対象になります。

verifyCredentials
function
in AuthService
(email: String, password: String)
returns User
swift

関数本体の処理内容は含まれていない点に注意してください。ソースコード全体に対する意味検索ではなく、シンボル名、シグネチャ、型などを中心にした意味検索に留まります。したがって authenticate、verifyCredentials、validateUser のような名前なら、「authentication」という検索語にマッチする可能性があります。一方、実装では認証していても名前が run や process のように一般的で、引数や型にも手掛かりがなければ、意味検索で発見できるとは限りません。

意味検索を使えるようにするには、graph embed で埋め込みを準備します。コードサイズが大きいとそれなりに時間を要します。

crg graph embed --repo-root .

CRG は ONNX Runtime を使って埋め込みモデルなどをローカルで実行するための Python ライブラリ fastretrieval を使って埋め込みを生成します。そのため、標準構成の意味検索では外部の LLM へコードの情報を送る必要はありません。埋め込みに使うモデルである Qwen3-Embedding-0.6B (約 570MB) は初回実行時に Hugging Face からダウンロードされ、~/.cache/fastretrieval 以下に保管されます。

コマンドラインから CRG を使う

構造グラフと埋め込みの準備ができたら、ソースコードの調査に CRG を使うことが可能になります。

意味検索から変更範囲を絞り込む

「ログインに必須の tenantId を追加する」という変更を考えます。リポジトリにはファイルが多数あり、セッション生成の場所も分からないとします。対象シンボルの名前が分からないため、まずは意味検索で探します。

crg query search \
  --search-query "authentication session" \
  --repo-root .

この検索では主に埋め込みを利用し、問い合わせに意味的に近い Node を探します。ここで AuthService.createSession が候補に出たとします。

対象が分かったら、構造グラフで呼び出し元を調べます。

crg query query \
  --pattern callers_of \
  --target "AuthService.createSession" \
  --repo-root .

たとえば次の呼び出し元が見つかった場合、tenantId を必須引数にすると、これらの修正が必要になる可能性が高いと分かります。

LoginController.login
AdminLoginController.login

対象ファイルを変更した場合の波及範囲も調べられます。

crg query impact \
  --changed-files src/auth/AuthService.ts \
  --max-depth 2 \
  --repo-root .

ここで得られるのは、一般的な意味での完全な影響分析結果ではありません。CRG が把握する呼び出し、import、継承などの関係をたどり、構造グラフ上で変更が及びそうな範囲 (blast radius) となります。

対象と呼び出し元に関連するテストも確認します。

crg query query \
  --pattern tests_for \
  --target "AuthService.createSession" \
  --repo-root .

こうしてリポジトリ内から、たとえば次の候補まで調査対象を絞れます。

AuthService
LoginController
AdminLoginController
SessionRepository
AuthServiceTests

この段階で、候補のソースコードを読みます。つまり、基本的な流れは以下のようになります。

  • 意味検索で入口を見つける
  • 構造グラフで関係をたどる
  • 必要なコードを読む

主要コマンドの使いかた

CRG では search や callers_of など、定型的な検索・グラフ探索を利用できます。callers_of などの探索対象は crg query query のオプション --pattern で指定します。

知りたいこと 主なコマンド 主に調べる情報
グラフの規模や状態 crg graph stats Node 数、Edge 数、言語、embedding 数
機能の実装場所 crg query search 名前、キーワード、embedding
関数の呼び出し元 callers_of CALLS
関数の呼び出し先 callees_of CALLS
このファイルを import しているファイル importers_of IMPORTS_FROM
このファイルが import しているファイル imports_of IMPORTS_FROM
継承するクラス inheritors_of 継承関係
関連するテスト tests_for テストとの関係
変更が及びそうな範囲 crg query impact 構造グラフの探索
大きな関数やクラス crg query large_functions Node の行数情報
現在の変更のレビュー crg review context Git 差分と構造グラフ

付属 Skill で開発作業を進める

CRG 付属の Skill を使うと、コマンドを毎回手作業で組み合わせずとも、典型的な開発作業を進められます。これらの Skill はソースツリーの skills 以下に収められています。

開発段階 Skill 調べる内容
初めてリポジトリを見る onboard-repo コードベースの構造
変更を計画する impact-audit 変更が及びそうな範囲
シンボルを変更する前 refactor-check 関数やクラスの変更に伴う影響
実装後、コミット前 review-delta 未コミット変更の問題
PR 作成前 review-pr ブランチ全体の問題
セキュリティ調査 security-sweep scanner の検出結果の優先順位

onboard-repoは、未知のリポジトリを端から読む Skill ではありません。代表的な処理を 2~3 段たどって内容を調べたうえで、最初に読むファイルや代表的な処理経路を示す「orientation map」を作成します。気になる処理がある場合は、Skill 呼び出し時にその情報を与えるとよいでしょう。

impact-audit は、ある変更がどの辺りに波及しそうかを調べます。たとえば「createSession に必須の tenantId を追加する」と伝えると、定義箇所、直接の利用者、import や継承の関係、構造グラフ上の波及範囲、関連テストを確認します。複数のリポジトリを一つのグラフに登録していれば、リポジトリをまたぐ変更候補も調べられます。

refactor-check、review-delta、review-prも同じ考え方に基づきます。変更箇所だけでなく、呼び出し関係や関連テストをグラフから集め、LLM に渡すコードを必要な範囲に絞ります。

security-sweep は脆弱性につながりそうなコードを探し、次に CRG の呼び出しグラフを使ってそのコードが外部から利用される処理 (HTTP のハンドラなど) から呼び出され得るかを調べます。デフォルト (heuristic) では正規表現ベースでいくつかのパターンを調査する程度ですが、Semgrep が追加されている場合は Semgrep も使って更なる解析を行います。

フックによるグラフの更新

コードが変われば、グラフも古くなります。開発者が変更のたびに手で更新する運用では、いずれ更新されなくなる可能性があります。そこで CRG は、ファイルの編集やシェル実行後に差分更新する仕組みを フック として用意しています。

  • SessionStart
    • セッション開始時に CRG の存在を伝え、コード検索前に CRG を使うように促す指示をセッションに入れる
  • UserPromptSubmit
    • 現在の CRG グラフを信用してよい状態かを確認 (graph.db が古くなっていないかを確認し、問題があれば更新を促す)
  • PostToolUse
    • crg graph build を実行して CRG のグラフを更新
    • Git の rebase などで履歴を書き換えたことによる差分更新失敗の可能性を検出 (この場合、--full-rebuild が必要になる)

手元で Codex 向けにしてみました。以下に.codex/hooks.json の登録例を示します。

.codex/hooks.json へのフック登録例
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv run --no-project python .codex/hooks/session-start.py"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "uv run --no-project python .codex/hooks/stale-graph-check.py"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "crg graph build > /dev/null 2>&1 || true"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "uv run --no-project python .codex/hooks/branch-sync-check.py"
          }
        ]
      }
    ]
  }
}

.codex/hooks/session-start.py は Codex 用に新しく実装したものです。stale-graph-check.py と branch-sync-check.py、および両者が利用する _graph_state.py は CRG リポジトリの hooks 以下にあるものを利用できます。

.codex/hooks/session-start.py の実装例
import json
import os

def emit(text: str) -> None:
    print(
        json.dumps(
            {
                "hookSpecificOutput": {
                    "hookEventName": "SessionStart",
                    "additionalContext": text,
                }
            }
        )
    )

if (
    os.path.exists(os.path.join(".crg", "graph.db"))
    or os.path.exists(os.path.join(".better-code-review-graph", "graph.db"))
):
    emit(
        "[crg] Knowledge graph available. "
        "Prefer crg MCP tools "
        "(semantic_search_nodes, query_graph "
        "callers_of/callees_of/imports_of/importers_of/children_of/"
        "tests_for/inheritors_of/file_summary, "
        "get_impact_radius, get_review_context) "
        "before manual Grep/Glob/Read. Saves tokens."
    )
else:
    emit(
        "[crg] No knowledge graph found. "
        "Use the graph tool action=build to enable graph-powered queries."
    )

グラフはコードに変更が発生する都度更新する必要があり、これは上記の PostToolUse フックで実施されています。ただし、上記のフックでも検出しているように、履歴が書き換わった場合は一部で取りこぼしが発生する可能性があるため、その場合は --full-rebuild が必要となります。

一方、コードを少し変えるたびに graph embed を実行する必要はありません。現在の CRG の埋め込みは主にシンボル情報を対象としているため、関数本体だけの小さな変更なら、埋め込みに使われる検索用テキストは変化しないからです。一方、新しい関数やクラスの追加後や、シンボル名、引数、返却値などの変更が発生した場合には graph embed を再実行するとよいでしょう。CRG は検索用テキストのハッシュを保持しているため、再実行しても内容が変わっていないシンボルの埋め込みは再生成されません。

まとめ

CRG の役割は、コードを読まなくて済むようにすることではなく、読むべきコードを早く決めることです。意味検索で入口を探し、構造グラフで関係をたどり、最後は必要なソースコードを読むという順序にすることで、大きなコードベースでも調査対象を小さく絞ることができます。

ただし、CRG のグラフはコンパイラによる完全なシンボル解決ではありません。たとえば cache.get(key) という呼び出しを認識できても、cache の型を常に特定の具象クラス、たとえば RenderCache.get に対応付けられるとは限りません。したがって「CRG で見つからなかったから存在しない」と判断せず、グラフの精度に応じて rg のような通常のテキスト検索も併用する必要があります。

大規模なコードベースほど、「たくさん読ませる」ことより「何を読ませるかを先に決める」ことが重要になります。CRG はそのための探索基盤として大きな助けとなるはずです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?