Skip to content
Tutorials5 min read

AIコーディングエージェント向けのGitHub PRスタイルのコードレビュー

Potatoでインラインdiffコメント、ファイル単位の品質評価、承認・却下の判定を備えたGitHub PRスタイルのコードレビューアノテーションを設定し、コーディングエージェントの出力を評価する方法です。

Potato Team

コードレビューのアノテーションが必要な理由

コーディングエージェントのベンチマークの多くは、評価を二値に落とし込んでいます。テストが通ったかどうかです。SWE-benchは解決したissueの割合を報告し、HumanEvalはpass@kを報告します。これらはリーダーボードには使えますが、コードの品質を理解する役には立ちません。

エージェントはすべてのテストを通しながら、誰も保守したくないコードを書けます。セキュリティホールのあるコード、遅い経路、コードベースの他の部分と噛み合わないスタイルなどです。人間のレビュアーなら、テストが緑でもそのPRには変更を要求します。人が実際にマージするコードを書くエージェントが欲しいなら、テストを走らせるだけでなくコードそのものをレビューする必要があります。

Potatoのcode_reviewアノテーションスキーマは、GitHubのPRレビュー体験をアノテーションツールに持ち込みます。アノテーターはシンタックスハイライト付きの統一diffを見て、diffの行をクリックしてインラインコメントを付け、いくつかの品質軸でファイルを評価し、approve・request-changes・commentのいずれかの判定を下します。実際のpull requestをレビューするのと同じ流れです。スキーマの完全なリファレンスは、コーディングエージェントアノテーションのドキュメントエージェント評価ガイドを参照してください。

Potatoのコードレビューインターフェースです。インラインdiffコメントとファイル単位の評価が表示されています。

インラインdiffコメントとファイル評価を備えたコードレビューアノテーションインラインdiffコメントとファイル単位の品質評価を備えたPotatoのコードレビューインターフェース


コードレビュースキーマの概要

code_reviewスキーマは3つの層で構成されています。

  1. インラインdiffコメント:アノテーターがdiffの任意の行をクリックして、カテゴリ付きのコメント(bug、style、performance、security、logic、suggestion、question)を付けます
  2. ファイル単位の評価:変更された各ファイルに対して、正確性(1〜5)とコード品質(1〜5)を独立に評価します
  3. 全体の判定:アノテーターが最終判定を下します。approve、request changes、comment onlyのいずれかです

実際のコードレビューをそのままなぞる形なので開発者には馴染みやすく、生成される構造化データはそのままコードレビューモデルの訓練に対応します。


CodingTraceDisplay:diffの描画方法

CodingTraceDisplayコンポーネントは、コーディングエージェントのトレースをツール呼び出しとその出力の連なりとして描画し、ファイル編集を特別に扱います。エージェントがファイルを編集すると、次の要素を含む統一diffが表示されます。

  • 赤い行:削除された行(-が先頭に付きます)
  • 緑の行:追加された行(+が先頭に付きます)
  • グレーの行:コンテキスト行(変更なし)
  • 行番号:旧・新の両方の行番号をガター部分に表示
  • シンタックスハイライト:ファイル拡張子に基づく言語対応のハイライト
  • クリックでコメント:任意の行をクリックすると、その行に紐づくコメントフォームが開きます

diffはエージェントの編集操作から自動的に計算されます。エージェントが検索置換ツールを使った場合、Potatoは変更前後の状態を復元して統一diffを生成します。

1つのトレース内で複数のファイルを編集するエージェント(現実のバグ修正ではよくあることです)の場合、ファイルごとに折りたたみ可能なdiffセクションが用意されます。GitHub PRの「Files changed」タブと同じ形です。

CodingTraceDisplayは、コードの変更を適切なシンタックスハイライト付きで描画します。

diff描画とファイルツリーを備えたコーディングトレース表示シンタックスハイライト付きの統一diffとファイルツリーのサイドバーを描画するCodingTraceDisplay


コメントカテゴリ

アノテーターがdiffの行をクリックしてコメントを追加するとき、カテゴリを1つ選びます。

カテゴリ説明
bugコードに機能的な誤りがある「userがNoneの場合、ここでNullPointerExceptionが発生します」
styleコードスタイルや規約の問題「このプロジェクトの関数名はcamelCaseではなくsnake_caseです」
performanceオレンジ非効率なコード「ループの中でデータベースに問い合わせています。バッチクエリを使ってください」
securityセキュリティ脆弱性「ユーザー入力がサニタイズされずに直接SQLクエリへ渡されています」
logic即座には失敗しないかもしれないロジックの問題「この条件は>ではなく>=にすべきです。境界でオフバイワンになります」
suggestion誤りではなく改善の提案「ここはコンテキストマネージャを使うとリソース管理がすっきりします」
questionグレー確認が必要「このimportはなぜ追加されたのでしょうか。使われていないように見えます」

各コメントには自由記述の本文もあり、アノテーターは実際のPRコメントを書くのと同じように問題点を詳しく説明します。


ファイル単位の評価

各ファイルのdiffをレビューしたあと、アノテーターは2つの軸で評価します。

正確性(1〜5)

  • 1:動作しない、新たなバグを持ち込んでいる
  • 2:部分的に動作するが、大きな問題がある
  • 3:正常系では動作するが、エッジケースを取りこぼしている
  • 4:正しく動作するが、軽微な問題がある
  • 5:完全に正しく、エッジケースも適切に処理している

コード品質(1〜5)

  • 1:保守不能、構造がない
  • 2:品質が低く、スタイルや設計に大きな問題がある
  • 3:許容範囲、基本的な規約には従っている
  • 4:品質が高く、整理されていて読みやすい
  • 5:優れている、イディオムに沿っていて、ドキュメントも十分

判定の選択肢

すべてのファイルをレビューしたあと、アノテーターは3つの判定から1つを選びます。

  • Approve:そのまま、あるいは些細な修正だけでマージできる状態です
  • Request Changes:マージ前に相応の修正が必要です
  • Comment Only:マージの可否を決めずにフィードバックだけを残します

これはGitHubの3つのPRレビュー状態にそのまま対応します。


セットアップ手順

ステップ1:コーディングエージェントのトレースを変換する

コーディングエージェントのトレース形式はさまざまです。ここではよく使われる3つのエージェントの例を挙げます。

Claude Codeから(JSONエクスポート):

bash
python -m potato.trace_converter \
  --input claude_code_sessions/ \
  --output data/code_traces.jsonl \
  --input-format claude_code

Aiderから(チャット履歴):

bash
python -m potato.trace_converter \
  --input aider_logs/ \
  --output data/code_traces.jsonl \
  --input-format aider

SWE-Agentから(トラジェクトリのディレクトリ):

bash
python -m potato.trace_converter \
  --input swe_agent_trajectories/ \
  --output data/code_traces.jsonl \
  --input-format swe_agent_trajectory

コンバータは標準化されたJSONL形式を出力します。各行にはタスク、エージェントのステップ、ファイルのdiffを含むトレースが入ります。

json
{
  "trace_id": "swe_bench_123",
  "task": "Fix the TypeError in django/db/models/query.py when calling .values() on an empty QuerySet",
  "repo": "django/django",
  "base_commit": "a1b2c3d",
  "steps": [
    {
      "type": "thought",
      "content": "Let me look at the QuerySet.values() method to understand the TypeError."
    },
    {
      "type": "action",
      "tool": "search",
      "input": "def values",
      "file": "django/db/models/query.py"
    },
    {
      "type": "observation",
      "content": "Found at line 234: def values(self, *fields):\n    ..."
    },
    {
      "type": "action",
      "tool": "edit",
      "file": "django/db/models/query.py",
      "old_content": "    def values(self, *fields):\n        clone = self._clone()\n        clone.query.set_values(fields)",
      "new_content": "    def values(self, *fields):\n        clone = self._clone()\n        if not fields and not self.query.values_select:\n            return clone\n        clone.query.set_values(fields)"
    },
    {
      "type": "action",
      "tool": "edit",
      "file": "tests/queries/test_values.py",
      "old_content": "class ValuesTests(TestCase):",
      "new_content": "class ValuesTests(TestCase):\n    def test_empty_queryset_values(self):\n        \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n        qs = Author.objects.none()\n        result = list(qs.values())\n        self.assertEqual(result, [])"
    }
  ],
  "files_changed": [
    {
      "path": "django/db/models/query.py",
      "diff": "--- a/django/db/models/query.py\n+++ b/django/db/models/query.py\n@@ -234,6 +234,8 @@\n     def values(self, *fields):\n         clone = self._clone()\n+        if not fields and not self.query.values_select:\n+            return clone\n         clone.query.set_values(fields)"
    },
    {
      "path": "tests/queries/test_values.py",
      "diff": "--- a/tests/queries/test_values.py\n+++ b/tests/queries/test_values.py\n@@ -1,4 +1,10 @@\n class ValuesTests(TestCase):\n+    def test_empty_queryset_values(self):\n+        \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n+        qs = Author.objects.none()\n+        result = list(qs.values())\n+        self.assertEqual(result, [])"
    }
  ]
}

ステップ2:コードレビュースキーマを設定する

config.yamlを作成します。

yaml
annotation_task_name: "Coding Agent Code Review"
 
data_files:
  - "data/code_traces.jsonl"
 
item_properties:
  id_key: "trace_id"
  text_key: "task"
 
# Display coding agent traces with diff rendering
display:
  type: "coding_trace"
  trace_key: "steps"
  diff_key: "files_changed"
  syntax_highlighting: true
  show_line_numbers: true
  collapse_large_diffs: true
  max_uncollapsed_lines: 200
 
annotation_schemes:
  - annotation_type: "code_review"
 
    # Inline comment categories
 
    # File-level ratings
 
    # Overall verdict
 
# Annotator settings
annotator_config:
  allow_back_navigation: true
 
# Output settings
output:
  path: "output/"
  format: "jsonl"

ステップ3:アノテーションサーバーを起動する

bash
potato start config.yaml -p 8000

http://localhost:8000にアクセスします。最初のコーディングエージェントトレースが表示され、タスクの説明、エージェントの推論ステップ、シンタックスハイライト付きのファイルdiffを確認できます。

ステップ4:アノテーターの作業手順

典型的なレビューの流れは次のとおりです。

  1. タスクを読む:エージェントが何を指示されたかを把握します(例:「django/db/models/query.pyのTypeErrorを修正する」)
  2. トレースを確認する:エージェントの推論ステップを追い、そのアプローチを理解します
  3. 各ファイルのdiffをレビューする
    • シンタックスハイライト付きのdiffを読む
    • 任意の行をクリックしてインラインコメントを付ける
    • コメントカテゴリ(bug、style、performanceなど)を選ぶ
    • 問題点を説明するコメント本文を書く
    • 正確性(1〜5)とコード品質(1〜5)でファイルを評価する
  4. 判定を下す:approve、request changes、comment onlyから選びます
  5. 送信する:「Submit」をクリックするか、Ctrl+Enterを押します

キーボードショートカットで作業が速くなります。

ショートカット動作
j / kファイル間を移動
c選択中の行にコメントを開く
1-5現在の評価軸にスコアを設定
a判定をapproveにする
r判定をrequest changesにする
Ctrl+Enterレビューを送信

エクスポート形式

送信された各レビューは、構造化されたJSONオブジェクトになります。

json
{
  "trace_id": "swe_bench_123",
  "annotator": "reviewer_01",
  "timestamp": "2026-03-22T14:32:11Z",
  "review": {
    "inline_comments": [
      {
        "file": "django/db/models/query.py",
        "line": 236,
        "side": "right",
        "category": "logic",
        "body": "This early return skips set_values entirely, but if fields are provided later via .values('name'), the previous empty .values() call will have returned a clone that never went through set_values. Consider checking if this clone is still valid downstream."
      },
      {
        "file": "tests/queries/test_values.py",
        "line": 5,
        "side": "right",
        "category": "suggestion",
        "body": "Consider adding a test case for .values() followed by .values('name') to verify the chaining behavior after your fix."
      }
    ],
    "file_ratings": [
      {
        "file": "django/db/models/query.py",
        "correctness": 3,
        "code_quality": 4
      },
      {
        "file": "tests/queries/test_values.py",
        "correctness": 4,
        "code_quality": 4
      }
    ],
    "verdict": "request_changes"
  }
}

この構造化形式は、コードレビューモデルの訓練にも集計分析にもそのまま使えます。


分析:レビューデータを扱う

レビューの読み込み

python
import json
import pandas as pd
from pathlib import Path
 
reviews = []
for f in Path("output/").glob("*.jsonl"):
    with open(f) as fh:
        for line in fh:
            reviews.append(json.loads(line))
 
print(f"Loaded {len(reviews)} code reviews")

コメントカテゴリの分布

python
from collections import Counter
 
all_comments = []
for rev in reviews:
    for comment in rev["review"]["inline_comments"]:
        all_comments.append(comment)
 
category_counts = Counter(c["category"] for c in all_comments)
print("Comment categories:")
for cat, count in category_counts.most_common():
    print(f"  {cat}: {count}")

ファイル評価の平均

python
ratings = []
for rev in reviews:
    for fr in rev["review"]["file_ratings"]:
        ratings.append(fr)
 
ratings_df = pd.DataFrame(ratings)
print("Average ratings by file:")
print(
    ratings_df.groupby("file")[["correctness", "code_quality"]]
    .mean()
    .round(2)
    .to_string()
)

判定の分布

python
verdict_counts = Counter(rev["review"]["verdict"] for rev in reviews)
total = sum(verdict_counts.values())
print("Verdict distribution:")
for verdict, count in verdict_counts.most_common():
    print(f"  {verdict}: {count} ({count/total*100:.1f}%)")

エージェント別のバグ率

トレースにagentフィールドが含まれていれば、エージェント間でバグ率を比較できます。

python
agent_bugs = {}
for rev in reviews:
    agent = rev.get("agent", "unknown")
    bug_count = sum(
        1 for c in rev["review"]["inline_comments"]
        if c["category"] == "bug"
    )
    if agent not in agent_bugs:
        agent_bugs[agent] = []
    agent_bugs[agent].append(bug_count)
 
print("Average bugs per review by agent:")
for agent, bugs in sorted(agent_bugs.items()):
    print(f"  {agent}: {sum(bugs)/len(bugs):.2f} (n={len(bugs)})")

活用例

コードレビューモデルの訓練

Potatoのコードレビューアノテーションから得られる構造化されたインラインコメント、ファイル評価、判定は、自動コードレビューモデルの訓練データに向いています。各レビューが提供するものは次のとおりです。

  • 特定のdiff行に紐づいた局所的なフィードバック
  • カテゴリ分けされた問題(bugかstyleかperformanceか)
  • 複数の粒度(行、ファイル、全体)での品質シグナル

これはCodeRabbitやGraphiteのAIレビュアーといったツールが使うのと同じデータ形式ですが、LLMから蒸留したものではなく人間の専門家が生成したものです。

SWE-benchでのコーディングエージェント評価

SWE-benchが教えてくれるのは、エージェントがissueを解決したか(テストが通ったか)であって、そのコードがマージできるかどうかではありません。SWE-benchの解答にコードレビューアノテーションを適用すれば、きれいなコードで解決したエージェントと、その場しのぎで解決したエージェントを見分けられます。実際の開発者体験と相関する、より細かいリーダーボードが作れます。

コード品質データセットの構築

多数のトレースにまたがるコードレビューデータを集計すると、AI生成コードによくある品質上の問題を集めたデータセットが作れます。こうしたデータセットの用途は次のとおりです。

  • ありがちなミスを避けるためのコード生成モデルのファインチューニング
  • AI生成コード特有のパターンに向けたリンターの構築
  • 人間のレビュー前にエージェント出力の問題箇所を洗い出す分類器の訓練

まとめ

Potatoのcode_reviewスキーマは、GitHubのPRレビューの作業手順をエージェント評価の中に持ち込みます。集まったインラインコメント、ファイル評価、判定は構造化されたコード品質データであり、合否だけのテスト結果よりずっと多くを教えてくれます。コードレビューモデルを訓練するにせよ、SWE-benchのきれいな解答とその場しのぎの解答を切り分けるにせよ、エージェントの品質基準を作るにせよ、必要になるのはこのデータです。