📝

Claude Code + GitHub Actionsでドキュメントを自動整備するワークフローを作った

に公開

はじめに

ドキュメントは書いた瞬間から陳腐化が始まります。この記事では、Claude Code + GitHub Actions を組み合わせて、ドキュメントを自動で整備するワークフローを構築した事例を紹介します。

課題

  1. ドキュメントの陳腐化 - コードは日々更新されるが、ドキュメントの更新は後回しになりがち
  2. 手動更新の負担 - リファクタリングのたびにドキュメントを更新するのは手間がかかる
  3. チーム間の情報共有 - 既存の実装をAndroidエンジニア以外が理解しづらい

自動化の対象

私の担当するAndroidプロジェクトでは、以下の2つのドキュメントを自動整備の対象としました。

  1. モジュール構成ドキュメント - 新しく参画するメンバー向けにプロジェクト構成を説明するもの。直近のリファクタリングで構成が大きく変わり、今後も継続的に見直していく予定があった。
  2. APIドキュメント - iOSアプリと並行開発しており、ソースコードを正とするドキュメントを整備することで、AI活用も含めた開発を円滑に進めたかった。

解決策

Claude Code + GitHub Actions を組み合わせたドキュメント自動整備機能を実装しました。

機能概要

機能名 対象ドキュメント 実行スケジュール
APIドキュメント更新 docs/api-reference.md 毎週月曜 06:00 JST
モジュール構成更新 docs/module-structure.md 毎週火曜 06:00 JST

アーキテクチャ

実装

ファイル構成

.claude/commands/
├── update-api-docs.md      # APIドキュメント更新コマンド
└── update-module-docs.md   # モジュール構成更新コマンド

.github/
├── workflows/
│   ├── api-docs-update.yml     # APIドキュメント更新ワークフロー
│   └── module-docs-update.yml  # モジュール構成更新ワークフロー
└── config/
    ├── api-docs-config.yml     # 監視対象ファイル定義
    └── module-docs-config.yml  # モジュール構成監視設定

Claude Code カスタムコマンド

.claude/commands/update-module-docs.md にClaude Codeのカスタムコマンドを定義しています。

カスタムコマンドは、.claude/commands/ディレクトリにMarkdownファイルを配置することで、/コマンド名として呼び出せるようになる機能です。プロンプトを再利用可能な形で定義できます。

# モジュール構成ドキュメント更新

このコマンドはモジュール構成ドキュメントを生成または更新します。

## 引数
- `$ARGUMENTS`: コマンド呼び出し時に渡された引数に置換されます
  - `docs_exist=true`: 既存ドキュメントを読み込んで変更箇所のみ更新
  - `docs_exist=false`: 新規にドキュメントを作成

## 実行手順

1. `settings.gradle.kts` を読み込み、全モジュールを特定
2. 各モジュールの `build.gradle.kts` から依存関係を抽出
3. 各モジュールのソース構造を確認
4. Markdownドキュメントを生成

## 出力フォーマット

以下の形式でドキュメントを生成してください。

# モジュール構成

## モジュール一覧

| モジュール | 種別 | 説明 |
|-----------|------|------|
| :app | Application | メインアプリケーション |
| :domain | Library | ドメイン層 |
| ... | ... | ... |

## モジュール依存関係図

(Mermaid記法で依存関係図を記載)

## 各モジュールの詳細

### :app
**役割**: ...
**主要パッケージ**: ...
**依存モジュール**: ...

ポイントは、出力フォーマットのテンプレートをコマンド内で詳細に定義している点です。どのような構成でドキュメントを生成するかを明示することで、出力の形式が安定しました。

GitHub Actions ワークフロー

.github/workflows/module-docs-update.yml でワークフローを定義しています。

トリガー設定

スケジュール実行と手動実行の両方に対応しています。

on:
  schedule:
    # 毎週火曜日 06:00 JST(月曜日 21:00 UTC)
    - cron: '0 21 * * 1'
  workflow_dispatch:
    inputs:
      force_update:
        description: '変更がなくてもドキュメントを強制更新する'
        required: false
        default: 'false'
        type: boolean

変更検出

git log --since で前回実行以降の変更をチェックします。

- name: Check for module changes
  id: check-changes
  run: |
    MONITORED_FILES="${{ steps.load-config.outputs.monitored_files }}"
    CHANGED_FILES=$(git log --since="${{ steps.check-date.outputs.last_update }}" \
      --name-only --pretty=format: -- $MONITORED_FILES 2>/dev/null | sort -u | grep -v '^$' || true)

    if [ -z "$CHANGED_FILES" ]; then
      echo "has_changes=false" >> "$GITHUB_OUTPUT"
    else
      echo "has_changes=true" >> "$GITHUB_OUTPUT"
    fi

Claude Code Action 実行

変更があった場合のみ、Claude Code Actionを実行します。

- name: Generate/Update Module Documentation
  if: steps.should-update.outputs.should_update == 'true'
  uses: anthropics/claude-code-action@v1.0.11
  with:
    anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
    timeout_minutes: 20
    claude_args: |
      --model claude-sonnet-4-5-20250929
      --allowed-tools "Read,Write,Glob,Grep,Bash(ls*),Bash(find*)"
    prompt: |
      /update-module-docs docs_exist=${{ steps.check-docs.outputs.docs_exist }}
ワークフロー全体のコード
name: Module Structure Documentation Update

on:
  schedule:
    - cron: '0 21 * * 1'
  workflow_dispatch:
    inputs:
      branch:
        description: '対象ブランチ'
        required: false
        default: 'develop'
        type: string
      force_update:
        description: '変更がなくてもドキュメントを強制更新する'
        required: false
        default: 'false'
        type: boolean

env:
  DOCS_FILE: docs/module-structure.md
  CONFIG_FILE: .github/config/module-docs-config.yml
  LAST_UPDATE_FILE: .github/.module-docs-last-update

jobs:
  check-and-update-module-docs:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    permissions:
      contents: write
      pull-requests: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          ref: ${{ inputs.branch || 'develop' }}
          fetch-depth: 100

      - name: Get last update date
        id: check-date
        run: |
          if [ -f "${{ env.LAST_UPDATE_FILE }}" ]; then
            LAST_UPDATE=$(cat "${{ env.LAST_UPDATE_FILE }}")
          else
            LAST_UPDATE=$(date -u -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ)
          fi
          echo "last_update=$LAST_UPDATE" >> "$GITHUB_OUTPUT"

      - name: Load monitored files from config
        id: load-config
        run: |
          MONITORED_FILES=$(yq -r '.monitored_files | join(" ")' "${{ env.CONFIG_FILE }}")
          echo "monitored_files=$MONITORED_FILES" >> "$GITHUB_OUTPUT"

      - name: Check for module changes
        id: check-changes
        run: |
          MONITORED_FILES="${{ steps.load-config.outputs.monitored_files }}"
          CHANGED_FILES=$(git log --since="${{ steps.check-date.outputs.last_update }}" \
            --name-only --pretty=format: -- $MONITORED_FILES | sort -u | grep -v '^$' || true)
          if [ -z "$CHANGED_FILES" ]; then
            echo "has_changes=false" >> "$GITHUB_OUTPUT"
          else
            echo "has_changes=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Check if docs exist
        id: check-docs
        run: |
          if [ -f "${{ env.DOCS_FILE }}" ]; then
            echo "docs_exist=true" >> "$GITHUB_OUTPUT"
          else
            echo "docs_exist=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Determine if update needed
        id: should-update
        run: |
          if [ "${{ steps.check-changes.outputs.has_changes }}" == "true" ] || \
             [ "${{ steps.check-docs.outputs.docs_exist }}" == "false" ] || \
             [ "${{ inputs.force_update }}" == "true" ]; then
            echo "should_update=true" >> "$GITHUB_OUTPUT"
          else
            echo "should_update=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Generate/Update Module Documentation
        if: steps.should-update.outputs.should_update == 'true'
        uses: anthropics/claude-code-action@v1.0.11
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          timeout_minutes: 20
          claude_args: |
            --model claude-sonnet-4-5-20250929
            --allowed-tools "Read,Write,Glob,Grep,Bash(ls*),Bash(find*)"
          prompt: |
            /update-module-docs docs_exist=${{ steps.check-docs.outputs.docs_exist }}

      - name: Create branch and commit changes
        if: steps.should-update.outputs.should_update == 'true'
        id: commit
        run: |
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git config user.name "github-actions[bot]"

          git add "${{ env.DOCS_FILE }}" "${{ env.LAST_UPDATE_FILE }}"

          if git diff --cached --quiet; then
            echo "has_commit=false" >> "$GITHUB_OUTPUT"
            exit 0
          fi

          BRANCH_NAME="docs/module-structure-update-$(date +%Y%m%d)-$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 4)"
          echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"

          git checkout -b "$BRANCH_NAME"
          git commit -m "docs: モジュール構成ドキュメントを自動更新"
          git push origin "$BRANCH_NAME"
          echo "has_commit=true" >> "$GITHUB_OUTPUT"

      - name: Create Pull Request
        if: steps.commit.outputs.has_commit == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh pr create \
            --base "${{ inputs.branch || 'develop' }}" \
            --head "${{ steps.commit.outputs.branch_name }}" \
            --title "docs: モジュール構成ドキュメント自動更新 ($(date +%Y-%m-%d))" \
            --body "モジュール構成ドキュメントを自動更新しました。"

設定ファイル

.github/config/module-docs-config.yml で監視対象ファイルを定義しています。

# 変更を監視するファイル
monitored_files:
  - settings.gradle.kts
  - app/build.gradle.kts
  - domain/build.gradle.kts
  - data/build.gradle.kts

# 監視するディレクトリ
module_directories:
  - app/src/main/java
  - domain/src/main/java
  - data/src/main/java

# ドキュメント出力設定
output:
  path: docs/module-structure.md
  format: markdown

ワークフロー側でこの設定ファイルを yq(YAMLをコマンドラインで操作するツール)でパースし、監視対象ファイルを動的に取得しています。

※ APIドキュメントの自動更新も同様の構成で実装しています。

工夫した点

不要なコストを発生させないことを念頭に設計しました。

1. 実行頻度の制限

スケジュール実行は週に1回のみに設定しています。毎日実行すると、変更がない週でも無駄にAPIコストが発生してしまいます。

schedule:
  - cron: '0 21 * * 1'  # 毎週火曜 06:00 JST(月曜 21:00 UTC)

2. 変更検出による条件実行

監視対象ファイルに変更があった場合のみ、Claude Code Actionを実行するようにしています。

# git log で前回実行以降の変更をチェック
git log --since="7 days ago" --name-only --pretty=format: -- $MONITORED_FILES

変更がなければワークフローは早期終了し、Claude Code Actionは実行されません。

3. 既存ドキュメントの差分更新

ドキュメントが既に存在する場合は、docs_exist=true を渡して差分更新モードで実行します。全体を再生成するよりもトークン消費を抑えられます。

結果

初回のドキュメント生成時の実績です。

項目
使用モデル claude-sonnet-4-5-20250929
実行時間 約1分24秒
コスト $0.14(約21円)
生成行数 142行

生成されたドキュメントには以下が含まれています:

  • モジュール一覧(5モジュール)
  • Mermaid形式の依存関係図
  • 各モジュールの詳細説明(役割、プラグイン、主要パッケージ、依存関係)

これは初回生成のため出力行数が多くなっています。差分更新モードでは、より少ない出力・コストになることが期待されます。

毎週変更が発生したとしても、月額100円以内に収まるのではないかと考えています。

まとめ

Claude Code + GitHub Actionsを組み合わせることで、ドキュメントの自動整備ワークフローを構築しました。

ポイント:

  • カスタムコマンドでプロンプトと出力形式を定義
  • 設定ファイルで監視対象を管理し、変更時のみ実行
  • コスト最適化のため週1回の実行 + 変更検出を組み合わせ

これにより、リファクタリングを継続的に行いながらも、ドキュメントが自動で追従する仕組みができました。

今後は画面遷移図の自動整備にも対応していきたいと考えています。

Discussion