羅針盤 技術航海日誌

株式会社羅針盤の技術ブログです

DORA-yaki: DORAメトリクスダッシュボード の紹介

あけましておめでとうございます(2月)、羅針盤の森川です。
今回はDORAメトリクスダッシュボードツール「DORA-yaki」をOSS 公開したので簡単に紹介します。

tl;dr

GitHubのデータからDORAメトリクス + サイクルタイム分析 + 生産性スコアを可視化するダッシュボードツール「DORA-yaki」をOSSとして公開しました。

GitHub PAT を作って docker compose up で動きます。

主な特徴:

  • DORAメトリクス4指標(デプロイ頻度、変更リードタイム、変更失敗率、MTTR)
  • サイクルタイム分析(First Commit → PullRequest(PR) Open → First Review → Approve → Merge の各区間)
  • 生産性スコア(0-100のスコアリング)
  • レビュー分析(レビュー回数・コメント数・レスポンスタイム)
  • チーム・メンバー別分析
  • ボットユーザー除外
  • 多言語対応

サンプルイメージ

まずはリポジトリを登録して、

Repository Management

データ同期するとそれっぽいものが表示されるようになります

Dashboard

チーム一覧

Team Performance

詳細ページではリポジトリ別も見れます

Member Detail

背景

Andrej Karpathy もすなる Vibe Coding といふものを、私もしてみむとして、するなり。

x.com

ということで森川潤さんと後藤直義さんがしきりに言っている SaaS is DEAD がモノホンなのかどうか試してみました。

newspicks.com

一応管理監督者という扱いなんですが、 コーディングマシンにもなれず、マネジメントもできず、なんか雑務ばっかしているただ日々呼吸する葦のような存在になっている気がしてきたので、 少しは管理というものをしてみようと思い、まともにDORAメトリクスを取ってみようかなと思った次第です。

DORA-yaki でできること

いわゆる開発者生産性みたいなのを可視化するためのダッシュボード的なやつです。

DORAメトリクス Four Keys

時間がある人はGoogleのDORAレポートを読んでいただきまして。 cloud.google.com

GitHubベースなので、以下のようなデータから指標を取ってます。たぶん。

指標 説明
Deployment Frequency デプロイ頻度(mainブランチへのマージ頻度)
Lead Time for Changes 変更リードタイム(first commit → マージまでの時間)
Change Failure Rate 変更失敗率(revert PRの割合)
MTTR (Mean Time to Recovery) 平均復旧時間(revert PRのマージまでの時間)

サイクルタイム分析

PRのライフサイクルを各フェーズに分解して可視化できるみたいです

First Commit ──→ PR Open ──→ First Review ──→ Approve ──→ Merge
     │              │              │              │          │
     └──────────────┘──────────────┘──────────────┘──────────┘
       Coding Time    Pickup Time   Review Time   Deploy Time
     └────────────────────────────────────────────────────────┘
                         Total Cycle Time

個人的にそんな使ったこと無いんですが、PR溜まりがちとかマージ遅いとかの課題があれば見られると嬉しいんでしょうか。 Coding Timeがもう少し正確に取れると良さそうですよね。 開始時に empty commit を切るみたいなルールとか習慣を導入しないと結構ブレるので微妙な気がしていますが、 起点をブランチ作成時間とかチケットのステータス変更とか色々組み合わせられるといい感じになりそう。

生産性スコア

なんか100点満点のスコアリングなんですが、適当に重み付けして足し合わせただけっぽいので正直微妙です。

レビュー分析

レビュー回数・コメント数、レビュー反応時間、人別統計とか出してます。

チーム分析・リポジトリ分析

メンバー単位・リポジトリ単位での分析が可能です。 拡張子別の統計も付けてます。

ボットユーザー除外・分析

dependabot や renovate 等のボットユーザー(名前末尾に [bot] が付くもの)をフィルタリングしたり分析したりできます。 こいつらも日々働いてますからね。

多言語対応

日本語、英語、中国語(繁体字・簡体字)、韓国語、スペイン語、フランス語、ドイツ語の8言語に対応しています。

使い方

GitHub Personal Access Token の設定

個人、またはorgのPATを生成してください。 Repository Permissions に以下のRead権限を付けてください。

  • Pull requests
  • Deployments
  • (Metadata)

https://github.com/compasstechlab/dora-yaki/blob/main/docs/DEPLOYMENT.ja.md#github-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A

Docker Compose で起動

簡易的に試すには docker compose で起動するのが楽です。

# GitHub Personal Access Token を設定
export GITHUB_TOKEN=github_pat_xxxxxxxxxxxx

# 起動
docker compose up

ブラウザで http://localhost:7201 を開けばダッシュボードが表示されます。

クラウド環境へのデプロイ

バックエンドは Terraform を使ってもいいし、使わなくてもいいですが、 Google Cloud の DatastoreのIndex設定や IAM追加、各種APIの有効化をし、Secret Manager に GitHub Token を登録して、Cloud Functions gen2 にデプロイします。

フロントエンドは Cloud Run / Firebase Hosting / Cloudflare Pages のサンプルを各々のMakefile内に用意しているので、 そこを見ながら実行してみてください。

技術スタック

レイヤー 技術
バックエンド Go + net/http + Cloud Functions Framework
フロントエンド SvelteKit (Svelte 5) + Chart.js
データベース Cloud Datastore
インフラ Terraform, Cloud Functions gen2
コンテナ Distroless ベースイメージ

バックエンドは Go の net/http をベースにしていて、Cloud Functions Framework を使うことで
ローカルでは普通のHTTPサーバーとして動き、GCPではCloud Functionsとして動作するようになっています。

フロントエンドは Svelteです。jsxが無理すぎてずっとReactを避けています

注意点

今のところ認証機構やアクセス制限といった現代の必需品であるセキュリティは考慮されていません。 漢の裸一貫100%全開放状態なので、本番デプロイする場合は Cloudflare Access や IAP とかでいい感じに制限かけてください。

(そしてバグってたらごめんなさい。パッと見でしか確認してないです ;w;)

まとめと感想

きっかけはCI上のテストの最適化をしていて、細切れの待ち時間が結構あったので、 「そういえば数週間前にClaude Code Webで依頼して放置してたアレやるか」と思い出したのが始まりです。 僕の個人的なニーズを満たすものは1日程度で出来ていて、 細かいバグやドキュメント、公開のための設定で+0.5日。 そしてデバッグやこのブログを書くための見直しとかで+1.5日って感じです。

簡単なツールは本当にすぐできますが、機能を追加してバグを直している内にClaudeが内容を忘れてきて精度が落ちてきている気がしました。 例えば初期はフロントエンドを直すとバックエンドも直してくれていたのが、片方だけになっていたり、 あるバグの修正を依頼して、実際には複数のページで同じ要因でバグってたのに、最初にエラーを出した1ページだけ修正して終わらせてしまったりとかです。

コード構成自体はあまり私の好みではない部分も多いですが、それを指示するのもめんどくさくてそのままにしてます。 とりあえずニーズを満たすそこそこ動く機能のものがすぐ作れたので満足です。 SaaSがDead or Aliveなのかどうかはまだ分かっていません。

エンジニア募集

羅針盤では一緒に働いてくれるソフトウェアエンジニアや子会社CTOを募集しています。 まずはカジュアルにお話を聞いてみたいという方も歓迎です。 お待ちしております。

www.wantedly.com

AWS IAM Identity Center SCIM Access Token Approaching Expiration メールへの対応備忘録 (for Google Workspace)

Google Workspaceのアカウントで AWS SSO している方向け 毎年忘れるのでブログ化しておく作戦

tl;dr

・対象者: 既にAWS SSO設定していてGoogle WorkspaceでSCIM的なプロビジョニング設定している人

AWS側の作業

  • ルートアカウントでログインし、IAM Identity Centerへ
  • 上部警告バナーの "Manage Token" か、Settings → Identity source タブ → Actionsボタン → "Manage provisioning"
  • "Generate Token" でSCIM用のアクセストークンを発行しメモしておく

Google Workspace側の作業

  • アプリ → ウェブアプリとモバイルアプリ → Amazon Web Services
  • "自動プロビジョニング" を選択
  • "アプリの承認" の "再承認"ボタンを押下し、先程の長いアクセストークンを入力し再認証する

無駄話

今年も12月がやってきました。師走でごわす。
12月といえばみなさん大好きクリスマス、忘年会、そして人によっては長期休暇を取って海外へバカンスいったりと羨ましい限りです。
人によっては年末年始セールに備えて入念な準備をしたり、ここしか長期メンテ時間取れないとかで、元旦あけおめDBリプレイスとか苦しい時期かもしれません。(遥か遠い目)

私にとって毎年やってくるのはサンタさんでもなまはげさんでもなく↓のメールさんです

Action required 煽り

AWS SSOや GoogleのSCIM設定とか普段触らないし、遷移もしづらいし、これ毎回やり方思い出すのにちょっとかかるんですよね。 そんなわけで2026年の私のために備忘録です。過去の自分に感謝・自画自賛する未来の私が目に浮かびます。

AWS側の作業(画像)

何はともあれAWSのルートアカウントでログインします。 そして、

  • IAM Identity Centerへアクセス
  • Settings → Identity source タブ → Actionsボタン → "Manage provisioning" (または上部警告赤バナー)

で移動します

不安を煽る赤バナー

遷移しづらい場所

そして "Generate Token" ボタンでトークンを生成します。 おそらくトークンは最大で2つまでしか同時に存在できないのでコピペし忘れたら使ってないやつを削除して再生成してください。

SCIMトークン生成

Google Workspace側の作業(画像)

次はGoogle Workspaceの管理者ページで、

  • アプリ → ウェブアプリとモバイルアプリ

と移動し、"Amazon Web Services" を選択します。 https://admin.google.com/ac/apps/unified

これも探しづらい場所

そして下部にある"自動プロビジョニング" を選択します。

最後に、"アプリの承認" の "再承認"ボタンを押下し、先程の長いアクセストークンを入力し再認証してください。

成功したのかどうか分からない再承認

これで終了です。

成功しているかどうかマジで分かりづらいですが、Google Workspace側での属性変更やグループ追加等でデバッグすると分かります。 今回のケースでは、Google Workspace側へグループ追加 → AWS側に1~2分くらいで同期されてユーザー追加されてました。結構早いです。

来年の私へ、半裸待機大感謝してください。

Go 言語でも Vertex AI で gpt-oss を使いたい

tl;dr

現在のプロジェクトの用途では GPT-5-mini と比較しても gpt-oss-120b-maas はかなり良い感じでした。
Text 専用モデルなのが欠点ですが、用途が合えばファーストチョイスになりそうです。

Go は ↓ みたいな感じでいけます。

※ 事前に GPT OSS API Service を有効にしておく必要があります
https://console.cloud.google.com/vertex-ai/publishers/openai/model-garden/gpt-oss-120b-maas

import (
    "context"
    "errors"
    "fmt"

    "github.com/openai/openai-go/v2"
    "github.com/openai/openai-go/v2/option"
    "golang.org/x/oauth2/google"
)

const (
    // OpenAI API準拠のVertexAIのエンドポイント
    vertexOpenAIEndpoint = "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/endpoints/openapi"
    vertexAILocation     = "us-central1"
)

// 通常のOpenAI APIを使う場合はこう
func ChatCompletionByOpenAI(ctx context.Context) error {
    cli := openai.NewClient() // OPENAI_API_KEY のAPIキーが自動で使われる

    messages := []openai.ChatCompletionMessageParamUnion{
        openai.SystemMessage("あなたは犬です"),
        openai.UserMessage("猫が飼いたいです"),
        openai.UserMessage("猫を飼ってもいいですか?"),
    }

    params := openai.ChatCompletionNewParams{
        Model:    "gpt-5-mini",
        Messages: messages,
    }

    // OpenAIへリクエスト
    resp, err := cli.Chat.Completions.New(ctx, params)

    // エラーチェック
    switch {
    case err != nil:
        return err
    case len(resp.Choices) == 0:
        return errors.New("no choices")
    case resp.Choices[0].Message.Content == "":
        return errors.New("empty content")
    }

    data := map[string]any{
        "model":             "gpt-5-mini",
        "content":           resp.Choices[0].Message.Content,
        "total_tokens":      resp.Usage.TotalTokens,
        "prompt_tokens":     resp.Usage.PromptTokens,
        "completion_tokens": resp.Usage.CompletionTokens,
    }
    fmt.Printf("OpenAI API Result: %+v\n", data)
    // => わん!猫が飼いたいんだね、いいと思うワン!ぼく(犬)から見たポイントを教えるね。
    return nil
}

// Vertex AI のOpenAI APIを使う場合はこう
func ChatCompletionByVertexAIGPTOSS(ctx context.Context) error {
    // GCPのOAuth認証を利用してアクセストークンを取得し、APIキーとして使う
    credentials, err := google.FindDefaultCredentials(ctx)
    if err != nil {
        return err
    }
    token, err := credentials.TokenSource.Token()
    if err != nil {
        return err
    }

    // クライアントを作成(BaseURLとAPIキーを設定)
    baseURL := fmt.Sprintf(vertexOpenAIEndpoint, vertexAILocation, "stay-operation-dev", vertexAILocation)
    cli := openai.NewClient(option.WithBaseURL(baseURL), option.WithAPIKey(token.AccessToken))

    messages := []openai.ChatCompletionMessageParamUnion{
        openai.SystemMessage("あなたは犬です"),
        openai.UserMessage("猫が飼いたいです"),
        openai.UserMessage("猫を飼ってもいいですか?"),
    }

    params := openai.ChatCompletionNewParams{
        Model:    "openai/gpt-oss-120b-maas",
        Messages: messages,
    }

    resp, err := cli.Chat.Completions.New(ctx, params)

    // エラーチェック
    switch {
    case err != nil:
        return err
    case len(resp.Choices) == 0:
        return errors.New("no choices")
    case resp.Choices[0].Message.Content == "":
        return errors.New("empty content")
    }

    data := map[string]any{
        "model":             "gpt-oss-120b-maas",
        "content":           resp.Choices[0].Message.Content,
        "total_tokens":      resp.Usage.TotalTokens,
        "prompt_tokens":     resp.Usage.PromptTokens,
        "completion_tokens": resp.Usage.CompletionTokens,
    }
    fmt.Printf("Vertex AI API Result: %+v\n", data)
    // => ワン!もちろん、猫を飼うのは大歓迎だよ!🐱
    return nil
}

背景・小話

私は安くて早くて美味しいものが好きです。

Gyudon from Wikimedia Commons

目線的なものを付けてみましたが、2 本の線も3本の線になるという話が思い起こされます。
頭の良い人たちは目の付け所がシェイピーですね。

gpt-oss もみなさん騒いでらっしゃって、私も安くて早くてエコで賢いということで使いたいなと思ってました。
ちょうど現在のプロジェクトで Vertex AI の Gemini を使っていたんですが、API で Structured Output(指定の JSON 形式での出力固定)を使うと ゴミ ちょっと変なレスポンスばっかだし、
速度もめちゃくちゃ遅くなって使い勝手が悪いので困っていました。
そこで GPT-5(mini) にリプレイスしてみたら動作が安定して 2~3 倍くらい早くなりました。しかも安い。
人間は一度いいものを体験してしまうと元の生活には戻れないものです。GPT-5 でも改善して良かったんですが、さらに早く安くなるというのであれば試してみたくなります。

Amazon Bedrock で使えるみたいなのは知ってたんですが、GCP でももしかして使えるのか? と思ってみてみたら使えて安心しました。
https://console.cloud.google.com/vertex-ai/publishers/openai/model-garden/gpt-oss-120b-maas

ただサンプルコード表示を見ると curl での例しか無くてちょっと困りました。
どうすればええねん。

ENDPOINT=us-central1-aiplatform.googleapis.com
REGION=us-central1
PROJECT_ID="YOUR_PROJECT_ID"

curl \
  -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" https://${ENDPOINT}/v1/projects/${PROJECT_ID}/locations/${REGION}/endpoints/openapi/chat/completions \
  -d '{"model":"openai/gpt-oss-20b-maas", "stream":true, "messages":[{"role": "user", "content": "Summer travel plan to Paris"}]}'

既存の Gemini 実装では google.golang.org/genai を使っていたので、これが使えるのかもと思って試してました。
それっぽい箇所もあって色々試してみたんですがなんか動きませんでした。
https://github.com/googleapis/go-genai/blob/18aeb1e9a4ec2e5f29ce619acdc544cd57a7ef55/transformer.go#L89-L92

よくよく見ると、通常の VertexAI 提供のモデルは projects/{project}/locations/{location}/models/{model} ですが、
OpenAI API Service の curl の例は projects/{project}/locations/{location}/endpoints/openapi/chat/completions です。
一瞬これ詰んでね? って思ったんですが、そういえば v0 の API も似たようなやり方で動かしたなと思い出しました。
そうです、VertexAI の API の提供形式ではなく、OpenAI の API の形式で BaseURL だけを GCP に変えれば OK でした。
猛暑の中、無駄な遠回りをしてしまいました。

curl サンプルの API キーの部分も Authorization: Bearer $(gcloud auth print-access-token) となってるので、Cloud Run 上でそのまま認証通せそうです。

なんかかんや試して無事に動かせました。
gpt-oss-120b-maas, gpt-5-min, gemini-2.5-flash の 3 モデルを、
既存のプロジェクトで使っている処理での実行比較をしてみると、、、

gpt-oss でさらに早くなりました。
スクショにはないですが、HTML テーブルでの表示結果や RawJSON も眺めましたが、内容も合っており、他モデルと変わりません。
(PDF 解析は出来ないので CSV 整形のみ)
本気の速度自体はちょっと早い(15~30%)くらいですが、長考する回数がかなり減って平均値が一気に早くなっている感触です。

なお Gemini は通常のタスクでは使うことが多いですが、
API として使うと細かい数字や長いハッシュ値の部分が適当になることと、構造化出力 (Response Schema)機能を使うと 2~3 倍遅くなる印象です。
さらに無効な文字列で永久ループを続けるので stopSequences 設定して途切れた JSON を補完したりとか試行錯誤してだるかったです。
とはいえ 1~2 ヶ月後にはまた改良されたモデルや新たなツールが出てきてもっと楽になることでしょう。(= 僕らの Yak Shaving はまだまだ続く)

補足

  • tmc/langchaingo を使って VertexAI の OpenAI API Service を使う場合はおそらく ↓ で動くと思います(未確認)
import (
    "github.com/tmc/langchaingo/llms"
    "github.com/tmc/langchaingo/llms/openai"
)

// tl;dr のコードをベースに↓
llm, err := openai.New(
  openai.WithBaseURL(baseURL),
  openai.WithModel(modelName),
  openai.WithToken(token.AccessToken),
)
  • v0 の API を使う場合は BaseURL に https://api.v0.dev/v1 を指定するとたぶん動きます

まとめ

  • テキスト専用モデルで、画像とかファイルを使わなければかなり良い。
  • 安い。早い。エコ。
  • VertexAI で使うと GCP の請求にまとめられるので(予算・費用の承認がいらない)、管理が楽。

クレジット

google.golang.org/genai, github.com/openai/openai-go/v2 の HTTP リクエストのデバッグの際には https://github.com/motemen/go-loghttp を使いました。
10 年間いつもありがとうございます。
これからもよろしくお願いします。

GitHub ActionsでAmazon Q CLIによるGitHub PR Reviewを実行する

前々々回のDevin, 前々回のGemini CLI, 前回のOpenAI Codex CLIに続き、 またまたまた同じような記事です。すみません。

今回は Amazon Q CLIです。

背景・雑感

Claude Code, Devin, Gemini, Codex でコードレビューを実施しているのでもう十分やん お腹いっぱいという気持ちもあったのですが、せっかくなのでAmazon Qもやってみたいと思っていました。コードレビュー蠱毒です。

現状のAmazon Qのプレビュー版のGitHub Integrationは自動で発火する上に、細かいチューニングがしづらいので外して使ってませんでした。

ただのノイズ

CLIはなんかインストーラーが必要だったり、認証オプション設定もなんか説明もよく分かんなかったので無理かなと思っていましたが、試行錯誤していたらなんとかできました。

正直周りで使っている話はあまり聞かないのでどうなんかなと思ってましたが、レビューはそれなりに使えそうです。 現状では Claude 4 Sonnet がデフォルトで使えるのでそれも魅力です。

tl;dr

↓のような感じで設定します

env:
  AMAZONQ_CLI_URL: https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip
  # arm64 の場合は https://desktop-release.q.us-east-1.amazonaws.com/latest/q-aarch64-linux.zip

jobs:
  amazonq_review:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
    env:
      AMAZONQ_DB_DIR: /home/runner/.local/share/amazon-q
    steps:
      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ vars.CODE_REVIEW_APP_ID }}
          private-key: ${{ secrets.CODE_REVIEW_APP_PRIVATE_KEY }}

      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          token: ${{ steps.app-token.outputs.token }}

      - name: Cache q binary
        id: cache-q
        uses: actions/cache@v4
        with:
          path: ~/.local/bin
          key: q-x86_64-linux-v1

      - name: Install q
        if: ${{ steps.cache-q.outputs.cache-hit != 'true' }}
        run: |
          curl --proto '=https' --tlsv1.2 -sSf "${{ env.AMAZONQ_CLI_URL }}" -o "q.zip"
          unzip q.zip
          ./q/install.sh --no-confirm

      - name: Create SQLite database
        run: |
          touch data.sqlite3
          chmod 600 data.sqlite3
          sqlite3 data.sqlite3 << 'EOF'
          CREATE TABLE migrations (
              id INTEGER PRIMARY KEY,
              version INTEGER NOT NULL,
              migration_time INTEGER NOT NULL
          );
          CREATE TABLE history (
              id INTEGER PRIMARY KEY,
              command TEXT,
              shell TEXT,
              pid INTEGER,
              session_id TEXT,
              cwd TEXT,
              start_time INTEGER,
              hostname TEXT,
              exit_code INTEGER,
              end_time INTEGER,
              duration INTEGER
          );
          CREATE TABLE auth_kv (
              key TEXT PRIMARY KEY,
              value TEXT
          );
          CREATE TABLE state (
              key TEXT PRIMARY KEY,
              value BLOB
          );
          CREATE TABLE conversations (
              key TEXT PRIMARY KEY,
              value TEXT
          );
          EOF

          sqlite3 data.sqlite3 << 'EOF'
          INSERT INTO migrations (id, version, migration_time) VALUES
          (1, 0, 1755013634),
          (2, 1, 1755013634),
          (3, 2, 1755013634),
          (4, 3, 1755013634),
          (5, 4, 1755013634),
          (6, 5, 1755013634),
          (7, 6, 1755014426),
          (8, 7, 1755014426);
          EOF

      - name: Import auth_kv data from secrets
        run: |
          sqlite3 data.sqlite3 << EOF
          INSERT INTO auth_kv (key, value) VALUES ('codewhisperer:odic:token', '${{ secrets.AMAZONQ_AUTH_TOKEN }}');
          EOF
          sqlite3 data.sqlite3 << EOF
          INSERT INTO auth_kv (key, value) VALUES ('codewhisperer:odic:device-registration', '${{ secrets.AMAZONQ_AUTH_DEVICE }}');
          EOF
          mkdir -p ${{ env.AMAZONQ_DB_DIR }}
          mv data.sqlite3 ${{ env.AMAZONQ_DB_DIR }}

      - name: Run command
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          git fetch origin ${{ github.base_ref }}:${{ github.base_ref }}
          git diff ${{ github.base_ref }} HEAD > _pr_diff.txt
          echo "/help" | q chat
          q chat -a --no-interactive -- "--command /review $(printf '%s' '${{ vars.AI_REVIEW_GUIDELINE_AMAZONQ }}')"

      # q で指示した _pr_comment.md が作られていることを想定している
      - name: Send comment to PR
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          gh pr comment ${{ github.event.pull_request.number }} --body-file _pr_comment.md

      - name: Remove sqlite3 database
        if: always()
        run: rm -rf ${{ env.AMAZONQ_DB_DIR }}

補足

Amazon Q CLIの認証

軽く試した範囲では、Amazon Q CLIの認証は、OAuthのブラウザログインしかなさそうでした。IAMユーザーのアクセスキーやOIDC認証はできなさそうです。

参考

そして認証を行うと $HOME/.local/share/amazon-q/data.sqlite3 にSQLiteのデータが保存されます。(macOSでは $HOME/Library/Application Support/amazon-q/data.sqlite3 ) ファイルサイズは40~50KBくらいになるので、S3やGCSに認証済みのSQLiteファイルを置いて使うとかでもいいんですが、実際に使っているのはこの中の auth_kv テーブルだけで、ここに認証データを入れれば Amazon Q CLIが実行できるようになります。

そこでまず SQLiteのスキーマを作成しています

touch data.sqlite3
chmod 600 data.sqlite3

# `sqlite3 data.sqlite3 ".schema";` の実行結果をそのまま利用
sqlite3 data.sqlite3 << 'EOF'
CREATE TABLE migrations (
    id INTEGER PRIMARY KEY,
    version INTEGER NOT NULL,
    migration_time INTEGER NOT NULL
);
CREATE TABLE history (
    id INTEGER PRIMARY KEY,
    command TEXT,
    shell TEXT,
    pid INTEGER,
    session_id TEXT,
    cwd TEXT,
    start_time INTEGER,
    hostname TEXT,
    exit_code INTEGER,
    end_time INTEGER,
    duration INTEGER
);
CREATE TABLE auth_kv (
    key TEXT PRIMARY KEY,
    value TEXT
);
CREATE TABLE state (
    key TEXT PRIMARY KEY,
    value BLOB
);
CREATE TABLE conversations (
    key TEXT PRIMARY KEY,
    value TEXT
);
EOF

実際には、起動時に migrations テーブルにもDBマイグレーションの適用結果の内容が入っていてカラム変更が行われるためこれも入れておきます。

sqlite3 data.sqlite3 << 'EOF'
INSERT INTO migrations (id, version, migration_time) VALUES
(1, 0, 1755013634),
(2, 1, 1755013634),
(3, 2, 1755013634),
(4, 3, 1755013634),
(5, 4, 1755013634),
(6, 5, 1755013634),
(7, 6, 1755014426),
(8, 7, 1755014426);
EOF

あとは、 auth_kv テーブルに認証データを入れればOKです。 auth_kvkeyvalue の2カラムで構成されていて、 key には codewhisperer:odic:tokencodewhisperer:odic:device-registration の2つのキーのデータがあり、 value は両方ともJSONデータが入っています。 (OIDCのtypoっぽいけど odic で合っています)

codewhisperer:odic:token の中身の例

{
    "access_token": "...",
    "expires_at": "2025-08-13T01:00:00.797792176Z",
    "refresh_token": "...",
    "region": "us-east-1",
    "start_url": null,
    "oauth_flow": "DeviceCode",
    "scopes": [
        "codewhisperer:completions",
        "codewhisperer:analysis",
        "codewhisperer:conversations"
    ]
}

codewhisperer:odic:device-registration の中身の例

{
    "client_id": "...",
    "client_secret": "<... jwt>",
    "client_secret_expires_at": "2025-11-10T01:00:00Z",
    "region": "us-east-1",
    "oauth_flow": "DeviceCode",
    "scopes": [
        "codewhisperer:completions",
        "codewhisperer:analysis",
        "codewhisperer:conversations"
    ]
}

この内、 ...:token の方だけでも動くんですが、有効期限が数時間しかなくて短いです。 中身に refresh_token も含まれているので自動更新されそうな気がするんですが、...:device-registration が無いとうまく認証トークンが更新されないようでした(PKCE?)。 これに気づかず途中まで動いていたものがいきなり動かなくなったりしてハマりました。

なので両方のデータをINSERTし、 $HOME/.local/share/amazon-q/data.sqlite3 に配置します。

env:
  AMAZONQ_DB_DIR: /home/runner/.local/share/amazon-q

----

sqlite3 data.sqlite3 << EOF
INSERT INTO auth_kv (key, value) VALUES ('codewhisperer:odic:token', '${{ secrets.AMAZONQ_AUTH_TOKEN }}');
EOF

sqlite3 data.sqlite3 << EOF
INSERT INTO auth_kv (key, value) VALUES ('codewhisperer:odic:device-registration', '${{ secrets.AMAZONQ_AUTH_DEVICE }}');
EOF

mkdir -p ${{ env.AMAZONQ_DB_DIR }}
mv data.sqlite3 ${{ env.AMAZONQ_DB_DIR }}

変数とか

  • vars.CODE_REVIEW_APP_ID: GitHub AppのApp IDを入れてください
  • secrets.CODE_REVIEW_APP_PRIVATE_KEY: GitHub AppのPrivate Keyを入れてください
  • vars.AI_REVIEW_GUIDELINE_AMAZONQ: コードレビューのガイドラインを記入します
    • OpenAIの記事と同じように _pr_comment.md を作成するように指示してください。
    • gh pr comment が直接できるかどうかめんどくさくてまだ試せていません)
  • secrets.AMAZONQ_AUTH_TOKEN
  • secrets.AMAZONQ_AUTH_DEVICE
    • 上記の codewhisperer:odic:tokencodewhisperer:odic:device-registration の中身のJSONデータを入れてください

例示の際に分かりやすいようにベタ書きしていますが、スキーマやマイグレーションも vars で管理した方が取り回ししやすい良いかもです。

変数の更新

このままだと3ヶ月くらいで使えなくなるので、定期的に secret を更新する必要があります。 もしローカル環境に保存された認証情報を使う場合は、 GitHub ActionsのSecretを更新するためのコマンドをシェルに登録しておくと楽に更新できそうです。

function amazonq-token {
  # ghコマンドが使えるように envchain とかで保存した GITHUB_TOKEN を使ってください
  # export GITHUB_TOKEN=$(envchain my-github sh -c 'echo $GITHUB_TOKEN')

  local dbfile="$HOME/Library/Application Support/amazon-q/data.sqlite3"
  local orgname="my-org"

  gh secret list --org $orgname || return

  AMAZONQ_AUTH_TOKEN="$(sqlite3 ${dbfile} "SELECT value FROM auth_kv WHERE key = 'codewhisperer:odic:token';")"
  AMAZONQ_AUTH_DEVICE="$(sqlite3 ${dbfile} "SELECT value FROM auth_kv WHERE key = 'codewhisperer:odic:device-registration';")"

  gh secret set GLOBAL_AMAZONQ_AUTH_TOKEN --body "${AMAZONQ_AUTH_TOKEN}" --org ${orgname}
  gh secret set GLOBAL_AMAZONQ_AUTH_DEVICE --body "${AMAZONQ_AUTH_DEVICE}" --org ${orgname}
}

Wrapping up

この子はCodexちゃんと違って素直な子でしっかり絵文字使ってくれます。偉い。かわいい。

Amazon Q によるコードレビュー

きちんと仕事してくれます。

関連

compasscorp.hatenablog.com

同じフォーマットで1日連続3投稿できました。

PR

エンジニアの人、助けてください。

www.wantedly.com

GitHub ActionsでOpenAI Codex CLIによるGitHub PR Reviewを実行する

前々回のDevin, 前回のGemini CLIに続き、 またまた同じような記事で当てこすり続けます。 今回は OpenAI Codex CLIです。

背景・雑感

GPT-5が出たのに合わせてトライしてみました。 GPT-5は期待外れみたいな声も多いですが、期待していなかった身としては順当に安くて使いやすいので嬉しいです。

モデルも重要ですが、同じくらいツール(≒プロンプト+ワークフロー)も重要だと思っていたおりに、Codex CLIも試してみたい気持ちが高まっていたのもあり、ちょうど良いタイミングでした。 元々CodexはPro/Teamプラン限定のイメージで使えないと思っていたんですが、Codex CLIはAPI利用で自由に使えることに気づいてしまいました。思い込みって怖いですね。

tl;dr

↓のような感じで設定するだけ

jobs:
  openai_review:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      # GitHub App を使う場合は↓、使わない場合は GITHUB_TOKEN を使う(おそらくpermission設定は必要)
      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ vars.CODE_REVIEW_APP_ID }}
          private-key: ${{ secrets.CODE_REVIEW_APP_PRIVATE_KEY }}

      - name: Checkout repository
        uses: actions/checkout@v4
        env:
          token: ${{ steps.app-token.outputs.token }}

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

      - name: Setup Node
        uses: actions/setup-node@v4

      - name: Install codex
        run: |
          pnpm add -g @openai/codex

      - name: Run command
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: |
          git fetch origin ${{ github.base_ref }}:${{ github.base_ref }}
          git diff ${{ github.base_ref }} HEAD > _pr_diff.txt
          codex exec -m "${{ vars.AI_REVIEW_OPENAI_MODEL }}" --full-auto "$(printf '%s' '${{ vars.AI_REVIEW_GUIDELINE_OPENAI }}')"

      # codex で指示した _pr_comment.md が作られていることを想定している
      - name: Send comment to PR
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          cat _pr_comment.md
          gh pr comment ${{ github.event.pull_request.number }} --body-file _pr_comment.md

補足

変数とか

  • vars.CODE_REVIEW_APP_ID: GitHub AppのApp IDを入れてください
  • secrets.CODE_REVIEW_APP_PRIVATE_KEY: GitHub AppのPrivate Keyを入れてください
  • vars.AI_REVIEW_OPENAI_MODEL: 使用するOpenAIモデルを指定してください。
    • gpt-5-minigpt-5
  • vars.AI_REVIEW_GUIDELINE_OPENAI: コードレビューのガイドラインを記入します
  • secrets.OPENAI_API_KEY: OpenAI API Keyを入れてください

Codex CLIの制限

ネットワークアクセスが入るコマンドの利用が制限されていて、Codex単体に処理を全て依頼することが難しそうでした。 そのため、 git fetchgit diff をしておき、ファイル差分を事前に出しています。 config.toml に設定を追加すると制限を回避できるみたいな記載があったのですが、うまくいきませんでした。

[sandbox_workspace_write]
network_access = true

追加しても↓みたいにいちいち尋ねてきます。私と同じ指示待ち人間。

補足(行コメントの投稿について)
- この環境からは GitHub に行コメントを直接投稿できないため、もし必要なら私が作成した「ファイル別の具体的改善案」を行コメント形式で整形して提供します(そのテキストを `gh api` で PR の行コメントとして投稿するか、GUI で貼り付けてください)。行コメントを自動で作るスクリプトが必要なら生成します。

次に私ができること(選択肢)
- A) 指摘事項を行コメント形式(ファイル/行指定付き)で出力します。→ あなたが `gh api` で投稿するか、GUIでコピペ可能。
- B) PR 全体のフォーマルなレビュー本文(より詳細)を作成します(そのまま `gh pr review --body` に貼れる)。
- C) 追加で特定ファイルの更に細かいチェック(例: YAML の schema 検証や action 入力/出力の厳密チェック)を行う。

どれを希望しますか?また、私が用意した上の要約をそのまま `gh pr review` 実行してもらって構いません。

なので諦めて、レビューの要旨をMarkdownで出力して、それを手動で gh pr comment で投稿するようにしました。

プロンプトには以下のような文言を追加しています。

**あなたが直接 gh コマンドが使えない場合は、 gitコマンドや github actions の変数から内容を取得することを試みてください**
**コード差分は `git diff ${{ github.base_ref }} HEAD` で取得できるはずで、その結果は `_pr_diff.txt` に既に保存しています**
**レビュー結果をMarkdownファイルとしてカレントディレクトリの `_pr_comment.md` に出力してください。私が `gh pr comment ${{ github.event.pull_request.number }} --body-file _pr_comment.md` と実行するだけでコメントが送信できるようにしてください**
**レビュー結果はMarkdownの機能をフル活用してください。絵文字や太字、Headingやコードブロックを適切に使用してください**
**レビュー結果は結果のみを簡潔に記述してください。結果は「重大な問題」「修正が必要な問題」「確認事項」「推奨事項」のカテゴリに分けて詳細に列挙してください。カテゴリはHeading2を使ってください。結果が存在しない場合は何も記載しないでください**
**最後に安全にマージ可能かどうかのスコア評点を5段階で採点してください**

デバッグのために cat _pr_comment.md を実行コマンドに追加していますが、 今のところはこれで成功しているので抜いても良いかもしれません。

Wrapping up

簡単にできると思ったら、ネットワーク制限設定でつまづき、 最終的に ファイル出力 + 手動実行 という形に落ち着きました。 Workaroundな感じで避けたかったのですが、意外に応用が効くのでこれはこれで良いのかもと思っています。

gpt-5-mini を使っていますが、他のツール・モデルのレビューと比べるとちょっとドライな感じがします。 「絵文字使って」っていってるのに全然使ってくれません。

OpenAI Codex CLIのレビュー

でも、これはこれでたまりません。ありがとうございます。

関連

compasscorp.hatenablog.com

前回から同じ内容でコードコピペ + 画像作成するだけだったので経営会議中に1hで書き終わりました。単純作業タスク最高です。

PR

エンジニアの人、助けてください。

www.wantedly.com

GitHub ActionsでGemini CLIによるGitHub PR Reviewを実行する

前回のDevinに続き、 GitHub ActionsでGemini CLIを使ってGitHub PR Reviewをする方法について記載します。

** Disclaimer: 下書きを6/29に9割書いて放置してました。 気づいたら8/15になってました。黒夢のライブも始まっています。タイムマシンで6月末になった気分で閲覧してください。たぶん少し古いです。

背景・雑感

Claude Codeに遅れて、ようやくGeminiもAIエージェント型CLIが出てきました。 ただちょっと探してみたところではそれっぽいワークフローがなさそうなので、強引に設定してみることにしました。 (1ヶ月くらいしたら公式で出てきそうなので、それまでのつなぎの想定)

Gemini CLIは比較的新しく、まだGitHub Actionsとの連携が整備されていない状況です。 とはいえ、CLIツールとして使えるので、GitHub Actionsから直接呼び出すことで自動レビューを実現できます。

tl;dr

↓のような感じで設定するだけ

env:
  # 0.1.18, 0.1.19だと run_shell_command のエラーが発生するので指定 @2025-08
  GEMINI_CLI_VERSION: 0.1.17

jobs:
  gemini_review:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      # GitHub App を使う場合は↓、使わない場合は GITHUB_TOKEN を使う(おそらくpermission設定は必要)
      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ vars.CODE_REVIEW_APP_ID }}
          private-key: ${{ secrets.CODE_REVIEW_APP_PRIVATE_KEY }}

      - name: Checkout repository
        uses: actions/checkout@v4
        env:
          token: ${{ steps.app-token.outputs.token }}

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

      - name: Setup Node
        uses: actions/setup-node@v4

      - name: Install gemini-cli
        run: |
          pnpm add -g @google/gemini-cli@${{ env.GEMINI_CLI_VERSION }}

      - name: Run command
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
        run: |
          gemini -y -m "${{ vars.AI_REVIEW_GEMINI_MODEL }}" -p "$(printf '%s' '${{ vars.AI_REVIEW_GUIDELINE_GEMINI }}')"

補足

変数とか

  • vars.CODE_REVIEW_APP_ID: GitHub AppのApp IDを入れてください
  • secrets.CODE_REVIEW_APP_PRIVATE_KEY: GitHub AppのPrivate Keyを入れてください
  • vars.AI_REVIEW_GEMINI_MODEL: 使用するGeminiモデルを指定してください。
    • gemini-2.5-flashgemini-2.5-pro
  • vars.AI_REVIEW_GUIDELINE_GEMINI: コードレビューのガイドラインを記入します
  • secrets.GEMINI_API_KEY: Google AI StudioのAPI Keyを入れてください。

pnpm

pnpmでインストールしていますがかなり早いです。 試行していた際はそのままnpmでインストールすると20~25sかかっていましたが、 pnpmにすると4~5sでインストールできます。早すぎぱない。 もう一生ついていきます。

pnpm add -g @google/gemini-cli

認証方法

Gemini CLIでは3種類の認証方法が使えます:

  1. Google Auth - Googleアカウントでの認証
  2. Gemini API Key - Google AI StudioのAPI Keyを使った認証
  3. Vertex AI - Google CloudのVertex AIを使った認証

最初はVertex AIで使おうとしていたんですが、モデルのデフォルトのQuota が低くてすぐエラーになって使いづらかったのでAPI Keyを使う形にしました。今は解消しているかもしれません。

プロンプト・printf

GitHub ActionsのOrganization variables, secretsにプロンプトを入れていますが、改行やクオートの取り扱いでエラーが出やすかったのでprintfを使って変数を展開しています。

Wrapping up

Gemini CLIを使ったGitHub PR Reviewは、比較的シンプルに設定できました。 他のツールと違い制限が少ないため自由に色々使えると思います。

レビューの精度に関してはまだ微妙な印象で、、プロンプトの工夫が必要かもしれません。

Gemini CLIのレビュー

個人的には、PR AgentやDevinと組み合わせて多層的なレビューを行うことで、より効果的なコードレビューが実現できると考えています。 次回はOpenAI Codexでのプルリクレビューについて書きます。

compasscorp.hatenablog.com

PR

エンジニアの人、助けてください。

www.wantedly.com

GitHub PR Review を自動でDevinにお願いする

お久しぶりです、羅針盤の森川です。 前回の記事から大分時間が経ってしまいました。
そう、それはまるで(以下略)
ということで今回はDevinを使ってGitHubのPRを自動でレビューする方法をご紹介します。

tl;dr

以下のようなAPIリクエストを送信するだけです。

curl -X POST "https://api.devin.ai/v1/sessions" \
     -H "Authorization: Bearer $DEVIN_API_KEY" \
     -H "Content-Type: application/json" \
     -d "{\"playbook_id\": \"playbook-000000000000000000000000\", \"prompt\": \"以下のプルリクエストのコードをレビューしてください。\nPR: https://github.com/your-org/your-repo/pull/123\nレビュー完了後は必ず 'gh pr review' を実行してフィードバックを残してください。\"}"

背景

最近も(GitHub) Copilotでレビュー依頼できるようになっていますが、 羅針盤では元々PR AgentでAIレビューを実行していました。
とはいえ普通に使っているとドメインロジックを前提としたレビューは難しく、わざわざそこの整備にリソースをかけたくはありませんでした。
(進歩が早いので、メンテで苦しんでいる間にGitHubや大手プラットフォーム側で対応する可能性が高いと思っていた)

そんな折、3月末頃にDevinでWiki機能がベータリリースされたので、もっとうまくレビューしてくれるかもという淡い期待を持ちながら自動レビューを追加してみることにしました。
また、DevinのACUsも余り気味だったので 無駄金を捨てている それをもっと活用したいという強いキモチもありました。

前提条件

  • Devin Teamプラン以上を契約していること
    • CoreプランではAPIが使えず...

設計と実装

ACUsが余っているとはいえ無駄打ちもしたくなかったので、一定の複雑さやコード量があった場合のみ依頼するようにしました。 ここでは既にPR Agentを導入しており、さらにPR Agentで5段階のラベル Review effors x/5 を付与しているので、それを元にDevinに依頼するようにしました。

実行の流れは以下のようになります。

  • (1) プルリクエスト作成
  • (2) PR Agentによるレビューが実行される
  • (3) PR Agentによって3/5以上のReview effortsのラベルが付与された場合に ai/review ラベルを追加付与
  • (4) ai/review ラベルが付与された場合にDevinに依頼

なお、5月中にGitHub Actionsを統一してほぼ全てReusable workflow化しており、実運用しているYAMLと微妙に違うので、動かなかったらごめんなさい;;

1. DevinのPlaybookを作成

Playbooksの設定画面から以下のようなPlaybookを適当に作成します。 (サンプルを元にAIでブラッシュアップしてもらったもの、のはず...)

## 概要 (Overview)

主なタスクは、指定されたプルリクエスト(PR)に対して、徹底的なコードレビューを実施することです。あなたの責任は、コードの変更点を分析し、潜在的な問題を特定し、改善点を提案し、一般的なベストプラクティスおよび(もし提供されていれば)プロジェクト固有のガイドラインへの準拠を確認することです。あなたのレビューは、開発者にとって建設的で、明確で、実行可能なものでなければなりません。あなたはレビューコメントを提供することに専念し、コードの変更、テストの実行、PRのマージは行いません。

## ユーザーから必要な情報 (What's Needed From User)

- レビュー対象のプルリクエスト(PR)のURL。
- (任意)レビューで特に重点を置いてほしい箇所。
- (任意)プロジェクト固有のコーディング規約、コントリビューションガイドライン、または関連するコンテキストへのリンク。

## 手順 (Procedure)

### 1\. 準備とコンテキストの理解

1-1.  **PR情報の取得:** 提供されたPRのURLを使用して、PRに関する詳細情報(例: `gh pr view <PR_URL>` を使用)を収集します。PRのタイトル、説明、および関連付けられたIssueを理解し、変更の目的と範囲を把握します。
1-2.  **コードのチェックアウト:** PRのブランチをローカルにチェックアウトし(`gh pr checkout <PR_NUMBER>`)、コードを簡単にナビゲートおよび検査できるようにします。比較のためにベースブランチをメモしておきます。
1-3.  **差分の分析:** PRによって導入されたコード変更(`git diff <base_branch>...<head_branch>` または `gh pr diff <PR_NUMBER>`)を注意深く調べます。変更されたファイルとセクションを特定します。
1-4.  **関連コードの確認(必要な場合):** 変更が複雑な既存システムと相互作用する場合、潜在的な影響を理解し、変更がうまく統合されることを確認するために、周辺のコードを簡単にレビューします。

### 2\. コードレビューの実施

2-1.  **体系的なレビュー:** コードの変更をファイルごと、またはコミットごとにレビューします。以下の側面(ただし、これらに限定されません)に焦点を当てます:
    -   **機能性と正確性:** コードは記載された目的を達成しているように見えますか?明らかなロジックエラー、off-by-oneエラー、または処理されていないエッジケースはありませんか?
    -   **可読性と保守性:** コードは理解しやすいですか?変数名や関数名は明確で意味のあるものですか?コードが過度に複雑になっていませんか?コメントは適切に使用されていますか(「何を」ではなく「なぜ」を説明しているか)?
    -   **一貫性:** コードスタイルはプロジェクト内の既存のコードと一致していますか?(フォーマット、命名規則などを考慮)
    -   **ベストプラクティス:** コードは使用されている言語、フレームワーク、ライブラリの一般的なベストプラクティスに従っていますか?非推奨の機能が使用されていませんか?
    -   **セキュリティ:** 明らかなセキュリティ脆弱性はありませんか?(例: ユーザー入力の処理、インジェクション攻撃の可能性、認証情報の不安全な取り扱い)_注: これは予備的なチェックであり、重要なアプリケーションには専用のセキュリティ監査が必要な場合があります。_
    -   **パフォーマンス:** 明らかなパフォーマンスのボトルネックはありませんか?(例: 非効率なループ、ループ内での不要なデータベースクエリ)
    -   **エラー処理:** エラー処理は堅牢ですか?コードは潜在的な障害を予期し、適切に処理していますか?
    -   **テスト容易性:** コードはユニットテストを比較的容易に記述できるような構造になっていますか?
2-2.  **コメントの作成:** フィードバックをレビューコメントとして準備します。コメントが以下の点を満たしていることを確認します:
    -   **具体的:** コメントがどのファイルのどの行(複数行)に関するものか明確に示します。
    -   **明確:** 問題点や提案を簡潔に述べます。
    -   **建設的:** _なぜ_ それが問題なのかを説明し、可能であれば具体的な改善策や代替案を提案します。フィードバックは丁寧な言葉遣いで行います。
    -   **実行可能:** 開発者があなたのコメントに基づいて何をすべきかを理解できるようにします。
    -   **バランス:** うまく実装されている部分を見つけたら、肯定的なフィードバックを残すことも検討します(任意ですが推奨されます)。

### 3\. レビューの提出

3-1.  **コメントの投稿:** 適切なツール(例: `gh pr review`)を使用して、GitHub PRに直接コメントを投稿します。コメントは行ごとに、または単一のレビュー提出の一部として追加できます。
3-2.  **全体レビューの提出:** PRに対するあなたの全体的な評価(例: 「Approve」(承認)、「Request Changes」(変更要求)、または「Comment」(コメントのみ))を提出します。必要に応じて、最も重要な点を強調する簡単な要約コメントを含めます。
3-3.  **ユーザーへの通知:** レビューが完了したことをユーザーに通知します。PRへのリンクを提供し、主な指摘事項や全体的な評価を簡単に要約します。

### 4\. フォローアップ対応(必要な場合)

4-1.  **修正内容の再レビュー:** 開発者があなたのフィードバックに基づいて変更をプッシュした場合、問題が満足に対処されたことを確認するために更新内容をレビューします。必要に応じて、追加のコメントや承認を提供します。

## タスク完了の要件 (Task Specification)

- レビューコメントは、GitHub PRの関連するコード行に直接投稿されていること。
- GitHubコメントは具体的、明確、建設的、かつ実行可能であること。
- レビューは、正確性、可読性、一貫性、セキュリティ(基本的なチェック)、ベストプラクティスなどの側面をカバーしていること。
- プロジェクト固有のガイドラインが提供されていた場合、レビューでそれらが考慮されていること。
- GitHubコメントは**自然な日本語で書かれている**こと。
- 全体的な**レビューステータス(Approve,  Request changes, Comment)が提出されている**こと。
  - レビューステータスの提出は1回のレビューセッションで1度だけ行うこと。複数回行う必要がある場合はまとめて実行するか、前回のコメントをEditすること。
- **指摘事項がなくても全体的なフィードバックをGitHubコメントに投稿する**こと

## 禁止事項 (Forbidden Actions)

- PRブランチのコードを直接**変更しないこと**。
- プルリクエストを**マージしないこと**。
- レビューコンテキストの一部として特に指示がない限り、テストを**実行しないこと**。
- 曖昧で役に立たない、または攻撃的なコメントを**しないこと**。
- PRへのレビューコメント追加の範囲を超えて、リポジトリに**いかなる変更も加えないこと**。
- 重大な問題が未解決のままPRを**承認しないこと**(明示的に指示された場合を除く)。

## ヒントと推奨事項 (Advice and Pointers)

- 効率化のためにGitHub CLI (`gh`) を積極的に活用すること:
  -   `gh pr view <PR_URL>`: PRの詳細を表示。
  -   `gh pr checkout <PR_NUMBER>`: PRブランチをチェックアウト。
  -   `gh pr diff <PR_NUMBER>`: 差分を表示。
  -   `gh pr review <PR_NUMBER>`: レビューを提出。行コメントには `--comment`、要約には `--body` のようなフラグを使用。
  -   例: `gh pr review <PR_NUMBER> --comment "ここにもっと説明的な変数名を使用することを検討してください。" --line <行番号> --path <ファイルパス>`
  -   例: `gh pr review <PR_NUMBER> --request-changes --body "全体的には良好に見えますが、エラー処理と可読性に関していくつかの点に対処しました。特に \`var a = b;\` は \`const a = b;\` への置き換えを検討してください。その他はインラインコメントを参照してください。"`
  - `gh pr review` 実行時のMarkdown記述に含まれるバッククォート(`) はリクエストが正常に送られるように必ずバックスラッシュでエスケープ (\'foobar\')すること。
- 明らかに正しい修正差分を考えついた場合はGitHubコメントの提案機能を使うこと。
  - 特に関数コメント・メソッドコメントが付いていなかったり、他からのコピペでコメントが間違っている場合はコメント提案をしてあげること。
- 変更を提案する際は、その提案の背後にある理由を説明すること。
- コードの一部について意図が不明な場合は、コメントで明確化のための質問をすること。
- フィードバックに優先順位をつけること: マイナーなスタイルの指摘よりも、最も重要な問題(例: 正確性、セキュリティ)にまず焦点を当てること。
- レビューに関連するコンテキストを提供する可能性のある、リポジトリ内の既存のドキュメント(`README.md`、`CONTRIBUTING.md`、 `.cursor/*`、スタイルガイドなど)を確認すること。
- PRのスコープ(範囲)を意識すること。現在の変更がそれを必要としない限り、関連性のない大規模なリファクタリングを提案することは避けること。

2. ラベル付与時にDevin APIを呼び出すワークフロー

まずは ai/review というラベルがPRに付与された際に発火して、 Devin APIを呼び出すGitHub Actionsのワークフローを作成します。

name: "[PR] Devin Review"

on:
  # プルリクエストにラベルが付与されたときにトリガー
  pull_request:
    types: [labeled]

env:
  label_name: ai/review
  playbook_id: playbook-0000000000000000

jobs:
  request_devin_review:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      issues: write
    steps:
      - name: Request Devin Code Review
        # 指定されたラベルが付与されている場合のみ実行 (i.e. `ai/review`)
        if: ${{ github.event.label.name == env.label_name }}
        env:
          DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
          PR_URL: ${{ github.event.pull_request.html_url }}
        run: |
          JSON_PAYLOAD=$(cat <<EOF
          {
            "playbook_id": "${{ env.playbook_id }}",
            "prompt": "以下のプルリクエストのコードをレビューしてください。\nPR: ${PR_URL}\nレビュー完了後は必ず 'gh pr review' を実行してフィードバックを残してください。"
          }
          EOF
          )

          curl -X POST "https://api.devin.ai/v1/sessions" \
               -H "Authorization: Bearer $DEVIN_API_KEY" \
               -H "Content-Type: application/json" \
               -d "$JSON_PAYLOAD"

これで手動で ai/review ラベルを付与するとDevinに依頼できるようになりました。

3. PR Agent側で Review efforts x/5 ラベルが付与された際に ai/review ラベルを付与するワークフロー

次にPR Agentと組み合わせて、 Review efforts x/5 ラベルが付与された際に ai/review ラベルを付与するワークフローを作成します。 ここでは 3/5以上のラベルが付与された場合に ai/review ラベルを付与するようにします。

name: "[PR] PR Agent"

on:
  pull_request:
    types: [opened, reopened, ready_for_review]
  issue_comment:
    types: [created]

permissions:
  issues: write
  pull-requests: write
  id-token: write

env:
  ai_review_label_name: ai/review

jobs:
  pr_agent_job:
    # bot によるPR作成・コメントはスキップ
    if: ${{ !endsWith(github.actor, '[bot]') }}
    runs-on: ubuntu-latest
    timeout-minutes: 8
    name: Run pr agent on pull request
    steps:
      - id: "auth"
        uses: "google-github-actions/auth@v2"
        with:
          # ※ ここではOIDC認証にしてますが、年始くらいからVertexAIの動作がバグってるので今はService Account JSONを使っています。(LiteLLMにうまくプロジェクトIDが渡せてなさそう)
          # あと気になる人は project_id とかにsecrets使ってください
          workload_identity_provider: projects/000000000000/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider
          service_account: github-vertex-ai-pull-request@example.iam.gserviceaccount.com
          project_id: example
          access_token_lifetime: 400s

      - name: PR Agent action step
        id: pragent
        uses: Codium-ai/pr-agent@main
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_ACTION_CONFIG.AUTO_REVIEW: true
          GITHUB_ACTION_CONFIG.AUTO_IMPROVE: true
          PR_REVIEWER.EXTRA_INSTRUCTIONS: >-
            ${{ vars.PR_AGENT_INSTRUCTION_COMMON }}
          PR_DESCRIPTION.EXTRA_INSTRUCTIONS: >-
            ${{ vars.PR_AGENT_INSTRUCTION_COMMON }}
          PR_CODE_SUGGESTIONS.EXTRA_INSTRUCTIONS: >-
            ${{ vars.PR_AGENT_INSTRUCTION_COMMON }}
          PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 3
          PR_ADD_DOCS.EXTRA_INSTRUCTIONS: >-
            ${{ vars.PR_AGENT_INSTRUCTION_COMMON }}
          PR_UPDATE_CHANGELOG.EXTRA_INSTRUCTIONS: >-
            ${{ vars.PR_AGENT_INSTRUCTION_COMMON }}
          PR_TEST.EXTRA_INSTRUCTIONS: >-
            ${{ vars.PR_AGENT_INSTRUCTION_COMMON }}
          PR_IMPROVE_COMPONENT.EXTRA_INSTRUCTIONS: >-
            ${{ vars.PR_AGENT_INSTRUCTION_COMMON }}
          # モデル設定(コスパ重視 & 完了が早いのでGemini 2.5 Flash)
          CONFIG.MODEL: ${{ vars.PR_AGENT_MODEL }}
          CONFIG.MODEL_TURBO: ${{ vars.PR_AGENT_MODEL }}
          CONFIG.FALLBACK_MODELS: ${{ vars.PR_AGENT_MODEL_FALLBACK }}
        #   VERTEXAI.VERTEX_PROJECT: example


  pr_labeler:
    needs: pr_agent_job
    runs-on: ubuntu-latest
    steps:
      # pr_agent_jobによって pull_request のラベル に 'Review effort [1-5]: 3' 以上のラベルが付与されているかチェック
      # PR Agentのバージョンによる仕様差異のせいか形式が `[1-5]: 3` だったり `3/5` だったりしていたので、両方入れてます
      - name: Check current label
        id: label_result
        uses: actions/github-script@v7
        with:
          script: |
            const labels = await github.rest.issues.listLabelsOnIssue({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number
            })
            console.log(labels.data)
            validLabels = {
              'review effort [1-5]: 3': true,
              'review effort [1-5]: 4': true,
              'review effort [1-5]: 5': true,
              'review effort 3/5': true,
              'review effort 4/5': true,
              'review effort 5/5': true,
            }
            for (const label of labels.data) {
              const s = label.name.toLowerCase()
              if (s == '${{ env.ai_review_label_name }}') {
                return "false"
              }
              if (validLabels[s]) {
                return "true"
              }
            }
            return "false"
          result-encoding: string
      - name: Add '${{ env.ai_review_label_name }}' label
        uses: actions/github-script@v7
        if: ${{ steps.label_result.outputs.result == 'true' }}
        with:
          # ここには任意のGitHubトークンを設定してください。
          # Fine-graded Tokenの権限は `Pull requests` の Read and write が必要です。
          # ※ PRデフォルトの secrets.GITHUB_TOKEN を使うと後続のワークフローが発火されない
          github-token: ${{ secrets.GH_TOKEN_FOR_LABEL }}
          script: |
            github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              labels: ['${{ env.ai_review_label_name }}']
            })

Wrapping up

これでPRに ai/review ラベルが付与された際にDevinに依頼できるようになり、 PR Agentが自動的に Review efforts x/5 ラベルを付与してくれ、3/5以上のラベルが付与されたPRに対してDevinに依頼できるようになりました。

弊社の環境だと消費ACUsは大体0.6前後になることが多く、多いときは1.x程度になります。
レビュー観点という意味では価値がありますが、個人的には精度はまだまだ満足いくものではなく、 もっと固有のドメイン知識から指摘をして欲しいと思っています。
ドキュメント・Wikiをもっと整備すれば良いのかもしれませんが、 この辺りはDevinに限らず、1年以内に勝手に対応して自動的に解消するのではないかと楽観的に思ってます。
PR AgentやCopilotのレビューに引きづられている印象を受けるので、プロンプト・Playbookで改善する部分もまだまだありそうです。

とりあえず PR Agent (Gemini 2.5 Flash) + Copilot + Devin の3つでレビュー依頼していて、 それぞれ微妙に違った観点でレビューしてくれていて、どれかが見逃したものを指摘してくれることもあるので、多層構造というかセーフティネット的な役割を果たしてくれていると感じています。

あまりチューニングできていないので、いい感じのPlaybookができたらぜひ教えていただけると我々万歳百唱で喜びます。