bash + jq + GraphQL + claude -p でチームの週報を自動生成する

週報を手書きするのが面倒だった

チームの週次活動報告を書くとき、GitHub Enterprise上のPRを手作業で集めていました。誰が何件マージしたか、どのissueに紐づいているか、テーマ別にグルーピングして…。毎週30分ほどかかる作業です。

この作業をbashスクリプト2本で自動化しました。GraphQL APIでPRデータを取得し、claude -p(Claude CLIのheadlessモード)でテーマ別の週報markdownに変換します。

./pr-report.sh -u "atani,alice,bob" -s "1 week ago"

これだけで、テーマ別にグルーピングされた週報markdownが標準出力に出てきます。

構成

ツールは2つのスクリプトで構成されています。

  • pr-stats.sh — PRデータの取得・集計・可視化を行うメインスクリプト
  • pr-report.sh — pr-stats.shの出力をclaude -pに渡して週報markdownを生成するラッパー

依存するのはbash、jq、gh CLI、claude CLIの4つだけです。

pr-stats.sh(データ取得・集計)
  ├─ GitHub GraphQL API → PRデータ取得
  ├─ jq → 集計・整形
  └─ ~/.cache/pr-stats/ → キャッシュ(TTL 1日)

pr-report.sh(週報生成)
  ├─ pr-stats.sh -R → JSON出力
  └─ claude -p → テーマ別markdown生成

データ取得:GraphQL APIで全PRを一括取得

REST APIでPRを1件ずつ取得すると、リポジトリの数だけリクエストが必要です。GraphQL APIなら組織横断で一括検索できます。

{
  search(
    query: "org:myorg is:pr is:merged merged:>=2026-03-04 author:atani author:alice"
    type: ISSUE
    first: 100
  ) {
    issueCount
    pageInfo { hasNextPage endCursor }
    nodes {
      ... on PullRequest {
        title
        url
        mergedAt
        author { login }
        repository { name }
        additions
        deletions
        body
      }
    }
  }
}

ポイントは search クエリの query パラメータです。org: で組織、author: で複数ユーザー、merged: で期間を指定できます。ページネーション対応で100件ずつ取得し、全件をJSONファイルにキャッシュします。

キャッシュの仕組み

ユーザーリスト・開始日・終了日からMD5ハッシュを生成し、キャッシュのキーにしています。

CACHE_KEY=$(echo "${USERS}-${SINCE}-${UNTIL}" | md5sum | cut -d' ' -f1)
CACHE_FILE="${HOME}/.cache/pr-stats/${CACHE_KEY}.json"

TTLは1日。同じパラメータで再実行するとキャッシュから読むため、APIリクエストなしで結果が返ります。-c オプションでキャッシュをクリアできます。

集計モード:日別・月別・四半期別

取得したデータはjqで集計します。-g オプションで集計単位を切り替えます。

# 月別統計
./pr-stats.sh -u "atani,alice,bob" -s 2026-01-01 -g monthly

# 日別統計(土日除外)
./pr-stats.sh -u atani -s "2週間前" -g daily -w

# 変更規模のグラフ表示
./pr-stats.sh -u atani -s "1ヶ月前" -g monthly -G

相対日付は日本語にも対応しています。「2週間前」「3ヶ月前」「昨日」をそのまま渡せます。macOSの date -v とLinuxの date -d の違いを内部で吸収しています。

normalize_date() {
    case "$input" in
        today|今日) num=0; unit="days" ;;
        yesterday|昨日) num=1; unit="days" ;;
        *) # "2週間前" → num=2, unit=weeks
           if [[ "$input" =~ ^([0-9]+)週間前$ ]]; then
               num="${BASH_REMATCH[1]}"; unit="weeks"
           fi ;;
    esac
    # macOS / Linux で分岐
    if [[ "$(uname)" == "Darwin" ]]; then
        date -v -"${num}"w +%F
    else
        date -d "-${num} week" +%F
    fi
}

匿名化モード:チーム外に見せる資料用

-a オプションで匿名化できます。自分(-m で指定)はそのまま表示し、PRマージ数が自分より多い人は「SENSEI」、それ以外は「Engineer_A」「Engineer_B」として表示します。

./pr-stats.sh -u "atani,alice,bob,charlie" -m atani -a -g monthly

事業部向けの報告で「チーム内の具体的な個人名は出さないけど、自分の位置は把握したい」というケースで使います。

週報生成:claude -p でテーマ別にグルーピング

pr-report.sh の核心は、PRデータのJSONを claude -p に渡す部分です。

REPORT_JSON=$("${SCRIPT_DIR}/pr-stats.sh" -R "$@")

cat <<PROMPT_EOF | claude -p
以下のPRデータ(JSON)をテーマ別にグルーピングし、
事業部向けの週次活動報告をmarkdown形式で作成してください。

## グルーピングルール
- 同じissue URLに紐づくPRは同じグループにまとめる
- 同じ目的で複数リポジトリにまたがるPRはまとめる
- Revert / Revert of Revert は元のPRと同じグループに

PRデータ(${PR_COUNT}件):
${REPORT_JSON}
PROMPT_EOF

-R オプションで出力されるJSONには、PRのタイトル・URL・リポジトリ名に加えて、PR本文から抽出したissue URLも含まれています。これにより、claude -pが「このPRとこのPRは同じissueに紐づいている」と判断できます。

なぜclaude -pなのか

jqだけでもPR一覧は作れます。テーマ別のグルーピングが難しいのです。

「DBスキーマ変更」「API層の修正」「管理画面の対応」が別リポジトリのPRとして存在していても、同じissueに紐づいていれば1つのテーマとしてまとめたい。タイトルの類似度判定やissueベースのグルーピングロジックをjqで実装するのも現実的ではありません。

claude -pに自然言語でグルーピングルールを渡すほうが、実装コスト・保守コストともに低いという判断です。

出力例

# 週次活動報告(2026/03/04 - 2026/03/10)

## 不要テーブル削除
myapp, myapp-api, myapp-web, myapp-admin, myapp-batch, myapp-schema

去年1年間アクセスがなかったテーブルに関連するコードとスキーマを削除。
6リポジトリにまたがる横断対応。
- 
https://git.example.com/myorg/myapp/issues/7328
(...) ## DB接続エラーの診断改善 myapp DB接続エラーを検知して警告表示する診断機能を追加。 -
https://git.example.com/myorg/myapp/pull/1837
(...) ## サマリ 3名で12件のPRをマージ。主な成果は技術的負債の解消と運用改善。

bashスクリプトで完結させた理由

PythonやNode.jsで書くこともできましたが、bashを選びました。

  • 依存が少ない — gh CLIとjqはほぼどの開発マシンにもある
  • パイプで繋げるpr-stats.sh | jq '.[]' | ... のように他のツールと組み合わせやすい
  • メンテナンスが楽 — 600行程度のスクリプト1本で完結している

claude -pの部分だけが外部依存ですが、ここをなくすとただのPR一覧になります。テーマ別グルーピングという「判断が必要な部分」だけをLLMに任せるのがポイントです。

まとめ

  • GraphQL APIで組織横断のPRデータを一括取得
  • jqで集計・整形し、キャッシュで高速化
  • claude -pでテーマ別グルーピングと週報markdown生成
  • 匿名化や相対日付(日本語対応)など、実用的なオプション

bashスクリプトで完結する軽量さと、LLMによるグルーピングの柔軟さを両立できた構成です。週報のような「データ収集は機械的、整理は判断が必要」なタスクには、この組み合わせが向いています。

コメント

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