Copilot SDKでGitLabのマージリクエストを自動レビューする方法

AI

はじめに

前回の記事では Copilot SDK を使って GitLab の Issue から自動で実装・MR 作成を行うツールを紹介しました。 今回はその続編として、Copilot SDK でマージリクエスト(MR)のコードレビューを自動化するツールを作ってみました。

MR のレビューは開発フローにおいて重要なプロセスですが、レビュー待ちがボトルネックになることも少なくありません。 AI による一次レビューを自動化することで、人間のレビュアーが見る前にセキュリティや論理バグといった基本的な問題を検出できます。 本記事では、このツールの仕組みと Copilot SDK をレビュー用途で使う際のポイントを紹介します。

コードは以下のリポジトリで公開しています。

全体の仕組み

ツールの処理フローは以下のとおりです。

  1. MR を対象に Docker コンテナを起動する
  2. GitLab API から MR の差分・バージョン情報を取得する
  3. 対象リポジトリをクローンする
  4. Copilot SDK でレビューセッションを開始し、差分を送信する
  5. Copilot が差分を解析し、懸念点ごとにインラインコメントを投稿する
  6. 最後にサマリーコメントを MR に投稿する
Docker コンテナ起動
  ├─ GitLab API で MR の差分・バージョン情報を取得
  ├─ GitHub Copilot SDK でレビューセッションを開始
  │    └─ ローカルのリポジトリも参照してコンテキストを把握
  └─ 懸念点ごとに GitLab Discussions API でインラインスレッドを作成

Copilot SDK をレビューに使うメリット

読み取り専用のツールに制限できる

前回の記事では approveAll(全許可)で Copilot にファイル操作やコマンド実行を許可していましたが、レビューではコードを変更する必要がありません。 Copilot SDK の onPreToolUse フックを使えば、許可するツールをホワイトリストで制限できます。

const ALLOWED_TOOLS = [
  "read_file",
  "glob",
  "grep",
  "view",
  "list_directory",
  "search_files",
  "post_review_comment",
  "web_fetch",
  "report_intent",
];

// セッション作成時のフック
hooks: {
  onPreToolUse: async (input) => {
    if (!ALLOWED_TOOLS.includes(input.toolName)) {
      console.warn(`[review] Tool blocked by policy: ${input.toolName}`);
      return {
        permissionDecision: "deny",
        permissionDecisionReason:
          `Only non-destructive tools are allowed during review. "${input.toolName}" was blocked.`,
      };
    }
    return { permissionDecision: "allow" };
  },
},

write_filerun_command のような破壊的なツールはブロックされるため、レビュー中に Copilot がコードを書き換えてしまったり、予期せぬコマンドを実行してしまうリスクを排除できます。 前回の「プログラムのフローで AI の不確実性をカバーする」という考え方を、権限制御の面でさらに強化した形です。

カスタムツールでレビューコメントを構造化できる

Copilot SDK の defineTool を使うと、Copilot が呼び出せるカスタムツールを定義できます。今回は post_review_comment というツールを作成し、Copilot がレビューで見つけた懸念点を GitLab のインラインコメントとして直接投稿できるようにしました。

const postReviewComment = defineTool("post_review_comment", {
  description:
    "Post a code review comment on the GitLab merge request. " +
    "When `file_path` and `line_number` are supplied, the comment appears as an inline diff thread. " +
    "When they are omitted, the comment is posted as a general MR-level discussion.",
  parameters: z.object({
    file_path: z.string().optional()
      .describe("Repository-relative path of the file. Omit for general comments."),
    line_number: z.number().int().positive().optional()
      .describe("Line number in the new version of the file. Omit for general comments."),
    comment: z.string()
      .describe("Concise review comment explaining the concern and how to resolve it."),
  }),
  skipPermission: true,
  handler: async ({ file_path, line_number, comment }) => {
    // GitLab Discussions API へコメントを投稿
    // ...
  },
});

Copilot は差分を読んで懸念点を見つけるたびにこのツールを呼び出し、ファイルパスと行番号を指定してインラインコメントを投稿します。

差分の行番号をマッピング

GitLab のインラインコメント API は、差分の old_line(変更前の行番号)と new_line(変更後の行番号)の正確な値を要求します。 Copilot が指定する行番号は変更後のファイルの行番号なので、unified diff を解析して対応する位置情報を算出する必要があります。

function parseDiffLines(diff: string): Map<number, DiffLineInfo> {
  const map = new Map<number, DiffLineInfo>();
  let oldLine = 0;
  let newLine = 0;

  for (const raw of diff.split("\n")) {
    const hunk = raw.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
    if (hunk) {
      oldLine = parseInt(hunk[1], 10);
      newLine = parseInt(hunk[2], 10);
      continue;
    }

    if (raw.startsWith("+") && !raw.startsWith("+++")) {
      map.set(newLine, { type: "added", old_line: null, new_line: newLine });
      newLine++;
    } else if (raw.startsWith("-") && !raw.startsWith("---")) {
      oldLine++;
    } else if (/* context line */) {
      map.set(newLine, { type: "context", old_line: oldLine, new_line: newLine });
      oldLine++;
      newLine++;
    }
  }
  return map;
}

追加行(+)にはコンテキスト行とは異なる位置情報を渡し、インラインコメントが差分ビュー上の正確な位置に表示されるようにしています。もしインライン投稿に失敗した場合は、フォールバックとして通常のコメントとして投稿するため、レビュー内容が失われることはありません。

レビュー観点のカスタマイズ

Copilot に渡すシステムメッセージでレビューの観点を指定します。

観点
セキュリティSQL インジェクション、認証不備、機密情報の露出
正確性ロジックバグ、未処理の例外、入力バリデーション漏れ
信頼性エラーハンドリング漏れ、リソースリーク
パフォーマンスN+1 クエリ、ホットパスでの無駄な処理
保守性複雑すぎるロジック、デッドコード
破壊的変更API シグネチャ変更、DB スキーマ変更

また REVIEW_LANG 環境変数でレビューコメントの言語を切り替えられます。Japanese を指定すれば日本語でコメントが投稿されるため、日本語のプロジェクトでも自然に運用できます。

使用方法

詳細な使用方法はリポジトリの README を参照してください。以下は簡単な手順です。

前提条件

  • GitHub アカウントが Copilot サブスクリプションに加入していること
  • GitLab のアクセストークンが api スコープで発行済みであること
  • Docker がインストールされていること

1. コンテナイメージのビルド

git clone https://github.com/shutils/gitlab-ci-review.git
cd gitlab-ci-review
docker build -t gitlab-ci-review:latest .

2. 環境変数の設定

実行時に以下の環境変数を設定します。

変数名説明
GITHUB_TOKENGitHub Personal Access Token(Copilot アクセス付き)
GITLAB_TOKENGitLab Access Token(api スコープ)
GITLAB_URLGitLab インスタンス URL。https://gitlab.com の場合は省略可
GITLAB_PROJECT_IDGitLab プロジェクト ID(数値またはURL エンコード済みパス)
GITLAB_MR_IIDレビュー対象のマージリクエスト IID
GITLAB_PROJECT_PATH プロジェクトパス(例: namespace/project)。REPOSITORY_URL 未設定時のクローンに使用
REVIEW_MODEL 使用する Copilot モデル名
REVIEW_LANG レビューコメントの言語(例: Japanese, English

3. 実行

docker run --rm \
  -e GITHUB_TOKEN="$GITHUB_TOKEN" \
  -e GITLAB_TOKEN="$GITLAB_TOKEN" \
  -e GITLAB_URL="https://gitlab.example.com" \
  -e GITLAB_PROJECT_ID="mygroup/myproject" \
  -e GITLAB_MR_IID="42" \
  -e REVIEW_MODEL="gpt-5-mini" \
  -e REVIEW_LANG="Japanese" \
  gitlab-ci-review:latest

まとめ

Copilot SDK を使うことで、MR のコードレビューを自動化できました。 AI レビューは人間のレビューを置き換えるものではありませんが、一次チェックとして導入することでレビューの効率を上げられます。次の機会ではこのツールを CI/CD パイプラインに組み込む方法も紹介できればと思います。

コメント

タイトルとURLをコピーしました