DartでGitHubのAIコードレビューBotを開発しよう!

AIコードレビュー

はじめに

こんにちは!今回は、DartとGitHubを使ってAIがコードレビューしてくれるBotを開発する方法を解説します。このBotは、GitHubのプルリクエストに対してGeminiというAIモデルを使ってコードレビューを自動的に行ってくれます。しかも、利用は無料です!

この記事では、Botの中身の構造を詳しく説明し、初心者でも完璧に内容を理解できるようにしています。章や箇条書き、コードブロックを多用して可読性を高め、適宜嚙み砕いた丁寧な解説を挟んでいます。さらに、コードブロックには適宜コメントを付与して、どの機能がどこにあるのかわかりやすくしています。

それでは、さっそくBotの開発に取り掛かりましょう!

プロジェクトの構成

まずは、プロジェクトのディレクトリ構成を見ていきましょう。

OS: nt
Directory: C:\Prj\code_sensei_base

├─ routes/
│  ├─ index.dart
├─ analysis_options.yaml
├─ pubspec.yaml
├─ README.md
  • routes/: HTTP リクエストのハンドラーが格納されているディレクトリ
    • index.dart: メインのリクエストハンドラー
  • analysis_options.yaml: Dart Analyzer の設定ファイル
  • pubspec.yaml: プロジェクトの依存関係やメタデータを管理するファイル
  • README.md: プロジェクトの説明を記述するファイル

設定ファイル

次に、各設定ファイルの中身を見ていきましょう。

analysis_options.yaml

include: package:very_good_analysis/analysis_options.5.1.0.yaml
analyzer:
  exclude:
    - build/**
linter:
  rules:
    file_names: false

このファイルでは、Dart Analyzer の設定を行っています。very_good_analysis パッケージの設定を include し、build/ ディレクトリを除外しています。また、file_names ルールを無効にしています。

pubspec.yaml

name: code_sensei_base
description: An new Dart Frog application
version: 1.0.0+1
publish_to: none

environment:
  sdk: ">=3.0.0 <4.0.0"

dependencies:
  dart_frog: ^1.1.0
  dart_jsonwebtoken: ^2.14.0
  dotenv: ^4.2.0
  github: ^9.24.0
  google_generative_ai: ^0.4.1
  http: ^1.2.1

dev_dependencies:
  mocktail: ^1.0.0
  test: ^1.19.2
  very_good_analysis: ^5.1.0

このファイルでは、プロジェクトの依存関係やメタデータを管理しています。主な依存パッケージは以下の通りです。

  • dart_frog: Dart Frog フレームワーク
  • dart_jsonwebtoken: JSON Web Token の生成と検証
  • dotenv: 環境変数の読み込み
  • github: GitHub API クライアント
  • google_generative_ai: Google の Generative AI API クライアント
  • http: HTTP リクエストの送信

開発時の依存パッケージとして、mocktail(モックライブラリ)、test(テストフレームワーク)、very_good_analysis(静的解析設定)が使用されています。

README.md

# code_sensei_base

[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
[![Powered by Dart Frog](https://img.shields.io/endpoint?url=https://tinyurl.com/dartfrog-badge)](https://dartfrog.vgv.dev)

An example application built with dart_frog

[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis

このファイルは、プロジェクトの説明を記述するためのものです。ライセンスやスタイルのバッジが表示されています。

メインのリクエストハンドラー

プロジェクトの中心となるのが、routes/index.dart ファイルです。このファイルでは、GitHub からのWebhookリクエストを処理しています。

Future<Response> onRequest(RequestContext context) async {
  // 環境変数の読み込み
  final env = DotEnv(includePlatformEnvironment: true)..load();

  // GitHub App の秘密鍵(Base64エンコード)
  final base64Pem = env['PEM_BASE64'];

  // GitHub App ID
  final githubAppId = env['GITHUB_APP_ID'];

  // Gemini API キー  
  final geminiApiKey = env['GEMINI_API_KEY'];

  // リクエストボディのパース
  final body = await context.request.body();
  final json = jsonDecode(body) as Map<String, dynamic>;

  // GitHub App のインストールID
  final installationId = json['installation']['id'] as int;

  // アクセストークンの取得
  final token = await accessToken(
    installationId,
    base64Pem,
    githubAppId,
  );

  // GitHub APIクライアントの作成
  final github = GitHub(auth: Authentication.withToken(token));

  // プルリクエストのアクション
  final action = json['action'];

  // リポジトリの所有者とリポジトリ名
  final fullName = json['repository']['full_name'] as String;

  // プルリクエストのデータ
  final pullRequest = json['pull_request'] as Map<String, dynamic>?;

  if ((action == 'opened' || action == 'reopened') && pullRequest != null) {
    // プルリクエストの番号
    final issueNumber = pullRequest['number'] as int?;

    // プルリクエストのファイル差分を取得
    final diffApiUrl =
        'https://api.github.com/repos/$fullName/pulls/$issueNumber/files';
    final diffResponse = await http.get(
      Uri.parse(diffApiUrl),
      headers: {
        'Authorization': 'token $token',
        'Accept': 'application/vnd.github.v3+json',
      },
    );

    if (diffResponse.statusCode == 200) {
      final files = jsonDecode(diffResponse.body) as List<dynamic>;

      for (final file in files) {
        // ファイルの情報を取得
        final filename = file['filename'];
        final status = file['status'];
        final additions = file['additions'];
        final deletions = file['deletions'];
        final changes = file['changes'];
        final patch = file['patch'];

        final diffInfo = {
          'filename': filename,
          'status': status,
          'additions': additions,
          'deletions': deletions,
          'changes': changes,
          'patch': patch,
        };

        // Gemini APIクライアントの作成
        final model = GenerativeModel(
          model: 'gemini-1.5-flash-latest',
          apiKey: geminiApiKey,
        );

        // コードレビューのプロンプトを作成
        final prompt = '次のコードを日本語でレビューして: $diffInfo';
        final content = [Content.text(prompt)];

        // Gemini APIを呼び出してコードレビューを生成
        final response = await model.generateContent(content);

        // コードレビューのコメントを作成
        final comments = <PullRequestReviewComment>[
          PullRequestReviewComment(
            path: diffInfo['filename'].toString(),
            position: 1,
            body: response.text,
          ),
        ];

        // リポジトリの所有者とリポジトリ名を取得
        final owner = fullName.split('/')[0];
        final repo = fullName.split('/')[1];

        // プルリクエストレビューを作成
        final review = CreatePullRequestReview(
          owner,
          repo,
          issueNumber,
          'COMMENT',
          comments: comments,
        );

        // GitHub APIを呼び出してプルリクエストレビューを投稿
        await github.pullRequests.createReview(
          RepositorySlug(owner, repo),
          review,
        );
      }
    }
  }

  return Response(body: 'Success');
}

このハンドラーでは、以下の処理を行っています。

  1. 環境変数から必要な情報(GitHub App の秘密鍵、ID、Gemini API キー)を読み込みます。
  2. リクエストボディをパースして、プルリクエストの情報を取得します。
  3. GitHub App のインストールIDを使って、アクセストークンを取得します。
  4. プルリクエストのファイル差分を取得します。
  5. 各ファイルについて、以下の処理を行います。
    • ファイルの情報(ファイル名、変更内容など)を取得します。
    • Gemini APIを使ってコードレビューを生成します。
    • コードレビューのコメントを作成します。
    • GitHub APIを使ってプルリクエストレビューを投稿します。

以上が、このBotの主な処理の流れです。

まとめ

この記事では、DartとGitHubを使ってAIがコードレビューしてくれるBotの開発方法を解説しました。プロジェクトの構成や設定ファイル、メインのリクエストハンドラーの処理の流れを詳しく説明し、初心者でも完璧に内容を理解できるようにしました。

このBotを使えば、プルリクエストに対して自動的にコードレビューを行ってくれるので、開発の効率化につながります。しかも、利用は無料なので、ぜひ試してみてください!

Dartを使った開発に興味がある方は、ぜひこのプロジェクトをカスタマイズしてみてください。GitHubとの連携やAIを活用した開発など、面白い機能を追加できるはずです。

Happy coding!

参考サイト

コメント

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