複数のAIツールが混在するチームで、DORAメトリクスを自動計測する仕組みをつくった話 <Claude Code / Cursor / Copilot / Codex>

こんにちは、makeshop事業本部開発部の鈴木(佳)です。

私のチームでは、Claude Code・Cursor・GitHub Copilot・OpenAI Codex といった複数のAIコーディングツールを、メンバーやタスクによって使い分けています。生産性は確実に上がった実感があるのですが、数値として可視化されていないことが課題でした。

リードタイムも実作業時間も、感覚では分かっても数字で出せない。かといって、開発者に毎回「着手した」「中断した」を手で記録してもらうのは——正直に言うと、続きません。私自身が続けられる自信がありませんでした。

本記事では、この「手で記録しないと続かない」という問題を、git hook と GitHub Actions で完全に自動化し、しかもどのAIツールを使っても同じように計測できるようにした仕組みを紹介します。

なぜ作ったのか

計測の仕組みを設計するうえで、私が最初に決めた前提は2つです。

  1. 開発者に手作業を増やさない。 計測のために何か特別な操作を覚えてもらう時点で負けです。普段どおり git checkout してコードを書けば、勝手に記録される状態をゴールにしました。
  2. AIツールに依存しない。 「Claude Code を使っている人だけ計測できる」では、チーム全体の数字になりません。Cursor でも Copilot でも Codex でも、同じ計測が成立する必要があります。

この2つを満たすために、計測の「土台」は AIツールではなく git そのもの(git hook) に置き、AIツール固有の便利機能はその上に「上乗せ」する二層構造にしました。

計測する4つの指標

いわゆる DORA(DevOps Research and Assessment)の指標をベースに、チームの実態に合わせて4つを計測対象にしました。

指標 定義 計測方法
デプロイ頻度 週あたりの本番リリース回数 GitHub Actions の実行履歴
リードタイム Issue作成 → PR承認 までの日数 Issueの作成日時+承認コメント
実作業時間 実際にコードを書いていた時間 着手 → 中断 の累計
リリース待機時間 PR承認 → developマージ までの時間 コメント → Issue close

なお、DORA本来の「リードタイム(Lead Time for Changes)」は コードがコミットされてから本番で稼働するまで を指す指標です。本記事では計測のしやすさとチームの運用実態を優先し、起点・終点を「Issue作成 → PR承認」に読み替えている点に注意してください(標準定義とは別物です)。

DORAでお馴染みの「変更失敗率」「平均復旧時間(MTTR)」は、私たちのチームではSRE側で別途取得できていたため、この仕組みではあえて対象外にしています。既にある数字を二重に取りにいかない、というのも設計判断のひとつでした。

全体アーキテクチャ

仕組み全体を図にすると、こうなります。

開発者が  git checkout feat/#nnnn
        │
        ▼
.githooks/post-checkout が自動実行
        │   GitHub Issue #nnnn にコメントを自動投稿
        ▼
  <!-- dora:start 2026-03-26T14:30:00+09:00 @username -->
  [開始] 着手開始: 2026-03-26 14:30 JST

別のIssueブランチへ切替 → 旧Issueへ自動で中断コメント
        │
        ▼
develop 向け PR が「承認」される(GitHub Actions)
        │
        ▼
  <!-- dora:complete 2026-03-27T15:00:00+09:00 -->
  [完了] PR承認: 2026-03-27 15:00 JST

毎週月曜 09:00(GitHub Actions)
        │   Issueコメントをパースして集計
        ▼
  週次DORAレポートを「新規Issue」として自動作成

本番デプロイ時(Slack通知)
        │
        ▼
  リリース一覧に「Issue作成からの経過日数」を自動追記

ポイントは、開発者が能動的に行う操作が git checkout だけ だということです。あとはすべて hook と Actions が裏で動きます。

土台:git hook が着手・中断を自動記録する

計測の心臓部は、.githooks/post-checkout というたった70行ほどのシェルスクリプトです。git checkoutgit switch のたびに git が自動実行してくれるフックで、AIツールに一切依存しません。

やっていることはシンプルで、

  1. ブランチ名から Issue 番号(#\d+ 形式)を抽出する
  2. develop / main などの共有ブランチはスキップ
  3. 別のIssueブランチから離れたら、旧Issueに「中断」コメントを投稿
  4. 新しいIssueブランチに入ったら「着手」または「再開」コメントを投稿

着手と再開の区別は、ロックファイルの有無で判定しています。コアの部分を抜粋するとこんな感じです(実際のコードを一般化しています)。

# ファイル単位の checkout では発火させない($3=1 がブランチ切替)
[ "$3" = "1" ] || exit 0

NEW_BRANCH=$(git branch --show-current)
# grep -P はGNU grep専用なので、移植性のため -E で抽出して # を除去
NEW_ISSUE=$(echo "$NEW_BRANCH" | grep -oE '#[0-9]+' | head -1 | tr -d '#')

# git の user.name を @username 用に正規化(英数字のGitHubハンドル前提)
GIT_USER=$(git config user.name | tr -cs '[:alnum:]_' '-' | sed 's/^-//;s/-$//')
TIMESTAMP=$(TZ=Asia/Tokyo date '+%Y-%m-%dT%H:%M:%S+09:00')

LOCK_FILE="/tmp/dora-locked-${REPO_HASH}-${NEW_ISSUE}-${GIT_USER}"

if [ -f "$LOCK_FILE" ]; then
  # 2回目以降 → 再開
  gh issue comment "$NEW_ISSUE" --body "<!-- dora:resume ${TIMESTAMP} @${GIT_USER} -->
[再開] 再開: $(TZ=Asia/Tokyo date '+%Y-%m-%d %H:%M JST') by ${GIT_USER}"
else
  # 初回 → 着手
  if gh issue comment "$NEW_ISSUE" --body "<!-- dora:start ${TIMESTAMP} @${GIT_USER} -->
[開始] 着手開始: $(TZ=Asia/Tokyo date '+%Y-%m-%d %H:%M JST') by ${GIT_USER}"; then
    touch "$LOCK_FILE"
  fi
fi

集計に使う情報は <!-- dora:start ... --> という HTMLコメント に埋め込んでいます。GitHub の画面上では見えないので、Issue のコメント欄が計測ログで汚れません。表示用の行([開始] 着手開始: ...)だけが人間に見える、という二段構えです。

なお gh issue comment は checkout のたびに同期実行されるため、オフライン時は記録に失敗します。着手側は投稿が成功したときだけロックファイルを作る作りにしているので、失敗しても二重記録にはなりません。

.git/hooks/ はリポジトリにコミットできませんが、git config core.hooksPath .githooks で hooks ディレクトリを差し替えれば、フック自体をリポジトリで共有できます。チーム展開ではここが地味に重要でした。

上乗せ:各AIツール固有の自動化

git hook だけでも計測は成立しますが、AIツールごとの「より精密な自動化」を上に乗せています。

Claude Code の場合は Hook 機能が強力で、.claude/settings.json にこう書くだけで、やり取りの区切り(プロンプト送信〜応答の一区切り)ごとの計測が完全自動になります。

{
  "hooks": {
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "bash .claude/scripts/check-hooks-setup.sh" }] }
    ],
    "PreToolUse": [
      { "matcher": "Edit|Write|Bash",
        "hooks": [{ "type": "command", "command": "bash .claude/scripts/track-issue-start.sh" }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "bash .claude/scripts/track-issue-stop.sh" }] }
    ]
  }
}
  • UserPromptSubmit:プロンプト送信のたびに、hook のセットアップ(core.hooksPath)が済んでいるか自動チェック
  • PreToolUse:Edit/Write/Bash の直前に「着手記録が漏れていないか」をフォールバックで補完
  • Stop:Claude が応答を終えて一区切りするたびに「中断」を自動投稿(次にプロンプトを送ると、再び「再開」が記録される)

一方、Cursor / Copilot / Codex には共通のフック機構がないので、それぞれの指示ファイル(.cursorrules / .github/copilot-instructions.md / AGENTS.md)に「会話開始時に core.hooksPath を確認し、未設定ならセットアップを実行する」というルールを書いておきます。土台は git hook で揃え、足りない部分だけ各ツールのやり方で埋める——この役割分担が、マルチツール対応の肝でした。

出力:週次DORAレポート

毎週月曜の朝、GitHub Actions が起動して Python の集計スクリプトを走らせ、その週に PR承認まで到達したIssueを対象にレポートを自動生成します。出力イメージはこんな形です(数値は説明用のサンプルです)。

## DORAレポート: 2026-03-20 〜 2026-03-26

### デプロイ頻度
- 本番デプロイ: 3回 / 週

### 実作業時間(着手 → 完了)
- 平均: 6時間 / 中央値: 5時間

### リードタイム(Issue作成 → PR承認)
- 平均: 8日 / 中央値: 7日

### Issue別内訳
| Issue | タイトル | 実作業時間 | リードタイム | 状態 |
|-------|---------|----------|------------|------|
| #nnnn | 〇〇画面の修正 | 3時間 (開発者A: 2時間, 開発者B: 1時間) | 8日 | ✅ マージ済 |

実作業時間が開発者ごとに内訳表示されているのがお気に入りで、同じブランチを複数人で触っても、@username 単位できちんと分けて集計しています。

地味だけど効く、堅牢性の作り込み

実運用で効いてくるのは、こうした「ハッピーパス以外」の処理でした。

  • 複数作業者対応:同じブランチに複数人が入っても、それぞれの @username で独立計測し、集計時に合算します。
  • 中断記録の漏れ対策:作業が日をまたいで「中断」が記録されないことがあります。翌日にAIが検知してユーザーに終了時刻をヒアリングし、過去に遡って中断を補完します。
  • 暴走防止のキャップ:それでも記録が漏れたケースに備え、1セッションあたり最大10時間でキャップを掛け、日をまたいだ過大カウントを防いでいます。
  • 氏名の正規化@username 単位の集計キーは git config user.name から生成しています。日本語名のままだと正規化で全部潰れて衝突するため、各メンバーが英数字の GitHub ハンドルを設定していることを前提にしています。

「自動計測は、計測漏れをどう扱うかで信頼性が決まる」というのが、作ってみての一番の学びでした。

おわりに

DORAメトリクスというと大げさな基盤を想像しがちですが、実体は git hook + GitHub Actions + Issueコメント という、既存の道具の組み合わせでした。とくに「計測の土台を AIツールではなく git に置く」と決めたことで、ツールが増えても破綻しない設計になりました。

いきなり全部を作る必要はありません。まずは手元の1リポジトリに、ブランチ切り替えで着手コメントを1行投げる post-checkout フックだけ仕込んでみてください。「自分がいつ何に着手したか」が自動で残るだけでも、振り返りの解像度がぐっと上がるはずです。

参考リンク


※ 本記事は社内での取り組みを一般化して紹介しています。Issue番号・ユーザー名・数値はサンプルに置き換えています。