GitHubでAIを強化する | 第3章：コード分析：AIによるインサイト生成

## はじめに

第2章では、**GitHub**にIssueを作成し、Pull Request（PR）にコメントを追加するMCPサーバーを構築し、開発タスク自動化エージェントを実現しました。これにより、AIがIssueやPRを操作できるようになり、チームの効率が向上しました。今回は、この基盤を活用して、GitHubリポジトリのコミットデータを解析する**コード分析エージェント**を構築します。

この第3章では、コミット履歴を分析し、コード変更頻度、主要貢献者、変更パターンのインサイトを生成します。たとえば、AIが「特定のファイルが頻繁に変更されている」ことを検出したり、「特定の開発者が特定のモジュールに集中している」ことを指摘したりできます。コード例とステップごとのガイドで、コード分析AIの構築を体験しましょう。さあ、始めましょう！

## コード分析エージェントとは？

コード分析エージェントは、GitHubリポジトリのコミットデータを解析し、開発プロセスに関するインサイトを提供するAIです。MCPサーバーを介して、以下のような機能を実現できます：
- **変更頻度分析**：ファイルやディレクトリごとの変更頻度を計算。
- **貢献者分析**：開発者ごとのコミット数や変更量を評価。
- **パターン抽出**：コード変更の傾向（例：バグ修正、機能追加）を特定。

### ユースケース
- コード品質：頻繁に変更されるファイルから潜在的な問題を検出。
- チームパフォーマンス：貢献者のワークロードや専門領域を評価。
- リファクタリング支援：変更パターンからリファクタリングの優先順位を提案。

## 開発環境の準備

第2章の環境を基に、以下の準備を行います：
- **Python 3.8以降**、**mcpライブラリ**、**requestsライブラリ**、**Claude Desktop**：これまでと同じ。
- **python-dotenv**：環境変数の管理（既にインストール済み）。
- **GitHubリポジトリ**：分析用のコミットデータを含むリポジトリ。

### GitHubのセットアップ
1. **リポジトリ準備**：
   - 第2章のリポジトリ（例：`yourusername/mcp-test-repo`）を使用。
   - 複数のコミットを追加（例：コード変更、ドキュメント更新、バグ修正）。
   - 異なる開発者（または模擬ユーザー）でコミットを作成。
   - ファイル構造を多様化（例：`src/main.py`、`docs/README.md`）。
2. **GitHubトークンの確認**：
   - 第2章のトークンを使用（スコープ：`repo`）。
   - トークンがコミットデータへの読み取り権限を持つことを確認。
3. **環境変数**：
   第2章の`.env`ファイルに以下を確認：
   ```bash
   GITHUB_TOKEN=your_token
   GITHUB_REPO=yourusername/mcp-test-repo
   ```

## コード例：コード分析用MCPサーバー

以下のMCPサーバーは、GitHubリポジトリのコミットデータを取得し、変更頻度と貢献者を分析します。

```python
from mcp import MCPServer
import os
from dotenv import load_dotenv
import requests
from datetime import datetime, timedelta
from collections import Counter
import statistics

class GitHubAnalysisServer(MCPServer):
    def __init__(self, host, port, token, repo):
        super().__init__(host, port)
        self.token = token
        self.repo = repo
        self.base_url = "https://api.github.com"
        self.headers = {
            "Authorization": f"Bearer {token}",
            "Accept": "application/vnd.github.v3+json"
        }
        self.register_resource("analyze_commits", self.analyze_commits)

    def get_commits(self, limit=100):
        try:
            url = f"{self.base_url}/repos/{self.repo}/commits"
            query = {"per_page": limit}
            response = requests.get(url, headers=self.headers, params=query)
            response.raise_for_status()
            return response.json()
        except Exception as e:
            return {"status": "error", "message": str(e)}

    def get_commit_diff(self, sha):
        try:
            url = f"{self.base_url}/repos/{self.repo}/commits/{sha}"
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()
            files = response.json().get("files", [])
            return [{"filename": f["filename"], "changes": f["changes"]} for f in files]
        except Exception as e:
            return []

    def analyze_commits(self, params):
        try:
            commits = self.get_commits(limit=params.get("limit", 100))
            if isinstance(commits, dict) and "status" in commits:
                return commits

            author_counts = Counter()
            file_changes = Counter()
            timestamps = []
            change_types = Counter()

            for commit in commits:
                author = commit["commit"]["author"]["name"]
                ts = commit["commit"]["author"]["date"]
                timestamp = datetime.fromisoformat(ts.replace("Z", "+00:00"))
                message = commit["commit"]["message"].lower()

                # 貢献者分析
                author_counts[author] += 1

                # ファイル変更分析
                files = self.get_commit_diff(commit["sha"])
                for file in files:
                    file_changes[file["filename"]] += file["changes"]

                # コミット頻度
                timestamps.append(timestamp)

                # 変更タイプ（簡易）
                if "fix" in message or "bug" in message:
                    change_types["bug_fix"] += 1
                elif "add" in message or "feature" in message:
                    change_types["feature"] += 1
                else:
                    change_types["other"] += 1

            # 頻度分析
            if timestamps:
                time_diffs = [(timestamps[i] - timestamps[i+1]).total_seconds() / 3600 
                             for i in range(len(timestamps)-1)]
                avg_interval = statistics.mean(time_diffs) if time_diffs else 0
                commits_per_day = len(timestamps) / ((datetime.now(tz=timestamps[0].tzinfo) - min(timestamps)).total_seconds() / 86400)
            else:
                avg_interval, commits_per_day = 0, 0

            # トップファイル
            top_files = [{"file": f, "changes": c} for f, c in file_changes.most_common(5)]

            return {
                "status": "success",
                "analysis": {
                    "commit_count": len(commits),
                    "avg_commit_interval_hours": f"{avg_interval:.2f}",
                    "commits_per_day": f"{commits_per_day:.2f}",
                    "top_contributors": [{"author": a, "count": c} for a, c in author_counts.most_common(3)],
                    "top_files": top_files,
                    "change_types": [{"type": t, "count": c} for t, c in change_types.items()]
                }
            }
        except Exception as e:
            return {"status": "error", "message": str(e)}

if __name__ == "__main__":
    load_dotenv()
    server = GitHubAnalysisServer(
        host="localhost",
        port=8129,
        token=os.getenv("GITHUB_TOKEN"),
        repo=os.getenv("GITHUB_REPO")
    )
    print("GitHub分析MCPサーバーを起動中: http://localhost:8129")
    server.start()
```

### コードの説明
- **get_commits**：リポジトリのコミットを取得（最大100件）。
- **get_commit_diff**：コミットの変更ファイルと変更行数を取得。
- **analyze_commits**：コミットデータを解析し、以下のインサイトを生成：
  - コミット総数：取得したコミットの数。
  - 平均コミット間隔：コミット間の平均時間（時間単位）。
  - 1日あたりのコミット数：開発の活発さを評価。
  - トップ貢献者：コミット数が多い上位3ユーザー。
  - トップファイル：変更頻度の高いファイル（行数ベース）。
  - 変更タイプ：コミットメッセージから「バグ修正」「機能追加」「その他」を推定。
- **register_resource**：コード分析をリソースとして登録。

### 前提条件
- リポジトリに複数のコミットと異なるファイルが存在。
- GitHubトークンに`repo`スコープが付与されている。
- `.env`ファイルに正しい`GITHUB_TOKEN`と`GITHUB_REPO`が設定済み。

## サーバーのテスト

サーバーが正しく動作するか確認します：

1. **サーバー起動**：
   ```bash
   python github_analysis_server.py
   ```
   コンソールに「GitHub分析MCPサーバーを起動中: http://localhost:8129」と表示。

2. **コード分析のテスト**：
   Pythonでリクエストを送信：
   ```python
   import requests
   import json

   url = "http://localhost:8129"
   payload = {
       "jsonrpc": "2.0",
       "method": "analyze_commits",
       "params": {"limit": 50},
       "id": 1
   }
   response = requests.post(url, json=payload)
   print(json.dumps(response.json(), indent=2, ensure_ascii=False))
   ```
   期待されるレスポンス：
   ```json
   {
     "jsonrpc": "2.0",
     "result": {
       "status": "success",
       "analysis": {
         "commit_count": 50,
         "avg_commit_interval_hours": "3.20",
         "commits_per_day": "8.50",
         "top_contributors": [
           {"author": "Your Name", "count": 30},
           {"author": "Contributor1", "count": 15},
           {"author": "Contributor2", "count": 5}
         ],
         "top_files": [
           {"file": "src/main.py", "changes": 150},
           {"file": "docs/README.md", "changes": 50}
         ],
         "change_types": [
           {"type": "bug_fix", "count": 10},
           {"type": "feature", "count": 20},
           {"type": "other", "count": 20}
         ]
       }
     },
     "id": 1
   }
   ```

## Claude Desktopとの接続

サーバーをClaude Desktopに接続します：

1. **設定ファイルの編集**：
   Claude Desktopの設定ファイル（例：`claude_desktop_config.json`）に以下を追加：
   ```json
   {
     "mcp_servers": [
       {
         "name": "GitHubAnalysisServer",
         "url": "http://localhost:8129",
         "auth": "none"
       }
     ]
   }
   ```

2. **Claudeでテスト**：
   Claude Desktopを起動し、プロンプトを入力：
   ```
   リポジトリのコミットデータを分析してください。
   ```
   レスポンス例：
   ```
   リポジトリ yourusername/mcp-test-repo のコミット分析：
   - コミット数：50
   - 平均コミット間隔：3.20時間
   - 1日あたりのコミット数：8.50
   - トップ貢献者：Your Name（30コミット）、Contributor1（15コミット）、Contributor2（5コミット）
   - 頻繁に変更されたファイル：src/main.py（150行）、docs/README.md（50行）
   - 変更タイプ：機能追加（20）、バグ修正（10）、その他（20）
   ```

## 実装のコツと注意点

- **データ品質**：コミットが少ない場合、分析結果が制限される。十分なデータ（例：50件以上）を用意。
- **レートリミティング**：GitHub APIの制限（例：5000リクエスト/時間）に注意。
- **セキュリティ**：本番環境では、`auth: none`を避け、トークン認証を導入。
- **テスト**：テスト用リポジトリを作成し、本番データに影響を与えない。
- **拡張性**：大量のコミットを処理する場合、キャッシュ（例：Redis）やNLPライブラリ（例：spaCy）でメッセージ分析を強化。

## 試してみよう：挑戦課題
以下の機能を追加して、エージェントを強化してみてください：
- 特定のファイル（例：`src/main.py`）の変更履歴だけを分析するフィルター。
- コミットメッセージの詳細分析（例：キーワード頻度）。
- 分析結果をIssueとしてリポジトリに投稿するツール。

## まとめと次のステップ

この第3章では、GitHubのコミットデータを活用してコード分析エージェントを構築しました。変更頻度や貢献者を分析することで、AIが開発プロセスのインサイトを提供し、コード品質向上を支援できるようになりました。

次の第4章では、GitHubのWebhookを活用して**リアルタイム管理AI**を構築します。たとえば、AIが新しいIssueやPRの作成をリアルタイムで検知し、通知や応答を生成します。リアルタイム管理AIに興味がある方は、ぜひお楽しみに！

---

役に立ったと思ったら、「いいね」や「ストック」をしていただけると嬉しいです！次の章でまたお会いしましょう！