Claude Codeで見積もりの実績表を作る — 過去PRから類似タスクの日数を集計
マージ済みPRの一覧をclaude -pで類似タスクに分類し、ファイル数・差し戻し回数・マージまでの日数をjqで集計して、見積もりの実績表にする手順です。
見積もりの根拠に強いのは、勘でも理屈でもなく、同じ種類のタスクが過去に何日かかったかという実績です。マージ済みのPRには、変更ファイル数・レビューの差し戻し・作成からマージまでの日数が残っています。
Claude Codeに任せるのは、PRのタイトルを「類似タスク」に分類する部分だけにします。平均や最大の計算はjqに回し、Claudeに計算させません。分類と計算を分けると、実績表の数字を誰でも再計算できます。ここでは、gh pr listの出力をclaude -pに渡して分類し、表にするまでを順に示します。
コードを読んで影響範囲を数える作業はplan modeで見積もりの根拠表を作る手順が扱っています。こちらはコードを開かず、リポジトリの履歴から実績だけを集めます。
実績表は「分類」と「計算」を分けて作る
実績表の元データは2か所にあります。PRのメタデータはGitHub側、実際に変わった行数はgitの履歴です。どちらも機械的に取り出せる数字なので、Claudeの出番は一か所に絞れます。
| 作業 | 担当 | 理由 |
|---|---|---|
| PR一覧の取得 | 担当gh pr list | 理由件数・日付は取得結果をそのまま使う |
| 類似タスクへの分類 | 担当claude -p | 理由タイトルの意味を読む作業で、正規表現では崩れやすい |
| 平均・最大の計算 | 担当jq | 理由同じ入力なら毎回同じ数字が出る |
| 行数の補正 | 担当git log --numstat | 理由ロックファイルなどを除いて数え直す |
LLMに平均値まで出させると、件数が増えたときに桁や丸めがぶれます。分類だけを任せれば、ぶれるのは「このPRはどの種類か」という判断だけになり、その判断はkinds.jsonに残るので後から直せます。
表に入れる列
見積もりに使う実績表の列は次の5つです。
- 類似タスクの種類(例: 帳票項目の追加、計算ロジックの修正)
- 件数
- 変更ファイル数の平均
- 差し戻し回数の平均
- 作成からマージまでの日数(平均と最大)
「最大」を残すのは、見積もりでは平均より、遅れた場合の幅のほうが効くことが多いからです。幅を出すために、件数が少ない種類は平均を信用しすぎないようにします。
手順: 取得して、分類して、集計する
実績表を作る流れ
- 1
マージ済みPRを取得する
gh pr listで、マージ済みPRのメタデータをJSONで保存します。 - 2
Claudeにタイトルを分類させる
番号とタイトルだけを
claude -pに渡し、--json-schemaで種類の候補を固定します。 - 3
jqで集計する
分類結果とPRのメタデータを番号で結合し、種類ごとに平均と最大を出します。
- 4
gitの履歴で行数を補正する
ロックファイルや生成物を除いた行数を
git log --numstatで数え直し、PRの変更量と見比べます。
1. マージ済みPRを取得する
gh pr listは既定で30件しか返しません。実績表には件数が要るので、--limitで広げます。
gh pr list --state merged --limit 300 \
--json number,title,labels,changedFiles,additions,deletions,createdAt,mergedAt,reviews \
> prs.json--jsonに指定できる項目は、存在しない名前を渡すとエラーで一覧が表示されます。手元のgh 2.90.0では、changedFiles・additions・deletions・createdAt・mergedAt・reviews・labelsがその一覧に含まれていました。reviewsにはレビューごとのstate(CHANGES_REQUESTED・APPROVED・COMMENTEDなど)が入るので、差し戻しの回数に使えます。
似た名前のlatestReviewsはレビュアーごとの最新の1件だけなので、往復の回数を数える用途には向きません。
2. Claudeにタイトルを分類させる
分類では、PRの本文や差分を渡す必要はありません。番号とタイトルだけで足りるうえ、入力が小さくなります。非対話モードは標準入力を読めるので、jqで絞ったJSONをそのままパイプします。
jq '[.[] | {number, title}]' prs.json | claude -p \
'PRのタイトルを、指定した種類のどれか1つに分類してください。' \
--output-format json \
--json-schema '{"type":"object","properties":{"tasks":{"type":"array","items":{"type":"object","properties":{"number":{"type":"integer"},"kind":{"type":"string","enum":["帳票項目の追加","計算ロジックの修正","依存更新","その他"]}},"required":["number","kind"]}}},"required":["tasks"]}' \
| jq '.structured_output' > kinds.jsonポイントは3つあります。
- 種類の候補を
enumで固定します。候補を自由にすると、実行のたびに「帳票への項目追加」「帳票項目追加」のように名前が揺れ、集計が割れます - 「その他」を必ず用意します。当てはまらないPRを無理に種類へ入れると、その種類の日数が膨らみます
- 結果は
structured_outputに入ります。--json-schemaで有効な出力が得られなかった実行では、このフィールド自体がありません。スクリプトでは存在を確かめてから使います
ここで渡しているのはデータだけなので、Claudeにツールの実行は要りません。公式の例でも、diffをパイプで渡せばClaudeにBash権限は要らないと説明されています。--allowedToolsを足さなくても動く構成にしておくと、無人実行に組み込みやすくなります。
非対話モードでも、--max-budget-usdで推定費用の上限を、--max-turnsでターン数の上限を指定できます。標準入力は10MBが上限なので、数百件を超えるときは、期間で分けて実行します。
3. jqで集計する
分類結果とPRのメタデータを番号で結合し、種類ごとにまとめます。マージされていないPR(mergedAtがnull)は除外します。
jq -r -s -f table.jq kinds.json prs.jsontable.jqの中身は次のとおりです。
(.[0].tasks | map({key: (.number|tostring), value: .kind}) | from_entries) as $kind
| .[1]
| map(select(.mergedAt != null))
| map({
kind: $kind[(.number|tostring)],
files: .changedFiles,
changes: (.reviews | map(select(.state == "CHANGES_REQUESTED")) | length),
days: (((.mergedAt | fromdate) - (.createdAt | fromdate)) / 86400)
})
| group_by(.kind)
| map({
kind: .[0].kind, prs: length,
files_avg: (map(.files) | add / length),
changes_avg: (map(.changes) | add / length),
days_avg: (map(.days) | add / length | . * 10 | round / 10),
days_max: (map(.days) | max | . * 10 | round / 10)
})
| .[] | [.kind,.prs,.files_avg,.changes_avg,.days_avg,.days_max] | @tsv差し戻しは「CHANGES_REQUESTEDの付いたレビューの数」で数えています。コメントだけの往復は含みません。レビューの往復を広く数えたいチームは、COMMENTEDも条件に加えるなど、数え方を先に決めておきます。
4. gitの履歴で行数を補正する
PRのadditions・deletionsにはロックファイルや生成物の差分も含まれます。タスクの大きさとして見たいのが手書きの変更だけなら、git log --numstatで除外して数え直します。スカッシュマージの運用なら、コミットの件名にPR番号が入っているので、PRと突き合わせられます。
git log --numstat --format='@%s' -- . \
':(exclude)package-lock.json' ':(exclude)*.snap'出力は、@で始まる件名の行のあとに、ファイルごとの「追加行 削除行 パス」が続く形です。手元の検証用リポジトリでは次のように出ました。
@chore: z (#113)
1 0 src/a.ts
@fix: b (#107)
1 0 src/a.ts
2 0 src/b.tspackage-lock.jsonを変更したコミットでも、除外指定によってロックファイルの行は出ていません。この出力をそのまま集計に使ってもよく、件名の(#番号)をキーにしてkinds.jsonと結合すれば、種類ごとの手書き変更行数も出せます。
実績表の出力例
手順2〜3を、PR 5件分の検証用データで動かした結果です。データは説明用に作った架空のもので、実在のリポジトリの数字ではありません。table.jqの集計は、この入力で実行して確かめています。
| 類似タスクの種類 | 件数 | 変更ファイル数(平均) | 差し戻し(平均) | 日数(平均) | 日数(最大) |
|---|---|---|---|---|---|
| 帳票項目の追加 | 件数2 | 変更ファイル数(平均)5 | 差し戻し(平均)0.5 | 日数(平均)1.8 | 日数(最大)2.3 |
| 計算ロジックの修正 | 件数2 | 変更ファイル数(平均)2.5 | 差し戻し(平均)0.5 | 日数(平均)1.6 | 日数(最大)3.0 |
依存更新のPRは未マージのため、集計から外れています。この例は2件ずつなので、平均に意味はありません。実際のリポジトリでは件数が数十になって初めて、平均と最大の差が読めるようになります。
実績表を見積もりに使う
表ができたら、新しいタスクを「どの種類に近いか」で引きます。
- 帳票に項目を足すタスクなら、帳票項目の追加の行を見る。平均と最大の間が、見積もりの幅の候補になる
- 件数が少ない種類は、数字を載せずに「実績不足」と書く。目安として5件を下回る種類は、平均が1件のばらつきで大きく動く
- 最大が平均の2倍を超える種類は、遅れた個別PRのタイトルを開いて理由を見る。レビュー待ちなのか、仕様の確認なのかで、見積もりの前提に足す条件が変わる
ここで出る日数は「PRの作成からマージまで」です。着手から数えた作業日数とは違います。実績を見積もりの人日に直すときは、チームの稼働の事情を人が置きます。
影響範囲の件数と組み合わせると、見積もりの根拠が二層になります。コードから数えた「今回の件数」と、履歴から集めた「同程度の件数の過去PRの日数」です。前者はplan mode、後者はこの記事の表が受け持ちます。
数字がゆがむ型
落とし穴
作成日がドラフトの時点で、日数が実際より長くなる
createdAtはPRを作った時刻です。ドラフトで長く置いたPRは、着手から数えるとずっと短い作業でも、日数が長く出ます。ドラフトを多用するチームは、ラベルやブランチ名でドラフト運用のPRを除いて集計します。
- カレンダー日数で数えている: 週末をまたぐPRは日数が増えます。営業日で見たいときは、jqの前に日付変換を挟みます
- ボットのPRが混ざる: 依存更新のbotのPRは、分類で「依存更新」に入れても平均が下がるだけです。
authorで除くか、集計で外します - 分類が実行のたびに変わる:
kinds.jsonを保存して、再実行しません。分類をやり直すときは、種類の候補(enum)を変えた版として別ファイルに残します - 大きなタスクが複数PRに割れている: 1つの機能を5本のPRで出すチームでは、PR単位の日数が機能単位の実績を表しません。ラベルやブランチ名で束ねるキーを決めてから分類します
向かない場面
PRの運用がバラバラなリポジトリでは、表の信頼度が下がります。たとえば、レビューをPRの外(チャットや口頭)で済ませているチームでは、差し戻しの列は常に0になります。スカッシュマージとマージコミットが混在していて、git logの件名にPR番号が入らない場合も、gitの履歴との突き合わせは使えません。
その場合は、先にラベルとPRテンプレートを整え、数か月分の実績が溜まってから表を作るほうが、使える数字になります。
実績の集計は、履歴から文書を起こす作業と手順が近いです。コミット履歴を材料にする別の例はClaude Codeでパッチノートをgitログから起こす手順にあります。プロジェクト管理ツールの進捗を集計して報告する場合はGitHub Projectsの進捗集計が参考になります。入力が大きくて止まるときはclaude -pにログを渡すと10MB上限で止まる場合の絞り方も合わせて読んでください。
まとめ
実績表の肝は、Claudeに数字を作らせないことです。分類はClaudeに、集計はjqに、行数の補正はgitに任せ、各段の入出力をファイルとして残します。そうすると、見積もりの根拠として表を出すときに「この数字はどのコマンドで出したか」を答えられます。
最初の1回は、直近の50件程度で分類の精度を目で確かめてから、全件に広げます。「その他」が3割を超えるなら、種類の候補が実際のタスクに合っていません。候補を見直してから、実績を見積もりに使い始めます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。