AIが書いた大量PRを高速レビュー — 輪郭を先に見るCLI rinkaku を作った - エムスリーテックブログ

エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

AIが書いた大量PRを高速レビュー — 輪郭を先に見るCLI rinkaku を作った

こんにちは、メディカルマーケティングオートメーション(MMA)チームの山本(@hiro_o918)です。

ソファーでくつろぐ犬
※ 記事とは何の関係もない、ソファーでくつろぐ犬

皆さんは、AI エージェントが生成した PR のレビューをどうさばいていますか?

コーディングエージェントに任せる作業が増えるにつれて、diff の行数と自分が理解している量がだんだん一致しなくなってきました。1 行ずつ読む速度は昔と変わらないのに、生成される速度だけが上がっていく。PR を開いた瞬間に +1,342 -286 みたいな数字が視界に入って「うっ」と手が止まる、というのは正直あります。しかもこれは他人のコードをレビューするときだけの話ではなくて、自分の作業でさえ、エージェントに任せた分だけ全体像がぼんやりしてくる、というのが実感としてあります。

そこで、PR の中身を読む前に「変更の輪郭」だけを先に見るための CLI ツール「rinkaku(輪郭)」を作りました。この記事ではその紹介と、私自身が AI 時代のコードレビューをどう回しているかを書きます。

rinkaku とは

rinkaku は、diff やリポジトリを「変更されたシンボルのシグネチャと、その依存関係」だけに凝縮して見せる CLI ツールです。

命名は日本語の「輪郭」から取っています。実装の中身を読む前に、まず外形を見る。どの関数・型が触られたのか、それらは何を呼び、何から呼ばれているのか。中身を追うのはそれが必要になってからで十分、というのがこのツールの考え方です。

実感として、diff の見た目の大きさに対してシグネチャレベルで見た変更はずっと小さいことが多くて、+1,342 -286 の PR も輪郭だけ見ると「触っているのは関数 2〜3 個」だったりします。まずその輪郭を眺めておくと、身構え方がだいぶ変わります。

使い方は次の 2 つを用意しています。

  • TUI: ターミナル上でツリーを歩きながら diff と影響範囲を見る。普段のレビューはこちら
  • CLI (Markdown / JSON 出力): LLM のレビュープロンプトに入力として渡す

対応言語は Rust / Go / Python / TypeScript で、依存解決は tree-sitter による構文ベースです。なお PR コメントに mermaid グラフを自動で貼る GitHub Action も同梱していますが、この記事では TUI と CLI に絞って紹介します。

Quick Start

Homebrew か、curl でのインストールスクリプトが手軽です。

# Homebrew
brew install hiro-o918/tap/rinkaku

# curl($HOME/.local/bin に置きたい場合)
INSTALL_DIR="$HOME/.local/bin" bash -c "$(curl -fsSL https://raw.githubusercontent.com/hiro-o918/rinkaku/main/install.sh)"

普段の使い方はほぼ --pr の 1 択なので、私は rkp という短いエイリアスを登録して使っています。

エージェントに書かせた自分の作業をチェックしたいときは、いつも通り push して draft PR を作り、そのままそのリポジトリのディレクトリで PR 番号だけを渡します(TUI で何が見えるかは次節で説明します)。

rinkaku --pr 123

人の PR に向けるときは URL を渡します。

rinkaku --pr https://github.com/owner/repo/pull/123

--pr は、いま自分がどこのディレクトリにいるかを気にしません。他のリポジトリの作業中に Slack でレビュー依頼 URL が飛んできたら、そのまま貼って打てば動きます。地味に嬉しいのは ghq との相性で、渡した URL のリポジトリが手元に ghq で clone 済みなら、それを見つけて再利用します。無ければキャッシュに clone してくるので、実行ディレクトリを切り替えずに見られるのが日々のレビューの効率化に効きます。

TUI で読む — 上から順に、シグネチャだけ

たとえば rinkaku 自身の PR #172 は、TUI 起動時に新版があれば self-update を促す overlay を出す機能を足した PR です。

PR #172 で追加された update prompt overlay。"Update rinkaku to v0.6.18?" と Enter で update & quit / Esc で not now の選択肢が中央に浮かんでいる

PR #172 で追加された update prompt overlay の実物

GitHub の PR ページを開くと、こういう見た目です。

GitHub の PR #172 ページ。右上に +1,336 -690 の変更数、左のファイルツリーは adr と tests が最初に並んでいる

変更数が多く、先頭に tests / ADR が並んでどこから読むか掴みにくい

+1,336 -690 の diff、しかも先頭に tests と adr が並ぶので「どこから読もう」となりがちです。同じ PR を rinkaku --pr 172 で開くと次のような表示になります。

PR #172 の輪郭。run_app に updater 受信の channel が入り、help_overlay.rs に UI が追加され、overlay.rs の旧実装は削除、が 1 画面で見える

上から読むと PR の概要が掴める

スクショの範囲だけでも次のことが読み取れます。

  • updater 周りの DI が刺し直された
    • run_app の引数に update_check が加わっている
  • overlay まわりが改修されて helper が切り出された
    • help_overlay.rs+ fn がまとめて並んでいる

レビューでいちばん腰が重いのは、コードを読む作業そのものというより、「まずどこから読むか」を決める入り口の部分だと思っています。一覧を上から下まで眺めて、当たりを付けて、開いて、違って、戻る。rinkaku の TUI を作ったのは、この往復を減らして、PR の大枠を素早く掴めるようにしたかったからです。上の例のように、シグネチャの情報だけでかなりのことが読み取れる、というのが使ってみての実感です。

上から読むだけでいい

変更ファイルのツリーは、アルファベット順ではなく、依存関係のなかで「呼び出し元に近いものほど上」に並びます。エントリポイント側が上、そこから呼ばれる基盤側が下、というイメージです。上のスクショで fn run_app がツリー上部に出ていたのはこの並びのおかげで、上から読み下すだけで自然にエントリ側からレビューしていることになります。「どこから読むか」を考える時間が消えるのが、TUI を開く一番の理由です。

なおこの並びが効くのはディレクトリ同士の並びで、その中のファイルはアルファベット順です。全体をアルファベット順に揃えたくなったら o でトグルできます。

各行のバッジは次のとおりです。

  • chg:N: 変更シンボル数
  • api:N: 呼び出し側に影響するシグネチャの変化
  • fan-in:N: 被参照数(テストからの参照は数えません)
  • lines:N: ファイルの行数。閾値を超えると色付きでハイライトされる
  • !: 契約変更と高 fan-in が重なるシンボルに付く

実際の読み方はだいたい次の 2 段階です。

  • 基本はシグネチャを流し読みしてパラパラめくる
  • 黄色い api: や赤い ! が目に留まったら、その行の diff を見る

1 つずつつぶさに読むというより、視界の端で引っ掛かりを生むための信号くらいのつもりです。

薄字も同じ役割で、body だけの変更や畳まれたテスト、削除されたシンボル(打ち消し線付き)は薄い色で表示され、流し読みしやすくなっています。

lines:N が目立っていると「そろそろ分割を指示したほうがいいか、そもそもモジュール境界の切り方がおかしくないか」を検討するトリガーとして使っています。エージェントに書かせていると 1 ファイルにどんどん詰まっていきがちなので、この手のヒントは地味に効きます。

疑問が湧いたら飛ぶ

シグネチャを眺めていると、「これ、他のどこから呼ばれてたっけ」「この引数増えたけど、対応するテストあるんだっけ」と気になる瞬間がちょくちょく出てきます。そのときに使うのが gr(Go to References)と gd(Go to Definition)です。シンボルの上で gr を叩くと参照元一覧の overlay が出ますし、逆向きに定義側へ戻りたいときは gd です1

gr で参照元一覧に飛べる

やっているのは「diff を読む」ではなく、疑問が湧いた瞬間にその答えがある場所へ飛ぶ、という動きです。grep でも PR の Files タブでも再現しにくいところだと思っています。

気づきは書き溜めて、エージェントに投げる

個人的に気に入っている機能の 1 つが annotation です。

気になったところは a を叩いてその場に annotation として書き溜めます(A で一覧が開きます)。「ここの境界値テストが足りない」「この引数名を直したい」といったメモを、輪郭を辿りながらどんどん積んでいく感じです。

rinkaku の annotation 入力オーバーレイ。ファイルとシンボルへの参照が自動で紐づいた状態でメモを書き込める

annotation を書き溜めている画面

annotation は Markdown 形式でクリップボードにまとめて出せるようになっていて、それをそのままコーディングエージェントに貼れば、書き溜めた指摘がそのまま修正指示として渡せます。

AI にコードを書かせている以上、レビューの出口は「人間向けコメント」ではなく「LLM への指示」に寄っていく、というのが最近の感覚です。annotation はその出口を TUI の中で完結させるための機能で、慣れると PR の Files タブに戻ることが減ってきました。行単位で指摘を残したい場合は、w で PR ページをブラウザで開いて、GitHub 上で普通にレビューします。輪郭の把握と大づかみの指摘は TUI、精読と行コメントは Web、という役割分担が、いまのところ一番しっくりきています。

仕組みの話 — なぜ tree-sitter か

実装は Rust で書きました。tree-sitter のバインディングが素直で、シングルバイナリで配りやすいのが選定理由です。

依存解決というと LSP(rust-analyzer や gopls)を思い浮かべますが、rinkaku はあえて tree-sitter を選んでいます。なるべく利用者側の依存を薄くしたかったからです。任意のリポジトリの diff をとりあえず開いてみる、という使い方でプロジェクトのビルドや言語サーバのセットアップを前提にすると、入口が重くなってしまいます。

その代わり、依存を追うのは変更されたシンボルから 1 段だけ、と割り切っています。全部を辿ってしまうと結局大きな塊を読むことになって、「diff を圧縮する」という目的から外れてしまうからです。詳細なレビューをするツールではなく、まず概要を掴んで、気になる箇所やコードスメルの当たりをつけるためのツールとして設計しています。

割り切りの裏返しで、tree-sitter を選んだ以上は同名関数の呼び分けや動的ディスパッチ、マクロ経由の呼び出しなど、型情報がないと解けない依存は取れません。実運用では「name-only + パスの近さで最有力候補に絞り込む」というヒューリスティックでほとんどのケースをカバーできる感触なので、精度が問題になったらそのとき方針を考えようと思っています。

今後の方向性

いま rinkaku が出している信号は、変更シンボル数・シグネチャ変更・fan-in・ファイル行数といった、割と素朴なものです。ただ、AI エージェントに書かせた PR を眺めていると「fan-in が偏りすぎている神ヘルパー」「1 ファイルに責務が寄りすぎている」「シグネチャは変わっていないのに body だけが肥大化していく」といった、いわゆるコードスメルに近いパターンがけっこう見えます。

このあたりを、レビュアーが目視で気付くのではなくツール側でもう少し積極的に指摘してあげられないか、というのが最近考えている方向性です。人間が全 diff を読むのではなく、AI が書いた変更のなかから「そもそも設計として引っかかりのある箇所」を輪郭のレイヤーで先出しできるようになると、AI との分担がまた一段変わるはずだと思っています。

まとめ

私自身は最近、実装が一段落したところで push して draft PR を作り、PR の AI レビューを走らせている裏で rinkaku --pr <番号> を打って自分でも概要を把握する、という並列運用に落ち着いています。ぜひ手元で試して、フィードバックやコントリビューションを頂けると嬉しいです。

AI に任せる範囲が広がるほど、レビュー観点やアーキテクチャ判断といった、人間側で握るところの重みは逆に増していく感触があります。rinkaku もその試行錯誤から出てきた副産物の 1 つです。

We are hiring!

MMA チームでは AI を最大限活用しつつ、アジャイルな開発を高速で回しています。この環境で一緒に手を動かしてくれるエンジニアを募集しています。 ご気軽にカジュアル面談からでも応募をお待ちしております!

エンジニア採用ページはこちら

jobs.m3.com

カジュアル面談もお気軽にどうぞ

jobs.m3.com

インターンも常時募集しています

open.talentio.com


  1. なお現状の gr / gd の飛び先は diff で触られている範囲に限られます(rinkaku が把握しているのは diff の変更シンボルと 1 hop 先までなので)。diff の外側まで辿るのは今後の改善対象です。