TL;DR
- 過去PRのレビューコメントを集約 → チェックリスト(Markdown)を自動更新
- 更新は“自動コミット”ではなく新しいPRとして出す(=人が介在してブロック&議論できる)
- そのチェックリストをAIレビュー時に一緒に読み込ませることで、チームのレビュー文化をAIにも継承できる
- 既存の
doc/rules/pr_review_checklist.mdを前提に、GitHub Actionsでコメント→チェックリスト→PRの流れを作った
背景
AIで“書く”のは速くなったのに、レビューはむしろ重くなる——
- PR数が増える/差分が大きくなる
- 「AIのコードこそ丁寧に見ないと」という心理
結果として読む時間が支配的になりがちです。
そこで、読む前に、読むべき観点を共通化するために、レビューコメントからチェックリストを蒸留し、それをPRとしてチームに回す運用を自動化しました。
何が嬉しい?(設計思想)
1) 文化の継承(人 → AI/人)
- 過去のレビュワーの指摘=そのチームの“文化”。
- それをチェックリストに落とし、AIレビュー時に同時投入すれば、AIも同じ観点で評価してくれる。
2) Human-in-the-loop を維持
- “自動で main に書き込む”のではなく、PRとして出す。
- 人が介在して「今回は見送る/追加する」などブロックや議論ができる。
- これにより信頼形成や設計思想の擦り合わせが促進される。
前提条件(必読)
- リポジトリに
doc/rules/pr_review_checklist.mdが存在すること- 既存のチーム規約や観点を置いておく想定です(このファイルを本Actionsが更新対象にします)
- GitHub リポジトリの Secrets に
OPENAI_API_KEYが設定済みであること- レビューコメントの要約/整理に OpenAI API(任意のモデル)を使います
使い方(トリガー)
- 手動実行(workflow_dispatch):対象PR番号を入力(複数可)
-
pull_request: closed:PRがclose/mergeされたタイミングで過去コメントを収集し、チェックリスト更新PRを提案
ワークフローYAML
実際に使っているワークフローは以下(抜粋)。
※フル版は文末の「付録」か、こちらからダウンロードしてください → update_pr_checklist.yml(全文)
name: Update PR Review Checklist
on:
workflow_dispatch:
inputs:
pr_number:
description: "PR番号(複数指定する場合はカンマ区切り: 123,124,125)"
required: true
type: string
pull_request:
types: [closed]
jobs:
update-checklist:
if: github.event_name != 'pull_request' || !startsWith(github.event.pull_request.head.ref, 'update-checklist/')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Codex CLI
run: |
npm install -g @openai/codex
codex --version
- name: Authenticate Codex CLI
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
if [ -z "$OPENAI_API_KEY" ]; then
echo "Error: OPENAI_API_KEY is not set. Configure the secret to use Codex CLI."
exit 1
fi
printf '%s\n' "$OPENAI_API_KEY" | codex login --with-api-key
- name: Determine PR numbers
id: pr-numbers
uses: actions/github-script@v7
with:
script: |
let prNumbers = [];
if (context.eventName === 'workflow_dispatch') {
prNumbers = '${{ inputs.pr_number }}'.split(',').map(n => n.trim());
} else if (context.eventName === 'pull_request' || context.eventName === 'pull_request_review') {
prNumbers = [String(context.payload.pull_request.number)];
}
core.setOutput('numbers', prNumbers.join(','));
return prNumbers;
- name: Fetch PR information and comments
id: pr-data
uses: actions/github-script@v7
with:
script: |
const prNumbers = '${{ steps.pr-numbers.outputs.numbers }}'.split(',').map(n => n.trim());
const allPRData = [];
const fs = require('fs');
const path = require('path');
for (const prNum of prNumbers) {
if (!prNum) {
continue;
}
const prNumber = parseInt(prNum, 10);
// PR情報取得
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
// 通常のコメント取得
const comments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100
}
);
// レビューコメント取得
const reviewComments = await github.paginate(
github.rest.pulls.listReviewComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
per_page: 100
}
);
// レビュー取得
const reviews = await github.paginate(
github.rest.pulls.listReviews,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
per_page: 100
}
);
allPRData.push({
number: prNumber,
title: pr.title,
body: pr.body,
state: pr.state,
merged: pr.merged,
comments: comments.map(c => ({
type: 'issue_comment',
body: c.body,
user: c.user.login,
created_at: c.created_at
})),
reviewComments: reviewComments.map(c => ({
type: 'review_comment',
body: c.body,
user: c.user.login,
path: c.path,
diff_hunk: c.diff_hunk,
created_at: c.created_at
})),
reviews: reviews.map(r => ({
type: 'review',
body: r.body,
user: r.user.login,
state: r.state,
submitted_at: r.submitted_at
}))
});
}
const outputPath = path.join('/tmp', 'pr-data.json');
fs.writeFileSync(outputPath, JSON.stringify(allPRData, null, 2));
core.setOutput('path', outputPath);
- name: Save PR data to file
run: |
cat "${{ steps.pr-data.outputs.path }}"
- name: Ensure checklist file exists
run: |
if [ ! -f "doc/rules/pr_review_checklist.md" ]; then
echo "Checklist file not found. Initializing as empty file."
mkdir -p doc/rules
: > doc/rules/pr_review_checklist.md
fi
- name: Create prompt file
run: |
cat > /tmp/codex-prompt.txt << 'PROMPT_EOF'
# タスク概要
指定されたPRのレビューコメントを分析し、doc/rules/pr_review_checklist.md のチェックリストを更新してください。
# 実行コンテキスト
- 対象PR番号: #${{ steps.pr-numbers.outputs.numbers }}
- PRデータファイル: /tmp/pr-data.json(同じパスが環境変数 `PR_DATA_PATH` にも設定されています)
# PR情報
PRの詳細情報は /tmp/pr-data.json に保存されています。
このファイルには以下の情報が含まれています:
- PR番号、タイトル、説明
- 通常のコメント
- レビューコメント(コード差分付き)
- レビュー全体のコメント
# 作業手順
## 1. PR情報の読み込みと分析
- /tmp/pr-data.json を読み込み、PR本文・コメント・レビュー内容を自由に分析する。
## 2. 現在のチェックリストの読み込み
- doc/rules/pr_review_checklist.md を読み込み、必要に応じて構造を見直す。
## 3. チェックリストの更新
### 基本ルール
- 既存の項目は削除しない
- 既存の構造とフォーマットを維持
- 適切なカテゴリに追加(必要なら新カテゴリを作成)
- 新規項目には [追加: PR#123] または [追加: PR#123,#124] のタグを付ける
- コード例がある場合は、既存の形式に合わせて追加
### カテゴリ分類ガイド
現在のカテゴリ:
- セキュリティ
- Laravel / PHP 固有のベストプラクティス
- パフォーマンス最適化
- コーディング規約・可読性
- エラーハンドリング
- Vue.js / Nuxt 固有のベストプラクティス
- データベース・API 設計
- UI/UX
- テスト・デバッグ
- コンポーネント設計原則
新しいカテゴリが必要な場合は適切に追加してください。
# 出力形式
更新後の doc/rules/pr_review_checklist.md を保存してください。
# 注意事項
- 既存のフォーマット、構造、スタイルを厳密に維持
- 日本語で記述(既存の文体に合わせる)
- コード例は既存の形式(❌/✅、シンタックスハイライト)を踏襲
- 新規項目が0個の場合もあります(その場合はメタ情報のみ更新)
PROMPT_EOF
- name: Run Codex to update checklist
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
PR_DATA_PATH: ${{ steps.pr-data.outputs.path }}
PR_NUMBERS: ${{ steps.pr-numbers.outputs.numbers }}
run: |
codex exec "$(cat /tmp/codex-prompt.txt)" \
--full-auto \
--config sandbox_mode="workspace-write"
- name: Verify checklist was updated
run: |
if [ ! -f "doc/rules/pr_review_checklist.md" ]; then
echo "Error: Checklist file not found!"
exit 1
fi
echo "=== Checklist file contents ==="
head -50 doc/rules/pr_review_checklist.md
echo "..."
tail -20 doc/rules/pr_review_checklist.md
- name: Create summary of changes
id: summary
run: |
echo "## 変更内容" > /tmp/pr-summary.md
echo "" >> /tmp/pr-summary.md
if git diff --quiet doc/rules/pr_review_checklist.md; then
echo "チェックリストに変更はありませんでした(既存項目でカバー済み)" >> /tmp/pr-summary.md
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "チェックリストを更新しました。GitHub上の差分を確認してください。" >> /tmp/pr-summary.md
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
cat /tmp/pr-summary.md
- name: Read summary file
id: read-summary
run: |
SUMMARY=$(cat /tmp/pr-summary.md)
echo "summary<<EOF" >> $GITHUB_OUTPUT
echo "$SUMMARY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create PR with updated checklist
uses: peter-evans/create-pull-request@v6
if: steps.summary.outputs.has_changes == 'true'
with:
commit-message: "docs: PR${{ steps.pr-numbers.outputs.numbers }}のレビューからチェックリストを更新"
branch: update-checklist/pr-${{ github.run_id }}
title: "📋 PRレビューチェックリスト更新 (from PR#${{ steps.pr-numbers.outputs.numbers }})"
body: |
## 概要
以下のPRのレビューコメントから学習し、チェックリストを更新しました。
### 対象PR
#${{ steps.pr-numbers.outputs.numbers }}
## 変更内容
- `doc/rules/pr_review_checklist.md` の更新
## レビュー依頼
- [ ] 新しいチェック項目が適切か確認
- [ ] 既存の項目が保持されているか確認
- [ ] カテゴリ分けが適切か確認
- [ ] コード例が正確か確認
- [ ] 日本語表現が自然か確認
## 詳細
${{ steps.read-summary.outputs.summary }}
labels: documentation, automation, review-checklist
delete-branch: true
ポイント:**“自動更新コミット”ではなく“PR作成”**にしている点。
合意形成・ブロック・議論の余地を残すことが、文化の維持に効きます。
AIレビューへの接続(活用例)
-
日々更新される
doc/rules/pr_review_checklist.mdを、AIレビューのプロンプトに同梱します。 -
例(Codex/Windsurf等):
- 「このPRを以下のチェックリスト観点で評価し、項目ごとに合否と修正提案を出して」
- 添付:
doc/rules/pr_review_checklist.md+ 差分
これで、人間が育てた文化をAIレビューにも反映できます。
運用Tips
- カテゴリ分けを意識(セキュリティ/設計/命名/エラーハンドリング/パフォーマンス…)
- 例コードは最小(レビュー観点の“意図”が伝わる粒度)
- PR本文テンプレに“チェック観点”のチェックボックスを入れると議論が進む
- 月1でふりかえり会(PRの差分を起点に「増やす観点/削る観点」を話す)
まとめ
- レビューコメントはチーム文化の資産
- GitHub Actionsで「コメント→チェックリスト→PR」を自動化すると、文化を更新し続ける仕組みになる
- さらにAIレビューに同梱すれば、人にもAIにも文化が染みる
これは“ベスト唯一解”ではありません。もっと良いやり方があれば、ぜひXで教えてください。
リプ/DM歓迎 👉 https://x.com/sho_fcafe