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?

なぜAIのPRを可視化したいのか

みなさんのPR一覧はこうなっていませんか…?
ラベルが付いていないPR一覧

  • AIで作成されたPR
  • タイトルだけでは中身が想像できないPR
  • 規模(差分の大きさ)が開くまでわからないPR
  • etc..

AI駆動開発が広がり、Claude CodeやGitHub CopilotといったエージェントがPRを出すことは日常になりました。GitHub Copilotであれば、IssueのAssignにCopilotを指定するだけで、クラウド上でIssueを処理してPRまで作成してくれます。複数のIssueを並列で処理できるようになった一方で、出てきたPRを人間がチェックする作業の比重は増えています。

ここで困るのが、PR一覧を見ても中身の傾向がつかめないことです。タイトルだけでは機能追加・バグ修正・依存更新のどれなのか判別できず、結局ひとつずつ開いて差分を確認することになりがちです。PRの数が増えるほど、このレビュー負荷はじわじわと効いてきます。

そこで役立つのがラベルです。変更の種類(機能追加・バグ修正・依存更新など)や、触っているレイヤー(フロントエンド・バックエンドなど)、変更の規模をラベルとして自動で付けておけば、PR一覧の時点で中身の見当がつきます。例えばモデルの変更が含まれたPRなら、label:modelのように絞り込めます。また、後から「どんな種類のPRが多かったか」を振り返ることもできます。

なお、ここで紹介するラベル付けはAIが出したPRだけに限った話ではありません。人間が出したPRにも同じルールで付くので、チーム全体のPRの傾向を可視化する仕組みとして使えます。

GitHub Labelerとは

Labeler(actions/labeler)は、PRに対してラベルを自動で付けてくれるGitHub公式のActionです。設定したルールに一致したラベルが自動で付きます。

仕組みはシンプルで、ルールを .github/labeler.ymlに書き、それを呼び出すワークフローを .github/workflows/に置くだけです。ルールは「ラベル名」と「そのラベルを付ける条件」の組み合わせで書きます。

.github/labeler.yml
# docs/ 配下か Markdown を変更したPRに docs ラベルを付ける
docs:
  - changed-files:
      - any-glob-to-any-file:
          - 'docs/**'
          - '**/*.md'

changed-files は変更ファイルのパスを見る条件で、any-glob-to-any-file に書いたglobのいずれかに一致したファイルが含まれていれば、そのラベル(ここでは docs)が付きます。ラベルを増やしたいときは、このブロックをラベルの数だけ並べていきます。

Labelerでできること

Labelerの正規表現が使える素材は次の2つです。

  • 変更ファイルのパス(changed-files)
  • ブランチ名(head-branch / base-branch)

つまり「どのファイルを変更したPRか」「どんな名前のブランチから出たPRか」でルールを設定しラベルを付けます。パスは **/*.tsmodels.tssrc/api/** のようなglobで、ブランチ名は ^fix/ のような正規表現で書けます。

「中身を見て判断する」のではなく、あくまでパスとブランチ名というルールベースの静的な判定です。逆に言えば、ここに当てはまらないもの(PRのタイトルや本文、差分の行数、作成者)はLabelerだけでは扱えません。このあたりは記事の後半で、補助のワークフローや専用Actionを組み合わせて補っていきます。

なぜLabelerなのか

PRの中身を読んでラベルを付けたいなら、LLMにPRの差分を渡して動的にタグ付けする方法も考えられます。柔軟ですが、APIキーが必要で、PRが出るたびに使った分の料金が発生します。

一方でLabelerはパスとブランチ名のルールに一致させるだけの静的な仕組みなので、追加のAPI利用料はかかりません。判定が単純なぶん挙動も予測しやすく、PRごとに結果がブレることもありません。まずは無料で安定して回るLabelerを土台にして、足りない部分だけを後から足していくのがおすすめです。

どんなラベルを用意すると良いか

ラベルは増やしすぎると一覧がごちゃごちゃしてしまうので、最初は「種類」「領域」「規模」の3方向に絞るとバランスが良いです。ここではWeb開発のプロジェクトを想定したサンプルを挙げます。実際のラベルは、お使いのプロジェクトのディレクトリ構成に合わせて正規表現(glob)を整えてください。

ラベル 付ける条件のイメージ 何がわかるか
docs docs/****/*.md を変更 ドキュメントだけのPR
deps package.jsonpnpm-lock.yaml などを変更 依存パッケージの更新
frontend src/frontend/****/*.tsx を変更 フロントエンド側の変更
backend src/api/**src/server/** を変更 バックエンド側の変更
model **/models/**models.ts を変更 データモデル・スキーマの変更
test **/*.test.tstests/** を変更 テストの追加・修正
ci/cd .github/workflows/** を変更 パイプラインの変更
ai-prompt .claude/** などプロンプト定義を変更 AIのプロンプトに関わる変更
size:* 差分の行数(後述の補助で算出) PRの規模

docsdepsci/cd のあたりは、変更ファイルのパスがほぼそのままラベルに対応するので、Labelerと相性が良い部類です。一方で size:* はパスでは決められないため、後半の応用で別の仕組みを使います。

実装: AI由来のPRを判定してラベルを付ける

ここからは、シンプルなTodoアプリ(Node.js + Express)のリポジトリを題材に、実際に動かした設定を載せていきます。まずはパスベースのラベル付けから始めて、そのあとにブランチ名・タイトル・規模の応用を足していく流れです。

labeler.yml でパスベースのラベルを付ける

Labelerを動かすには、ルールを書く .github/labeler.yml と、それを呼び出すワークフローの2つを用意します。

まず呼び出し側のワークフローです。PRをトリガーに、actions/labelerを実行するだけです。

.github/workflows/labeler.yml
name: Labeler

on:
  - pull_request_target

permissions:
  contents: read
  pull-requests: write

jobs:
  label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

pull_request_target で動かすのは、フォークからのPRでも書き込み(ラベル付け)ができるようにするためです。ラベルを付けるには pull-requests: write の権限が必要になります。

次にルールを定義する labeler.yml です。Todoアプリの構成に合わせて、変更ファイルのパスからラベルを決めています。

.github/labeler.yml
ci/cd:
  - changed-files:
      - any-glob-to-any-file:
          - '.github/workflows/**'

test:
  - changed-files:
      - any-glob-to-any-file:
          - 'test/**'
          - '**/*.test.js'

model:
  - changed-files:
      - any-glob-to-any-file:
          - 'src/models/**'

docs:
  - changed-files:
      - any-glob-to-any-file:
          - '**/*.md'
          - 'docs/**'

deps:
  - changed-files:
      - any-glob-to-any-file:
          - 'package.json'
          - 'package-lock.json'

any-glob-to-any-file に並べたglobのいずれかに一致したファイルが変更に含まれていれば、そのラベルが付きます。例えば src/models/todo.js を触ったPRには model が、docs/ 配下や任意のMarkdownを触ったPRには docs が付きます。なお、付けようとしたラベルがGitHub上にまだ存在しない場合は、Labelerが自動で作成してくれます。

応用: 変更の種類を判定する(ブランチ名)

ブランチ名の規約が決まっているなら、種類のラベルもLabeler単体で付けられます。github-scriptを書く必要はありません。head-branch にブランチ名の正規表現を並べると、いずれかに一致したときにラベルが付きます。

.github/labeler.yml
fix:
  - head-branch:
      - '^fix/'
      - '^hotfix/'

feature:
  - head-branch:
      - '^feature/'
      - '^feat/'

配列はOR条件なので、fix/login-bug でも hotfix/login-bug でも fix ラベルが付きます。

パス条件(changed-files)と組み合わせたいときは all: でまとめるとAND条件になります。次の例は「ブランチ名が feature/ で始まり、かつ src/ 配下を変更した」ときだけ付きます。

.github/labeler.yml
feature-src:
  - all:
      - head-branch:
          - '^feature/'
      - changed-files:
          - any-glob-to-any-file:
              - 'src/**'

ただし注意点があります。AIエージェントが出すブランチ名は、必ずしも fix/feature/ の規約に従うとは限りません。Claude Codeのように claude/... といった独自のprefixを付けることもあります。その場合はブランチ名だけでは種類を判定できないので、次のPRタイトルでの判定にフォールバックします。

応用: 変更の種類を判定する(PRタイトル)

Labelerはタイトルや本文を見られないので、タイトルから種類を判定したいときは別のワークフローを用意します。ここではactions/github-scriptで、Conventional Commits形式のタイトル(feat: fix: など)を正規表現マッチして type:* ラベルを付けます。

.github/workflows/pr-title-label.yml
name: PR Title Labeler

on:
  pull_request_target:
    types:
      - opened
      - edited
      - reopened
      - synchronize

permissions:
  contents: read
  pull-requests: write

jobs:
  title-label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            const title = context.payload.pull_request.title || '';

            // Conventional Commits の type → 付与するラベル
            const rules = [
              { re: /^feat(\(.+\))?!?:/i,  label: 'type:feat' },
              { re: /^fix(\(.+\))?!?:/i,   label: 'type:fix' },
              { re: /^docs(\(.+\))?!?:/i,  label: 'type:docs' },
              { re: /^(chore|build|ci|refactor|perf|style|test)(\(.+\))?!?:/i, label: 'type:chore' },
            ];

            const matched = rules.find((r) => r.re.test(title));
            if (!matched) {
              core.info(`No conventional-commits type matched in title: "${title}"`);
              return;
            }

            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              labels: [matched.label],
            });

typesedited を入れているので、後からタイトルを直したときにも付け直されます。タイトルが規約に沿っていなければ何もせず終了します。

応用: 変更の規模(size)を判定する

変更の規模(差分の行数)もLabelerでは判定できません。専用のAction(pascalgn/size-label-actionCodelyTV/pr-size-labeler)を使う手もあります。ここではactions/github-scriptで additions + deletions を集計して size:* を付ける自前実装を載せます。

.github/workflows/pr-size-label.yml
name: PR Size Labeler

on:
  pull_request_target:
    types:
      - opened
      - synchronize
      - reopened

permissions:
  contents: read
  pull-requests: write

jobs:
  size-label:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            const total = pr.additions + pr.deletions;

            // total <= max の最初にマッチしたものを採用。閾値はカスタム可能。
            const thresholds = [
              { max: 10,       label: 'size:XS' },
              { max: 50,       label: 'size:S' },
              { max: 200,      label: 'size:M' },
              { max: 500,      label: 'size:L' },
              { max: Infinity, label: 'size:XL' },
            ];

            const target = thresholds.find((t) => total <= t.max).label;
            const allSizeLabels = thresholds.map((t) => t.label);

            // 既存の size:* ラベルのうち、今回付けるものと違うものは外す
            const current = (pr.labels || []).map((l) => l.name);
            for (const name of current) {
              if (allSizeLabels.includes(name) && name !== target) {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  name,
                }).catch(() => {});
              }
            }

            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: pr.number,
              labels: [target],
            });

閾値は次のようにしています。プロジェクトの粒度に合わせて調整してください。

ラベル 変更行数 (additions + deletions)
size:XS 〜10
size:S 〜50
size:M 〜200
size:L 〜500
size:XL 501〜

ポイントが2つあります。1つは、再実行時に古い size:* ラベルが残らないよう、今回付けるもの以外のsizeラベルを削除していることです。もう1つは、synchronize(追記のpush)でも再評価されるよう types に含めていることです。これで、PRに追記して差分が増えたときも規模ラベルが付け替わります。

可視化して振り返る

ラベルが付くようになったら、あとはそれを使って眺めるだけです。

一番手軽なのはPR一覧のフィルタです。GitHubの検索ボックスに label:type:fix label:size:XS のように複数のラベルを並べると、AND条件で絞り込めます。「小さいバグ修正のPR」だけを抜き出してまとめてレビューする、といった使い方ができます。

件数を集計して傾向を見たいときは、ghコマンドが便利です。特定のラベルが付いたマージ済みPRの件数を数えることもできます。

直近で何回modelの変更があったか

直近で何回modelの変更があったかを可視化できます。

Issueのサイズが適切に細分化出来たか
Issueのサイズが適切に細分化できたかを見ることもできます。size:XL ばかりが並ぶようなら、もう少し小さく分割してからAIに渡したほうが良い、といった気づきにつながります。

さらに進めるなら、この集計を定期的に回してダッシュボード化したり、新しいPRにラベルが付いたタイミングでSlackへ通知したり、といった発展も考えられます。まずはフィルタとghでの集計から始めて、必要になったら自動化していくのがおすすめです。

まとめ

LabelerでPRを可視化する流れを、土台から応用まで見てきました。

  • 土台: Labelerで変更ファイルのパスからラベルを付ける(docs,deps,model など)。無料で安定して回る
  • 応用: ブランチ名で種類を判定する。規約があればLabeler単体で完結する
  • 応用: PRタイトルや差分の行数は、github-scriptや専用Actionで補う(type:* size:*)
  • 活用: PR一覧のフィルタやghコマンドの集計で、PRの傾向を振り返る

まずはパスベースのラベルだけでも、PR一覧の見通しはぐっと良くなります。

ラベルありPR
ラベルの設定をすることで、冒頭に見たPR一覧もこのようにざっくり理解できるPRに変化しました。AI駆動開発でPRの数が増えていくほど、この可視化の効果は大きくなっていくはずです。

参考文献

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?