SKILL.md
Review PR
GitHub PR に対して下記「エージェント一覧」のレビュアーを trigger に従って並列起動し、統合サマリーを提示する。承認があれば Pending Review としてインラインコメントを投稿する。
引数解析 → 差分取得 → トリガー評価 → エージェント並列 → 指摘の集約 → 統合サマリー → 確認 → Pending Review 投稿
エージェント一覧
「どのエージェントがどの条件で起動するか」の唯一の所在。観点を追加・廃止するときはこの表に行を足す/消すだけ。skill 本文の他の箇所にはエージェント名・トリガー条件をハードコードしない。
| エージェント名 | trigger | 一次責任 | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
code-reviewer |
always |
品質・設計・可読性・パフォーマンス・テスト | |||||||||||||||||
security-reviewer |
always |
セキュリティ脆弱性 (OWASP Top 10 等。XSS / SQL injection 等の一次責任はここ) | |||||||||||||||||
typescript-reviewer |
extensions=.ts, .tsx, .js, .jsx, .mjs, .cjs |
型安全性・非同期・JS/TS イディオム (any の濫用等の一次責任はここ) |
|||||||||||||||||
postgres-reviewer |
`content=sql\ | migrat\ | schema\ | prisma\ | drizzle\ | typeorm\ | sequelize\ | knex\ | sqlalchemy\ | active_?record\ | postgres\ | supabase\ | \brls\b\ | row.?level.?security\ | create (table\ | policy\ | index)\ | alter table` | Postgres 設計・クエリ・インデックス・RLS・接続管理 (生 SQL / ORM DML / Markdown DB 仕様。DB に関する記述がなければ即終了) |
meta-reviewer |
always |
メタ認知: 問題設定・前提・構造 (症状対処になっていないか / 上流に軽い解はないか / そもそもやるべきか)。指摘は scope: PR の PR-level ブロックのみで、行単位指摘はしない |
trigger 列の値:
always— 無条件で起動paths=<glob...>(省略可で; excludepaths=<glob...>) —pathsのいずれかが差分ファイルにマッチし、excludepathsにマッチしないとき起動extensions=<.ext...>— 差分ファイルの拡張子のいずれかが該当するとき起動content=<regex>—gh pr diffの生出力 (ファイルパス含む) に regex が 大文字小文字無視 でマッチするとき起動。diff 全文入りプロンプトを「即終了」のためだけに送るトークン浪費を防ぐ起動前ゲート。境界が曖昧なら regex を緩めに書く (誤起動 1 体分のコストは取りこぼしより安い)
新しい観点の追加 = 既存エージェント (例: @.claude/agents/code-reviewer.md) を手本に .claude/agents/<観点>-reviewer.md を作り、この表に 1 行追加する。
引数
$ARGUMENTS
優先順:
- PR番号 (
^\d+$/^#\d+$):gh pr diff <番号> - PR URL (
github.comを含む): URL から番号抽出 →gh pr diff - 引数なし:
gh pr view --json number --jq '.number'で現ブランチから自動検出
手順
Phase 1: 情報収集
gh pr view <number> --json number,title,body,baseRefName,headRefName,files
gh pr diff <number>
gh repo view --json nameWithOwner --jq '.nameWithOwner'
gh api repos/{owner}/{repo}/pulls/{number}/comments \
--jq '[.[] | {path, line, side, body, user: .user.login}]'
gh api repos/{owner}/{repo}/pulls/{number}/reviews \
--jq '[.[] | {state, body, user: .user.login}]'
comments / reviews は --jq で必ず絞る。生 JSON は diff_hunk・各種 URL・user オブジェクト・reactions 等が大半を占め、重複指摘の回避に必要なのは上記フィールドだけ。この出力は Phase 2 で全エージェントに複製されるため、絞らないとエージェント数ぶんトークンを浪費する。
差分が空なら「レビュー対象の差分がありません」と報告して終了する。
.files[].path を控える (Phase 2 のトリガー評価で使う)。
絶対行番号の取り方
各 reviewer agent は指摘ごとに 元ファイルの絶対行番号 (= Phase 5 のインライン投稿に使う line) を返す必要がある。diff だけからはハンクヘッダの相対位置しか分からないので、agent が以下を組み合わせる:
- 追加/変更行 (
side: RIGHT): ハンクヘッダ@@ -X,Y +A,B @@のAを起点に、ハンク内のコンテキスト行 (開始) と追加行 (+開始) を 1 つカウントするごとに 1 ずつ加算する。削除行 (-開始) はカウントしない - 削除行 (
side: LEFT): 同じ要領でXを起点に、コンテキスト行と削除行をカウントし、追加行はカウントしない - 取れないケース: ハンクが大きすぎて起点が確定できないときや、生成コード等で行番号自体が不安定なときは、
lineの代わりに<関数名>フォールバックで返す (集約側でインライン投稿対象から除外される)
skill が agent prompt に diff 全文を渡す際は、上記の数え方が再現できるよう gh pr diff <number> の生出力 (ハンクヘッダ込み) を整形せずに埋め込む。
Phase 2: トリガー評価とエージェント並列実行
「エージェント一覧」各行の trigger を評価する — paths= / extensions= は .files[].path に対して、content= は gh pr diff の生出力に対して。起動対象が決まったら、同一メッセージ内の独立した Agent ツール呼び出しで並列起動する。各エージェントへ渡す prompt は次の節構成にする:
## PR メタ
<タイトル / 本文 / ベース・ヘッドブランチ名>
## 差分
<gh pr diff の生出力をハンクヘッダ込みで貼る>
## 既存コメント
<Phase 1 で --jq 絞り込み済みの comments / reviews 出力>
## 出力フォーマット
<@.claude/skills/review-pr/output-format.md の本文を丸ごと貼る>
## 出力フォーマット 節は全 reviewer 共通の構造化スキーマ・量のコントロール・本文トーンの単一情報源で、agent 定義側には記載していない。毎回貼ること。
観点は各エージェントの「一次責任」で大部分は分離されるが、ボーダーラインケースで複数エージェントが同箇所を指摘することはある。その場合は Phase 3 でマージする。
いずれかが失敗しても残りで続行し、失敗したエージェント名を統合サマリーに明記する。
Phase 3: 指摘の集約
責務: エージェント横断で Findings を正規化・重複検出・マージし、後続フェーズが使う中間データ構造 (統合 Finding リスト) を作る。表示整形や Overview/Key Changes の生成は Phase 4、副作用を伴う投稿は Phase 5 で行う。
各エージェントは Findings (構造化指摘ブロックのリスト) を返す。各ブロックには priority / file / line / side / タイトル / 本文段落 / (任意の) 修正案 が含まれる (詳細は各 agent 定義の「出力フォーマット」節)。
各 agent は自分の責任範囲で「出した方が安全」に倒れがちなので、集約は重複除去だけでなく 全体観での最終フィルタ も担う。
集約手順
- 正規化 — 各 Finding を
{priority, file, line, side, title, body, suggestion?, source}の内部表現に揃える。sourceには起動したエージェント名 (code-reviewer等) を入れる - 重複検出 — 以下のいずれかに該当する Finding 群を 1 クラスタにまとめる:
- file + line が完全一致 (ただし根本原因が完全に独立しているとき — 例: 同じ行に偶然「セキュリティ脆弱性」と「命名規約違反」が並んだ場合 — はクラスタ化せず別 Finding として残す。判断基準は 両指摘の修正が互いに依存しないか = 片方を直しても他方が残るか) - file が一致し、line が ±3 行以内かつ同一の根本原因を指している (タイトル/本文から判断) - file が一致し、片方が <関数名> フォールバックでもう片方が同関数内の絶対行番号
- マージ — クラスタ内で 1 件の統合 Finding にまとめる:
- priority: 最も高いもの (critical > warning > suggestion) を採用 - source: 全エージェント名を併記 (例: security-reviewer, typescript-reviewer) - title: priority 高の Finding の title を主軸にし、低 priority 側に固有情報があれば括弧書きで補足する - body: priority 高 → 低の順で段落を並べる。両方に固有情報がある場合のみ統合し、片方が冗長な再説明なら捨てる - suggestion: priority 高の suggestion を採用。同 priority で両方ある場合は「指摘行をそのまま単純置換できる方」を優先 (Suggested Changes に変換できるため) - line / side: 絶対行番号を持つ方を優先 (フォールバックより具体的)
- 最終フィルタ (集約後ゲート) — マージ済みの統合 Finding ごとに、親が以下 4 テストを順に当てる。落ちた Finding は 観察事項 節に降格し、Phase 5 の投稿候補から外す。sub-agent のフィルタを信用せず親が必ず実施 (省略不可)。全件落ちて「指摘なし」も正常な結果。
1. 実害シナリオの一文テスト — 放置したら何が起きるかを親が 1 文で言えるか。「念のため」「一貫性のため」「将来〜」「理論上」しか言えないなら落とす。 - 条件付きはOK: 「〈前提〉なら〈結果〉」と書けるなら前提を明示して残す (例: 攻撃者到達可能な内部 API での SQL injection) - priority と実害が乖離する場合: 削除前に priority を再校正 (critical→warning→suggestion) し再適用。それでも実害が言えなければ落とす 2. 作者反応の一文テスト — PR 作者がこれを見て手を動かすか。「好みの問題」しか言えない suggestion (可読性の好み・スタイル違いのみ) は落とす 3. 実コード一次資料テスト — 根拠が「コードを読めば崩れる思い込み」になっていないか (例: 「toHaveBeenCalledWith で undefined が常に成功」は実は厳密一致で失敗する/「emit が例外を投げる」が実はハンドラで握っている)。引っかかったらコード片を読み直して判断。保留にしない 4. 本 PR 差分内テスト — 修正対象行が本 PR の追加/変更行に含まれているか。差分外への指摘は正しくても投稿対象外、「別 PR で対応」として観察事項に降格
- 集計 — 起動したエージェントごとに最終フィルタ後の Findings 件数を数える (PR-level Finding も含む)。マージ済みクラスタは関与した各エージェント列に 1 件ずつ計上 (合計はクラスタ数 = 列の単純和ではない)。降格分は集計に入れない (観察事項節には残る)
PR-level Finding の扱い
scope: PR の Finding (行に紐付かない、問題設定・前提・構造への指摘) は行単位の集約とは別扱いにする:
- 重複検出: 行単位のクラスタ化 (手順 2〜3) の対象外。PR-level 同士で同一の根本原因を指すものだけマージする
- 最終フィルタ: テスト 1〜3 は同様に適用する。テスト 4 (本 PR 差分内テスト) は免除 — PR 全体への指摘であり差分行を持たないため。降格時の観察事項ではファイル列を
PR 全体と書く - 後続フェーズ: フィルタ通過分は統合 Finding リストとは別の「前提・構造への問い」リストとして Phase 4 に渡す。Phase 5 のインライン投稿候補には含めない (行番号を持たないため投稿不可)
本フェーズの出力 (フィルタ後の統合 Finding リスト + 観察事項リスト + 集計) は Phase 4 と Phase 5 の入力として再利用される。
Phase 4: 統合サマリーの提示
責務: Phase 3 の集約結果と、親が diff から生成する Overview / Key Changes を組み合わせて、ユーザー向けの統合サマリーを表示する。
Overview
Summary 1 文(ビジネス/プロダクト背景を含む平易な表現)と Type/Scope/Impact/Size の表を記載する。レビュアーが 5 秒で PR を理解できることを狙う。親が PR タイトル・本文・diff サマリから生成する (エージェント出力は使わない)。
Key Changes
ファイルごとに読み物として解説する。レビュアーが差分を開く前に全体像を把握できるようにする。親が diff から生成する (エージェント出力は使わない)。
- 読み順は変更の性質に合わせる:
- 実装コード中心: データ構造/ドメインモデル → コアロジック → 統合/オーケストレーション → UI → テスト - ドキュメントのみ: 意思決定 (ADR) → 概要 (README) → データ → 振る舞い (API) → UI (画面)
- 各ファイルは
#### N.見出しブロック (Markdown のナンバードリストは使わない)
- 見出し: #### N. + 太字バッククォートのファイルパス + (new)/(modified)/(deleted)/(renamed) - 本文: (a) アーキテクチャ上の役割、(b) 本 PR での変更と理由、(c) 前後ファイルとの繋がり、(d) 非自明な設計判断・トレードオフ - 重要なコードスニペットはインラインコメント付きで引用し、Claude Code でのレンダリングのため > ブロッククォートで囲む
- プロジェクト固有の用語・略語は初出時に短く補足する
Findings
Phase 3 (最終フィルタ後) の統合 Finding リストをテーブル形式で出す (箇条書き不可)。指摘がない場合は本文に No findings. と書く (空テーブルは描画しない、セクション自体は省略しない)。全件降格で空になった場合も同じ扱いで、降格件数を併記するときは No findings. (X 件が観察事項に降格) の形。
各行に含める列:
- # (連番、Phase 5 で投稿対象を選ぶ番号と一致)
- 優先度 (
priorityを絵文字付きで表示: 🔴 Critical / 🟡 Warning / 🟢 Suggestion) - 出典 (
source。複数エージェント由来は併記) - ファイル:行 (絶対行番号がなければ
file (関数名)フォールバック) - 問題 (タイトル + 本文から要約)
- 推奨対応 (具体的な修正案)
前提・構造への問い (省略可)
PR-level Finding (フィルタ通過分) がある場合のみ Findings 節の直後に出す (0 件なら見出しごと省略)。1 件ずつ {絵文字} {タイトル} + 本文の形で列挙する (問いの文章が主体でテーブルに収まらないため、Findings と違い箇条書きで良い)。インライン投稿の対象外であることを節冒頭に 1 行明記する。
観察事項 (省略可)
降格した Finding がある場合のみ前節 (なければ Findings 節) の直後に出す (0 件なら見出しごと省略)。Findings と同じ列に 降格理由 列 (どのテストで落ちたか + 1 行根拠。例: 「テスト 1: 実害が条件付きでも書けない」「テスト 4: 差分外 — 別 PR で対応」) を末尾追加。Phase 5 のインライン投稿対象には含めない。
集計テーブル
- 行は
🔴 Critical/🟡 Warning/🟢 Suggestionの 3 行のみ (「計」行なし) - 列は Phase 2 のトリガー評価で起動を決定したエージェント分のみ +
合計(トリガーで除外したエージェントの列は出さない)
- Findings 0 件のエージェントも列を残す (「起動して 0 件」と「起動しなかった」は別) - postgres-reviewer が DB 記述なしで即終了した場合も「起動して 0 件」扱いで列を残す
- マージ済みクラスタは各出典列に 1 件ずつ計上し、
合計はクラスタ数 (列の単純和ではない) - 全件降格時も集計テーブルは出す (全セル 0)。「起動し指摘を返したが親フィルタで全落ち」を可視化するため
Phase 5: Pending Review 投稿(オプション)
統合サマリー提示後、ユーザーに投稿対象の指摘番号を尋ねる (例: 1,3,5 / all / skip)。
skip→ 表示のみで終了- それ以外 → 選ばれた指摘を Pending Review のインラインコメントとして投稿
- submit はユーザーに委ねる (Pending 状態のまま残す)
番号を尋ねる際、フォールバック指摘 (<関数名> 形式で行番号未確定) はインライン投稿不可なので、その番号を (投稿不可: 行番号未確定) と明示する。例: 投稿可能: 1,2,4 / 投稿不可: 3 (行番号未確定のため Findings テーブルにのみ残す)。
指摘 → コメントへのマッピング
指摘テーブル 1 行 = 1 インラインコメント。
path← ファイル:行 のファイル部分line← 元ファイルの絶対行番号 (diff ハンク内の相対位置ではない)
- side: "RIGHT" のとき PR 適用後 (head) の絶対行番号、side: "LEFT" のとき PR 適用前 (base) の絶対行番号 - 絶対行番号が取れない指摘 (file (function_name) フォールバック) はインライン投稿不可。選択肢から除外し、Phase 4 の Findings テーブルには残す
side← 追加/変更行はRIGHT、削除行はLEFTbody← エージェントが返した タイトル + 本文段落 をそのまま使う。整形しない:
``` {絵文字} {一文サマリ}
{本文: 自然な文章}
{必要なら修正案コードブロック} `` - タイトル行 (絵文字 + 一文サマリ) は必ず先頭に残す。インラインコメント先頭の「何の指摘か」を 1 行で伝える目印で、レビュイーがコメント一覧を流し読みするときに最重要となる。本文段落だけ転記してタイトルが落ちる事故を起こさない - 優先度ラベル (Critical` 等) は冒頭の絵文字で表現済みなのでコメント本文では繰り返さない。指摘テーブル (Phase 4) で優先度・出典は別途担保される
- 修正案は可能な限り Suggested Changes として投稿する (GitHub の ```
`suggestion``` ブロック)。エージェントが返した修正案コードブロックをこの形式に変換する:
- 変換可能な条件: 修正が 指摘行 (または範囲) の単純置換で表せる。中身は置換後の最終形そのもの (前後の文脈・コメント・余分な空行は含めない) - GitHub 側で line (単一行) または startline + line (範囲) に対する完全置換として解釈されるため、コメントの line/side がそのまま置換範囲になる。エージェントが範囲指摘 (startline を持つ) を返した場合はそれを使う - 通常コードブロックのまま残す ケース: 別ファイルへの修正、import 追加、構造説明、複数の独立変更を含むなど、単純置換で表せないもの。エージェントが既に通常ブロック (``` `ts ``` 等) で返している場合は変換せずにそのまま投稿する
手順
1. 既存 Pending Review を確認 (自分が作成したもののみ対象):
ME=$(gh api user --jq .login)
gh api repos/{owner}/{repo}/pulls/{PR番号}/reviews \
--jq ".[] | select(.state == \"PENDING\") | select(.user.login == \"$ME\") | {id, state, user: .user.login}"
2a. Pending Review なし → REST API で新規作成
event フィールドを省略すると pending 状態になる (event: "PENDING" を明示すると 422):
cat <<'PAYLOAD' | gh api repos/{owner}/{repo}/pulls/{PR番号}/reviews --method POST --input -
{
"comments": [
{
"path": "src/example.ts",
"line": 10,
"side": "RIGHT",
"body": "🔴 **Critical**: SQL injection via unsanitized input\n\nUse parameterized queries."
}
]
}
PAYLOAD
2b. Pending Review あり → GraphQL でコメント追加 (REST では既存 pending にコメント追加不可)
Node ID 取得 → コメント追加:
gh api graphql -f query="
{
repository(owner: \"{owner}\", name: \"{repo}\") {
pullRequest(number: {PR番号}) {
reviews(states: PENDING, first: 20) {
nodes { id state author { login } }
}
}
}
}" --jq ".data.repository.pullRequest.reviews.nodes[] | select(.author.login == \"$ME\")"
cat <<'GQL' | gh api graphql --input -
{
"query": "mutation($input: AddPullRequestReviewThreadInput!) { addPullRequestReviewThread(input: $input) { thread { id comments(first: 1) { nodes { id body } } } } }",
"variables": {
"input": {
"pullRequestReviewId": "PRR_kwDOxxxxxxx",
"path": "src/example.ts",
"line": 10,
"side": "RIGHT",
"body": "コメント本文"
}
}
}
GQL
エラーハンドリング
| シナリオ | 対応 |
|---|---|
| いずれかのエージェントが失敗 | 残りの結果で続行し、失敗したエージェントを明記する |
| Pending Review 作成失敗 | エラー内容を表示し、統合サマリーはユーザーに残す |
出力フォーマット
````
PR レビューサマリー
概要
パスワードリセット時にリセットトークンの有効期限を検証していなかったため、期限切れのトークンでもリセットが成功してしまう不具合を修正する。
| 種別 | バグ修正 |
| スコープ | 認証機能 — パスワードリセットフロー |
| 影響 | 期限切れのリセットリンクが、これまで通ってしまっていたのを正しくエラーとして返すようになる |
| 規模 | 3 ファイル変更、+45 / -12 行 |
主な変更
1. src/errors.ts (新規)
ここから読む。認証フローの失敗モードを区別するためのカスタムエラークラスとして TokenExpiredError を導入。ミドルウェア (#3) で個別捕捉して汎用 500 ではなく 401 を返せるようにする。
```ts
// src/errors.ts:1-6
export class TokenExpiredError extends Error {
constructor(message = "Reset token has expired") {
super(message);
}
}
```
auth.ts (#2) と middleware.ts (#3) の両方から import される。
2. src/auth.ts (変更)
(同様の解説 + コードスニペット引用)
3. src/middleware.ts (変更)
(同様の解説 + コードスニペット引用)
指摘事項
| # | 優先度 | 出典 | ファイル | 問題 | 推奨対応 |
|---|---|---|---|---|---|
| 1 | 🔴 Critical | security-reviewer, typescript-reviewer | src/auth.ts:42 | サニタイズされていない入力による SQL インジェクション | パラメータ化クエリを使用する |
| 2 | 🟡 Warning | typescript-reviewer | src/api.ts:15 | 非同期呼び出しで未処理の Promise rejection | await + try-catch でエラーを伝播させる |
| 3 | 🟢 Suggestion | code-reviewer | src/utils.ts:8 | ロジックの重複 | 共通ヘルパーに抽出する |
出典列には冒頭テーブルの エージェント名 をそのまま入れる (上記は例示)。
前提・構造への問い
PR-level Finding (フィルタ通過分) のみ載せる (0 件なら本節ごと省略)。インライン投稿の対象外。
- 🟡 リセットトークンの再発行フローが Description に見当たらない — 期限切れトークンをエラーにする対処 (症状) は妥当だが、期限切れに至るユーザーが多いなら有効期限の設定値そのもの (上流) を見直す選択肢もある。設定値の根拠は検討済みか
観察事項
降格した Finding のみ載せる (0 件なら本節ごと省略)。
| # | 優先度 | 出典 | ファイル | 問題 | 降格理由 |
|---|---|---|---|---|---|
| - | 🟢 Suggestion | code-reviewer | src/utils.ts:8 | ロジックの重複 | テスト 4: 差分外 — 別 PR で対応 |
集計
起動したエージェント分の列のみ出す (例: TS/JS 変更がない PR では typescript-reviewer 列を省略する)。
| 優先度 | code-reviewer | security-reviewer | typescript-reviewer | 合計 |
|---|---|---|---|---|
| 🔴 Critical | 0 | 1 | 1 | 1 |
| 🟡 Warning | 0 | 0 | 1 | 1 |
| 🟢 Suggestion | 1 | 0 | 0 | 1 |
````