コードを書いた直後は、ドキュメントを更新する気力があります。問題はその3ヶ月後です。関数の動作が変わったのにdocstringは初期実装のまま、READMEの手順は古いAPIを参照したまま、という状況に何度も遭遇してきました。
個人開発を続けていると、コードは着実に成長するのにドキュメントだけが置き去りになるパターンが癖のように繰り返されます。「後で書く」は、たいてい「永遠に書かない」と同義です。
そこで最近試みたのが、Gemini API と GitHub Actions を組み合わせた「ドキュメント自動追従パイプライン」です。Pull Request を起点に、変更されたコードを Gemini API に解析させてdocstringの更新案を自動生成し、PRコメントとして投稿する仕組みです。AI が全部書いてくれるわけではありませんが、「この関数、説明変えなくていいですか?」と聞いてくれる仕組みがあるだけで、更新漏れは大幅に減りました。
なぜドキュメントは常に後回しにされるのか
根本的な原因は、コード変更とドキュメント更新が別タスクとして扱われているからだと思います。
コードのテストは CI でブロックできます。型チェックも lint もブロックできます。でも「ドキュメントの鮮度」をCIで自動チェックする仕組みを持っているプロジェクトは、ほとんどありません。「動くコードを最優先」という判断は正しいのですが、その積み重ねで「誰も信頼できないドキュメント」が生まれていきます。
個人開発の場合、6ヶ月後の自分が最初の読者になることが多いです。そのとき「なぜこう書いたのか」が分からないのは、純粋に開発スピードを落とします。
パイプラインの全体像
このパイプラインがやっていることはシンプルです。
- Pull Request が作成・更新されると GitHub Actions が起動する
git diffで変更された Python ファイルを特定する- 差分と現在のコードを Gemini API(Gemini 2.5 Flash)に送信する
- Gemini がdocstringの更新案を返す
- 更新が必要な場合のみ、PRコメントとして提案を投稿する
コードを自動で書き換えるのではなく、「レビューコメントとして提案を投稿する」設計にしているのがポイントです。AIの提案を開発者がチェックしてから適用する流れにすることで、意図しない変更が入り込むリスクを防げます。
関連する実装パターンとして、Gemini API を活用した GitHub Actions CI/CD ガイドも参考にしてください。
Step 1: GitHub Actions ワークフローを作成する
リポジトリに .github/workflows/doc-review.yml を作成します。
# .github/workflows/doc-review.yml
name: AI Document Review
on:
pull_request:
types: [opened, synchronize]
paths:
- '**/*.py' # Python ファイルの変更時のみ発火
jobs:
doc-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write # PR にコメントを書くために必要
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 差分取得のために全履歴が必要
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install google-genai PyGithub
- name: Run AI doc review
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO_NAME: ${{ github.repository }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: python scripts/doc_reviewer.pyGEMINI_API_KEY は、リポジトリの Settings → Secrets and variables → Actions で設定してください。Gemini API キーは Google AI Studio から無料で取得できます。
Step 2: Gemini API でドキュメントを解析するスクリプト
次に scripts/doc_reviewer.py を作成します。これがパイプラインの核心部分です。
# scripts/doc_reviewer.py
import os
import subprocess
from github import Github
import google.genai as genai
# --- 設定 ---
GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]
PR_NUMBER = int(os.environ["PR_NUMBER"])
REPO_NAME = os.environ["REPO_NAME"]
BASE_SHA = os.environ["BASE_SHA"]
HEAD_SHA = os.environ["HEAD_SHA"]
# Flash を選ぶ理由: Pro より約10倍安く、ドキュメントレビュー程度の
# タスクなら精度に大きな差はない。大量のPRを処理するときにコストが効く。
MODEL_ID = "gemini-2.5-flash"
def get_changed_python_files() -> list[str]:
"""PR で変更された Python ファイルの一覧を返す"""
result = subprocess.run(
["git", "diff", "--name-only", f"{BASE_SHA}...{HEAD_SHA}"],
capture_output=True, text=True, check=True
)
return [f for f in result.stdout.strip().split("\n") if f.endswith(".py")]
def get_file_diff(filepath: str) -> str:
"""特定ファイルの差分を取得する(前後5行のコンテキスト付き)"""
result = subprocess.run(
["git", "diff", "-U5", f"{BASE_SHA}...{HEAD_SHA}", "--", filepath],
capture_output=True, text=True, check=True
)
return result.stdout
def get_current_content(filepath: str) -> str:
"""現在のファイル全体を読み取る"""
try:
with open(filepath, "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return "" # ファイル削除の場合は空文字を返す
def review_with_gemini(filepath: str, diff: str, content: str) -> str | None:
"""
Gemini API にドキュメントレビューを依頼する。
更新不要な場合は None を返す。
"""
client = genai.Client(api_key=GEMINI_API_KEY)
prompt = f"""以下の Python ファイルの変更差分と現在のコードを確認してください。
docstring(関数・クラス・モジュールの説明)が変更内容と整合しているか
レビューし、更新が必要な場合のみ改善案を Markdown 形式で返してください。
問題がない場合は「ドキュメントの更新は不要です。」とだけ返してください。
## ファイル名
{filepath}
## 変更差分
```diff
{diff[:3000]}現在のコード
{content[:5000]}出力形式(更新が必要な場合のみ)
-
更新が必要な関数またはクラスの名前
-
現在の docstring の問題点(1〜2行で簡潔に)
-
改善後の docstring の提案(Python コードブロックで) """
response = client.models.generate_content( model=MODEL_ID, contents=prompt, )
suggestion = response.text.strip()
「更新不要」の応答はスキップしてコメントのノイズを減らす
if "更新は不要" in suggestion or len(suggestion) < 30: return None
return suggestion
def post_pr_comment(suggestions: list[tuple[str, str]]) -> None: """提案をまとめて PR にコメントとして投稿する""" if not suggestions: print("提案なし — コメントはスキップします") return
gh = Github(GITHUB_TOKEN)
repo = gh.get_repo(REPO_NAME)
pr = repo.get_pull(PR_NUMBER)
body = "## 🤖 AI ドキュメントレビュー (Gemini)\n\n"
body += "コード変更に基づいてドキュメントの更新を提案します。\n"
body += "_自動提案です。内容を確認してから適用してください。_\n\n---\n\n"
for filepath, suggestion in suggestions:
body += f"### 📄 `{filepath}`\n\n{suggestion}\n\n---\n\n"
pr.create_issue_comment(body)
print(f"✅ {len(suggestions)} 件の提案を PR コメントに投稿しました")
def main(): changed_files = get_changed_python_files()
if not changed_files:
print("変更された Python ファイルがありません")
return
print(f"📂 解析対象: {len(changed_files)} ファイル")
suggestions = []
# 最大10ファイルに制限(大規模リファクタリング時のコスト暴走を防ぐ)
for filepath in changed_files[:10]:
print(f"🔍 {filepath} を解析中...")
diff = get_file_diff(filepath)
content = get_current_content(filepath)
if not diff:
continue
suggestion = review_with_gemini(filepath, diff, content)
if suggestion:
suggestions.append((filepath, suggestion))
print(f" → 更新提案あり")
else:
print(f" → 更新不要")
post_pr_comment(suggestions)
if name == "main": main()
## 実際に動かしてみて分かったこと
このパイプラインを自分のアプリプロジェクトに導入して2週間ほど使ってみました。体感として良かった点と気をつけたい点を正直に共有します。
**特に役立った場面:**
引数が1つ増えた関数で、既存の `Args:` セクションが更新されていないことを毎回指摘してくれました。これは手動レビューで見落としやすいパターンで、実際に「あ、これ書くの忘れてた」と気づいたケースが何度もありました。また、例外の追加と `Raises:` セクションの漏れも一貫して検出してくれます。
**ノイズになるケース:**
docstringが最初から存在しない関数に対しては毎回提案が来ます。「全部書け」と言われているようで最初は少しうんざりしましたが、逆に「この関数、説明が必要な複雑さだな」と気づくきっかけにもなっています。必要に応じて、プロンプトに「docstringが存在しない場合はスキップする」という条件を追加するのも手です。
GitHub PR へのAIコードレビューボットの実装パターンも参考になるかもしれません。こちらはコードの品質に特化した実装です。
## コストとスケーラビリティの見通し
Gemini 2.5 Flash を使った場合、1 PR あたりのトークン消費はおおよそ3,000〜8,000トークンです(変更規模による)。
Gemini API の無料枠は1日15リクエスト、1分あたり100万トークンなので、個人開発のペース(1日に数件のPR)であれば無料枠で運用できます。有料になってもFlashは非常にコストが低く、月100件のPRを処理しても数十円程度で収まる計算です。
スケールする場合は、`changed_files[:10]` の上限を設定して1回の実行コストを抑えるのが現実的です。大規模なリファクタリングでは変更ファイルが数十件になることもありますが、全部を解析する必要はありません。差分が大きいファイルを優先するロジックに変えることもできます。
## 全体を振り返って
まずは既存リポジトリの `.github/workflows/` にこのワークフローを追加してみてください。最初は `paths` フィルタを特定のフォルダ(例: `src/**/*.py`)だけに絞ることで、ノイズを抑えながら動作を確認できます。
このパイプラインの本質は「完璧なドキュメントを自動生成すること」ではなく、「更新漏れを人間の目に届ける仕組みを作ること」にあります。AIの提案を全部適用する必要はありません。「ここ説明変えなくていいっけ」と思い出すきっかけになるだけで、ドキュメントの質は確実に上がります。