Claude Media
Claude Codeで見積もりの実績表を作る — 過去PRから類似タスクの日数を集計

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. 1

    マージ済みPRを取得する

    gh pr listで、マージ済みPRのメタデータをJSONで保存します。

  2. 2

    Claudeにタイトルを分類させる

    番号とタイトルだけをclaude -pに渡し、--json-schemaで種類の候補を固定します。

  3. 3

    jqで集計する

    分類結果とPRのメタデータを番号で結合し、種類ごとに平均と最大を出します。

  4. 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.json

table.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.ts

package-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割を超えるなら、種類の候補が実際のタスクに合っていません。候補を見直してから、実績を見積もりに使い始めます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. code.claude.com/docs/en/cli-reference
この記事を共有:XはてブLinkedIn