0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「PRコメント → チェックリスト → PR」をGitHub Actionsで自動化して、AIにも人にもレビュー文化を浸透させる話

Posted at

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 リポジトリの SecretsOPENAI_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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?