$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Claude CodeからPull Requestのレビュー操作を便利に行うClaude Skillsを作った

Claude CodeやClaude Code Actionを用いて、AIに自律的にPull Requestのレビューを行なってもらうとき、いくつかの課題があった。その解決のためにPull Requestのレビュー操作に特化したgithub-pr-review-operationというClaude Skillsを作ったので紹介します。

課題

次の3つの課題があった。

  • インラインコメントを付ける時に、コメントする行を間違える
    • Claude Code ActionにはインラインコメントをつけるMCPが同梱されているが、ある行に対する指摘内容を別の行にコメントを付けることが多かった
  • そのPull Requestに過去ついたコメントを考慮してと指定しても、うまくコメント一覧を取得できない
    • 通常のコメントは取得できてもインラインコメントは取得せずに進めてしまうなど
  • そのPull Requestについたコメントに対してうまくReplyを付けることができない
    • 特定コメントに対して、別のインラインコメントで返信していた

これらの課題は全てghの操作をうまく行うだけで解決できるのだが、LLMの確率論的な特性上、うまくいくこともあればいかないこともあった。

Claude Skillsを使って操作をClaude Codeに教える

先ほどいったとおり、課題を解決するにはClaude Codeに対してghの操作を教えてあげれば良さそうだ。その目的にはClaude Skillsが使えそうなので、Pull Requestのレビュー操作に特化したClaude Skillsを自作した。

Skillsを作るときはClaudeの公式でSkill Generatorというデフォルトで組み込まれているスキルを活用すると良い。今回はClaude Desktopで以下プロンプトを入れてベース部分を作った後、使ってみながら色々な調整を加えていった。

GitHubのPullRequestのレビューのため、それに必要な操作をスキル化したい。必要な操作は

- Pull Requestのtitleやdescriptionを取得する
- Pull Requestのファイル変更を取得する
- Pull Requestのコメントを全て取得する
- Pull Requestにコメントする
- Pull Requestのline指定でinline commentする
- Pull Requestの特定のコメントへ返信する

最終的にすごくシンプルにSKILL.mdだけの構成にした。これを使うだけで、上記課題はほぼ100%解決するようになった。便利。

SKILL.md:

---
name: github-pr-review-operation
description: GitHub Pull Requestのレビュー操作を行うスキル。PR情報取得、差分確認、コメント取得・投稿、インラインコメント、コメント返信をghコマンドで実行する。PRレビュー、コードレビュー、PR操作が必要な時に使用。
---

# GitHub PR Review Operation

GitHub CLI (`gh`) を使ったPRレビュー操作。

## 前提条件

- `gh` インストール済み
- `gh auth login` で認証済み

## PR URLのパース

PR URL `https://github.com/OWNER/REPO/pull/NUMBER` から以下を抽出して使用:
- `OWNER`: リポジトリオーナー
- `REPO`: リポジトリ名
- `NUMBER`: PR番号

## 操作一覧

### 1. PR情報取得

```bash
gh pr view NUMBER --repo OWNER/REPO --json title,body,author,state,baseRefName,headRefName,url
```

### 2. 差分取得(行番号付き)

```bash
gh pr diff NUMBER --repo OWNER/REPO | awk '
/^@@/ {
  match($0, /-([0-9]+)/, old)
  match($0, /\+([0-9]+)/, new)
  old_line = old[1]
  new_line = new[1]
  print $0
  next
}
/^-/ { printf "L%-4d     | %s\n", old_line++, $0; next }
/^\+/ { printf "     R%-4d| %s\n", new_line++, $0; next }
/^ / { printf "L%-4d R%-4d| %s\n", old_line++, new_line++, $0; next }
{ print }
'
```

出力例:
```
@@ -46,15 +46,25 @@ jobs:
L46   R46  |            prompt: |
L49       | -            (削除行)
     R49  | +            (追加行)
L50   R50  |              # レビューガイドライン
```

- `L数字`: LEFT(base)側の行番号 → インラインコメントで`side=LEFT`に使用
- `R数字`: RIGHT(head)側の行番号 → インラインコメントで`side=RIGHT`に使用

### 3. コメント取得

Issue Comments(PR全体へのコメント):
```bash
gh api repos/OWNER/REPO/issues/NUMBER/comments --jq '.[] | {id, user: .user.login, created_at, body}'
```

Review Comments(コード行へのコメント):
```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments --jq '.[] | {id, user: .user.login, path, line, created_at, body, in_reply_to_id}'
```

### 4. PRにコメント

```bash
gh pr comment NUMBER --repo OWNER/REPO --body "コメント内容"
```

### 5. インラインコメント(コード行指定)

まずhead commit SHAを取得:
```bash
gh api repos/OWNER/REPO/pulls/NUMBER --jq '.head.sha'
```

単一行コメント:
```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments \
  --method POST \
  -f body="コメント内容" \
  -f commit_id="COMMIT_SHA" \
  -f path="src/example.py" \
  -F line=15 \
  -f side=RIGHT
```

複数行コメント(10〜15行目):
```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments \
  --method POST \
  -f body="コメント内容" \
  -f commit_id="COMMIT_SHA" \
  -f path="src/example.py" \
  -F line=15 \
  -f side=RIGHT \
  -F start_line=10 \
  -f start_side=RIGHT
```

**注意点:**
- `-F` (大文字): 数値パラメータ(`line`, `start_line`)に使用。`-f`だと文字列になりエラーになる
- `side`: `RIGHT`(追加行)または `LEFT`(削除行)

### 6. コメントへ返信

```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments/COMMENT_ID/replies \
  --method POST \
  -f body="返信内容"
```

`COMMENT_ID`はコメント取得で得た`id`を使用。

工夫ポイント: インラインコメントを正しい行に付けられるように

インラインコメントを正しい行へ付けられるようにするのがなかなか難しく、Skillsの自動生成後に一番調整を行なった。

なぜインラインコメントが正しい行に付けられないか説明する。gh pr diffコマンドで差分を取得すると、git diffの形式で差分が取得できる。たとえば https://github.com/shibayu36/slack-explorer-mcp/pull/20 から取得して抜粋すると次のようになる。

diff --git a/main.go b/main.go
index 3029a7a..9383209 100644
--- a/main.go
+++ b/main.go
@@ -3,6 +3,7 @@ package main
 import (
        "context"
        "log/slog"
+       "net/http"
        "os"

        "github.com/mark3labs/mcp-go/mcp"
@@ -163,17 +164,55 @@ func main() {
                handler.SearchUsersByName,
        )

-       if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
-               ctx = WithSlackTokenFromEnv(ctx)
+       transport := os.Getenv("TRANSPORT")
+       if transport == "" {
+               transport = "stdio"
+       }
+
+       switch transport {
+       case "stdio":
+               if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
+                       ctx = WithSlackTokenFromEnv(ctx)

-               // Add session ID from ClientSession
-               if session := server.ClientSessionFromContext(ctx); session != nil {
-                       ctx = WithSessionID(ctx, SessionID(session.SessionID()))

さらにインラインコメントを付けるときは行番号 + side(LEFT=削除行かRIGHT=追加行)を指定する必要がある。

gh api repos/OWNER/REPO/pulls/NUMBER/comments \
  --method POST \
  -f body="コメント内容" \
  -f commit_id="COMMIT_SHA" \
  -f path="src/example.py" \
  -F line=15 \
  -f side=RIGHT

この2つのAPIインターフェースのため、diffを取得した後にインラインコメントを付けるにはgit diffの@@ -163,17 +164,55 @@ func main() { のような行から行番号を抽出し、さらに行頭の-+を考慮して、sideのLEFT=削除行かRIGHT=追加行のどちらかを決定し、その二つの組み合わせでコメントする必要がある。この抽出操作がLLMにとって難しく(Opus-4.5を使っても同様だった)、インラインコメントを別の行に付けてしまうという課題が生じていた。

ただ、よく考えるとGitHubのWeb上のFiles changedのページの情報を見れば、以下のようにインラインコメントを付けるための情報はすぐにわかる。

じゃあCLIから同じような情報を作ればいいじゃんということで作ったコマンドが、SKILL.mdの2. 差分取得(行番号付き)の部分。

gh pr diff NUMBER --repo OWNER/REPO | awk '
/^@@/ {
  match($0, /-([0-9]+)/, old)
  match($0, /\+([0-9]+)/, new)
  old_line = old[1]
  new_line = new[1]
  print $0
  next
}
/^-/ { printf "L%-4d     | %s\n", old_line++, $0; next }
/^\+/ { printf "     R%-4d| %s\n", new_line++, $0; next }
/^ / { printf "L%-4d R%-4d| %s\n", old_line++, new_line++, $0; next }
{ print }
'

これを使って https://github.com/shibayu36/slack-explorer-mcp/pull/20 のdiffを取得すると、Web上のFiles changedのページと同じような情報がテキストで取得できる。

@@ -163,17 +164,55 @@ func main() {
L163  R164 |            handler.SearchUsersByName,
L164  R165 |    )
L165  R166 |
L166      | -   if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
L167      | -           ctx = WithSlackTokenFromEnv(ctx)
     R167 | +   transport := os.Getenv("TRANSPORT")
     R168 | +   if transport == "" {
     R169 | +           transport = "stdio"
     R170 | +   }
     R171 | +
     R172 | +   switch transport {
     R173 | +   case "stdio":
     R174 | +           if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
     R175 | +                   ctx = WithSlackTokenFromEnv(ctx)
L168  R176 |
L169      | -           // Add session ID from ClientSession
L170      | -           if session := server.ClientSessionFromContext(ctx); session != nil {
L171      | -                   ctx = WithSessionID(ctx, SessionID(session.SessionID()))

あとは左に出ているL163 R164 | のような行から、インラインコメントを付ける情報を簡単にゲットできるため、Claude Codeは正しい位置にコメントが付けられるようになった。

まとめ

今回はClaude CodeやClaude Code Actionを用いて、AIに自律的にPull Requestのレビューを行なってもらうときに便利に使えるClaude Skillsの紹介をした。.claude/skills/github-pr-review-operation/配下に上記したSKILL.mdを配置するだけで使えるのでご利用ください。

良いドキュメントは、伝えたいことを、最も削ぎ落とした文章で伝える

AIが進歩して、ドキュメントやブログ記事などを非常に作りやすくなった。一方でAIは文章の足し算に偏重しているため、伝えたいことに対して非常に多くの文章を吐き出す。その結果、何が伝えたいか分からないドキュメントや記事が増えていると感じる。

こう感じたとき、僕は自分が好きな「圕の大魔術師」の、とあるセリフを思い出す。5巻 p141の「本が優れているかどうかの判断に厚みは直接的な関係がないと思います。言葉を削ぎ落とし簡潔に真実を述べた冊子のような姿もありうれば、いたずらに文字だけ増やした辞典のような姿もありうるからです」というセリフだ。

圕の大魔術師 5巻 p141から引用

AIによって文章を作りやすくなった時代だからこそ、「何を伝えたいか」を徹底的に意識し、「伝えたいこと以外を徹底的に引き算」した文章を作るように心がけたいと思う。

「Goで作る自作コーディングエージェント nebula 開発入門」が良かった

エージェント実装の理解を深めたいなと思い、「Goで作る自作コーディングエージェント nebula 開発入門」を読みながらコーディングエージェント実装の写経をしてみた。学びが多く、非常に良かった。

zenn.dev

実際に作ったコードは https://github.com/shibayu36/nebula にある。資料と違って、ちょっとだけ設計を変えてみたり、セッション一覧表示を作ったり、編集時のdiff表示を作ってみたりなどもしてみた。

この実践では、エージェントを作る時の基本構成や設計パターンについて概要を学ぶことができた。たとえばツールコール、システムプロンプト設計、メモリ機能設計など。この辺りはAIエージェントを作る上でコーディングエージェント作り以外にも役立つ知識なので、手を動かして深めに理解できたのは良かった。

Goを普段書いていて、AIエージェント作りに挑戦してみたい人にとって、非常に参考になる資料だったと思う。全部作ったとしても10時間程度で終わるように感じたので、おすすめ資料でした。