mcpField
T
task-estimator

task-estimator

github.com/marks97開発0904 回インストール4 週間前に更新
リポジトリ

task-estimator について

task-estimatorは、記録した作業履歴に基づいてタスクを見積もるClaude Code向けスキルです。類似タスクを参照する台帳を管理し、見積もりの精度を評価して調整します。

主な機能

  1. 01会話・タスク・見積もりの台帳管理
  2. 02類似する過去タスクに基づく見積もり
  3. 03完了した作業と実績の記録
  4. 04見積もり精度の採点
  5. 05台帳データによる見積もりの再調整

活用シーン

  1. 01過去のプロジェクト実績からタスクを見積もる
  2. 02完了後に実際の作業量を記録する
  3. 03過去の見積もり精度を確認する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marks97/task-estimator --skill task-estimator

インストール後、Claude Code で/task-estimatorを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
prompts/
audit-task.md
classify-chat.md
estimate-features.md
references/
ai-era-evidence.md
estimation-science.md
measurement-model.md
scripts/
audit.mjs
backfill.mjs
calibrate.mjs
classify.mjs
digest.mjs
extract.mjs
lib.mjs
record.mjs
score.mjs
slice.mjs
stitch.mjs
templates/
claude-md-snippet.md

詳細

タスク見積もり

モデルが見た目の難しさから直感で判断するのではなく、実測した履歴に基づいて見積もります。問題なのはモデルの直感です。AI ツールを使う開発者は、実測では 19%遅くなっていたにもかかわらず、20%速くなったと思っていました(METR 2025)。記録された実績に基づかない見積もりは、文章上でその差を再現するだけです。

そこで、このスキルは次の 3 つを行います。実際のタスクにかかった時間を台帳に記録し、過去の類似タスクの実測分布に基づいて新しいタスクを見積もり、過去の見積もりを採点して偏りを可視化します。

根拠の詳細は references/ にあります。estimation-science.md(古典的な研究結果 — EBS、参照クラス、アンカリング、フローメトリクス)、ai-era-evidence.md(エージェントが介在するようになって何が変わったか)、measurement-model.md(フェーズの分類、実測した定数、スクリプトが実装する較正の計算式)を参照してください。ここにあるルールが恣意的に見える場合は、これらを読んでください。すべてのルールは、そこにある研究結果に基づいています。見積もりの際に理論を再導出する必要はありません。手順を実行してください。

モードを選ぶ

ユーザーが行っていることモード
所要時間、ETA、規模、「1 日でできますか?」を尋ねているestimate(デフォルト)
初回実行、$LEDGER(Ledger layout を参照)がない、「セットアップして」「較正して」「過去分を埋めて」setup
「このタスクを記録して」「ログに残して」、タスクが完了した、CLAUDE.md フックが起動したrecord — record.mjs
「較正を再実行して」「見積もりの精度は?」「当たっていた?」「再較正して」recalibrate — calibrate.mjs + score.mjs

4 つのモードは 1 つのループを構成しており、すべての段階が実行されて初めて完結します。estimate で見積もりを記録し、→ record で実績を測定してその見積もりと照合し、→ recalibrate で見積もりが適切かを報告し、→ 次の estimate でそれを反映します。estimate モードでログを省略したり、record モードを一度も実行しなかったりすると、このスキルは履歴から見積もりを出し続けるだけで、自分の見積もりが正しかったかを学習できません。

$LEDGER/calibration.json が存在せず、ユーザーが見積もりを求めている場合は、そのことを伝えてセットアップを提案してください。そのうえで、今すぐ見積もりが必要であれば、事前情報だけに基づいて見積もりを出してください。空の台帳から、何も伝えずに見積もることは決してしないでください。

台帳の構成

$LEDGER は次の順に解決され、最初に見つかったものが使われます。

  1. $CLAUDE_ESTIMATION_DIR — 明示的な上書き設定。
  2. ~/.claude/estimation — ポータブルなデフォルト。

台帳はスキルディレクトリの外に置かれるため、再インストール後も保持されます。また、アカウントごとの設定ディレクトリの外に置かれるため、アカウントごとではなく、すべてのアカウントで 1 つの履歴を共有します。

$LEDGER/
  ledger/
    chats.ndjson       # one row per Claude Code conversation (mined)
    tasks.ndjson       # one row per unit of work; the reference class lives here
    estimates.ndjson   # one row per quote this skill emitted, for scoring
  calibration.json     # population priors + per-class empirical quantiles

スクリプト($SKILL/scripts/)。4 つの基本スクリプトは純粋な処理を行い、標準出力を中心に設計されています。extract.mjs(トランスクリプト JSONL → 1 件のチャット行)、stitch.mjs(チャット → タスク)、digest.mjs(1 件のチャット → LLM 処理用の簡潔なテキスト)、calibrate.mjs(台帳 → calibration.json)。最後の 2 つの間に slice.mjs が入り、各エピックのチャットをラベル付きのサブ成果物(task.sub_tasks[])に分割します。エピックの行自体は分割しません。

さらに 2 つのスクリプトがループを完結させます。これらは record と recalibrate が実行するスクリプトです。record.mjs(完了した 1 セッション → 抽出 → 結合 → 手戻りの統合 → 結果 → 採点を行い、台帳に追加)と、score.mjs(見積もり × タスク → 見積もりの精度を評価)。2 つの較正がそれぞれ異なる問いに答えており、しばしば混同されるため、役割の違いに注意してください。

問い読み込み書き込み
calibrate.mjsこのような タスクにはどれくらい時間がかかるか?tasks.ndjsoncalibration.json
score.mjs見積もりは適切だったか?estimates.ndjson + tasks.ndjsonなし

見積もりの根拠となるのは calibrate.mjs です。score.mjs は、見積もりが体系的に低すぎることを知らせます。estimates.ndjson の行がない台帳でも前者はいつまでも実行できますが、後者は実行できません。このループは、その失敗を防ぐためにあります。

estimates.ndjson — 行スキーマ

見積もりごとに1行を記録します。estimate のステップ8で書き込まれ、record.mjs と score.mjs で読み込まれます。schema: "estimate.v1":

{"schema":"estimate.v1",
 "estimate_id":"2026-08-31-add-oauth-provider",
 "date":"2026-08-31",
 "task":"add-oauth-provider",
 "ticket":null,
 "task_desc_hash":null,
 "features":{"type":"feature-epic","novelty":"adaptation","precedent":"saml-provider"},
 "class":{"neighbors":["example-api/TICKET-3880"],"n":1,"gate":"prior-only"},
 "quoted":{"agent_hours":{"p50":76,"lo":50,"hi":150},
           "human_rounds":{"p50":null,"lo":600,"hi":1000},
           "calendar_days":{"p50":null,"lo":28,"hi":49}},
 "source":"ledger-neighbor+bounded-adjustment"}

各フィールドのルールです。いずれも、破ると行を採点できなくなるため設けられています。

  • estimate_id は、record.mjs --estimate-id が受け取る結合キーです。デフォルト値は <date>-<task> にします。タスクがこの ID に紐付けて記録されると、ID は タスク行に estimate_id として書き戻されるため、関連付けを推測し直す必要はありません。
  • task はスラッグで、後から record.mjs が見つけられる値でなければなりません。チケット キー、ブランチ名、または分類器が作業内容を要約するときに使うフレーズを指定します。 一致判定は estimate_id → ticket → task_desc_hash → task_id slug → summary → epic-slice slug → sub-deliverable slug の順に行い、最初に一致したものが採用されます。 エピックスライスに一致した見積もりは、エピック全体ではなく、そのスライスの工数に対して採点されます。 「webhook部分」の見積もりを76時間の統合全体と照合すると、そもそも統合全体を対象としていない見積もりなのに 60倍の誤差として報告されます。このような場合、score.mjs は sub_task にスライス名を記録します。
  • quoted には、台帳が測定する3つの軸、すなわち agent_hours、 human_rounds、calendar_days のみを指定します。週単位で示された範囲は日数に換算して保存します。 見積もり可能な軸にはすべて値を設定してください。範囲がない軸は採点対象にならないため、 見積もり結果が1つの軸だけに基づくカバー率になってしまいます。
  • lo/hi は実際に提示した範囲です(P50→P85 の幅、またはレジーム分割範囲)。 score.mjs はこの範囲に対して公称80%のカバー率を採点します。
  • p50 は null でもかまいません。その場合、score.mjs は範囲の対数中点 (sqrt(lo*hi))を使い、そこから導出されたすべての比率にフラグを付けます。そのため、導出された中心値が 実際に提示した値と誤認されることはありません。
  • task_desc_hash は、アンカーを除去した説明の sha256 です。任意のフィールドですが、チケットがない場合は 推奨されます。台帳に文章を保存せずに、後から照合できるようになります。

レガシー行(スキーマ導入前の手書き形式:agent_h_p50、agent_h_band、 rounds_band、calendar_weeks_band)は読み込み時に正規化され、引き続き採点されます。 新しい行は標準形式で記録してください。

実在する文字列がすべて、実際の結合シグナルになるわけではありません。 stitch.mjs は一般的なブランチ名 (main は一度でもプッシュされたすべてのチャットに含まれます)と、同じ理由で裏付けのないチケットキーを除外します。 レビューラウンドに貼り付けられた日付付きレポートファイル名から REPORT-2026 が生成され、 46件のチャットと79時間分のエージェント作業が1つの「タスク」に結合されたことがありました。キーが結合エッジとして 採用されるのは、結合数が負荷を左右するしきい値を下回る場合、プレフィックスがフィーチャーブランチに含まれる場合、 またはその名前空間が有効な場合に限られます。有効な名前空間とは、同じキー以外にも複数の関連キーが単独で現れる状態です。 一緒に貼り付けられた断片は常に一緒に移動しますが、実際のチケットはそうではありません。 ルールと根拠は、isPlausibleTicketKey(extract.mjs)と eligibleTicketKeys(stitch.mjs)のコメントにあります。

3つのドライバーはコーパス全体を処理し、再開可能です。再実行すると最初からではなく、前回の続きから処理します。 backfill.mjs(トランスクリプトのルートを走査 → chats.ndjson + tasks.ndjson)、 classify.mjs(haiku → classifications.ndjson、tasks.ndjson にマージ)、 audit.mjs(層化した sonnet による再判定 → audit.ndjson + フィールドごとの一致率)。

モード:estimate

以下の順序は必須です。 各ステップは特定の失敗を防ぐためにあります。順序を入れ替えたり、 「まあ、だいたい1日くらいに見える」と判断して先に進んだりしないでください。

1. アンカーを取り除く — 推論を始める前に

タスクの説明を読みます。所要時間について考える前に、期限、目標日、ストーリーポイント、「すぐ終わる/小さい/大きい/些細」といった表現、ユーザー自身の予想、クライアントが約束した日付、チケットの見積もり欄を、検討対象から除外してください。

プロンプトに含まれる無関係な数字によって見積もりは4~5倍ずれます。この影響は、無視するように指示された後も、明らかに適任ではない情報源から示された場合も残ります。アンカーは一度目にすると頭から消せないので、明示的に対処してください。依頼にアンカーが含まれていた場合は、**「約Xとおっしゃっていた点に留意しています。以下の数値は履歴に基づくもので、それを読む前に算出しました」**と伝え、分位点には決して反映させないでください。

2. 準備状況のゲート — 準備ができていない作業は見積もらない

次のいずれかに当てはまる場合、見積もりを出さないでください。

  • テスト可能な受け入れ条件がない。 テストや具体的な観察によって決着できる形で、「完了」の条件を誰も説明できません。
  • 実装方針が収束していない。 実質的に異なる複数のアプローチが候補に残っており、どれを選ぶかによって作業量がバケット1つ分を超えて変わります。

こうしたタスクは、まさに大きく膨らむものです。分布は二峰性で、「簡単」なタスクの約78%はクラスの中央値付近に収まりますが、残りの約22%は180%を超えて超過し、その超過分の大半はこのゲートを通過できなかったタスクです。

代わりに、次のように出力してください。

まだ見積もれません — 時間を区切ったスパイクを推奨します。 不足しているもの:<具体的な条件、または未解決の分岐点>。 制限時間:<見積もり尺度で1バケット>。スパイクの成果物は機能そのものではなく、不足しているものです。 スパイクの後に見積もりをやり直してください。その際はクラスが変わり、見積もりの幅も大幅に狭まります。

スパイクの推奨も正当な回答です。仕様が定まっていないタスクに数値を出すのは、正当な回答ではありません。

3. 特徴を抽出する

prompts/estimate-features.mdに従ってください。実際に実行するコマンドが記載されたチェックリストです。答えを推測せず、検索を実行してください。抽出する項目は次のとおりです。

repos[]、task_type(bugfix|feature|refactor|migration|infra|investigation|script)、greenfield|brownfield、precedent_in_repo(このリポジトリで以前に同じ形のものを出荷したか。見つけるか、なければ「なし」と記載)、self_verifiable(tests|typecheck-only|human-eyes|prod-only)、blast_radius(影響する可能性が高いファイルのgrep件数)、external_deps[](サードパーティの承認、他の人の作業、デプロイ)、parallelizable(ファイル間/リポジトリ間で並列化できるか)。

特徴の中で最も大きな影響を与えるのは、先例と自己検証可能性です。これらの検索に時間をかけてください。

次に、タスクをスピードアップの象限に分類します。エージェントによる割引は一律ではありません。

シンプル複雑
グリーンフィールド+30~40%+10~15%
ブラウンフィールド+15~20%0~10%、場合によってはマイナス

成熟したリポジトリの作業の大半は右下に位置し、割引はほぼゼロで、場合によっては逆効果になります。象限を記録してください。手順6で使用します。

4. 参照クラスを取得する

$LEDGER/ledger/tasks.ndjson から、次の順に取得します。

  1. 特徴ベクトル上の近傍 k = 1~3 件。k を小さくするのは意図的です。k を大きくすると平均に回帰し、類推の利点が失われます。
  2. 絞り込んだクラスの n < 5 の場合は、task_type クラス全体に範囲を広げ、その旨を伝えます。
  3. それでもデータが少ない場合は、calibration.json の母集団事前分布にフォールバックし、推定が事前分布に基づくことを伝えます。

フィールドを判断材料にする前に field_reliability を確認し、確認結果を伝えてください。 ここにあるすべてのフィールドには、測定された一致率とともに reliable: true または reliable: false のいずれかが設定されています。未測定のフィールドはありません。2026-09-01 の監査時点で、失敗しているのは task_type です(stride は raw で 0.38、admissible で 0.43、n=58)。したがって、このリストの中で task_type クラスに範囲を広げるのは最も弱い選択肢であり、その旨を明示する必要があります。近傍 k=1~3 件を優先してください。範囲を広げる場合は、クラスラベル自体が、より強い判定者と一致したのは約10回中4回にすぎないと伝えます。適切に紐づけられた行では一致率が 0.51 であるため、問題の大部分はラベルではなく、統合されすぎた行が26%を占めることにあります。

該当するクラスとその n を必ず記録してください。中心値だけでなく、近傍間の分散も報告してください。1h/1h/9h と 3h/3h/4h は中心値が同じでも、意味はまったく異なります。

依頼が、より大きな作業の一部分である場合は、代わりに epic-slice クラスを使用してください。 calibration.json には、slice.mjs がチャット数6件以上のすべてのタスクのサブ成果物から作成した reference_classes["epic-slice"](独自の by_task_type を含む)が用意されています。「決済統合にどれくらいかかるか」ではなく、「決済統合のうち Webhook 部分にはどれくらいかかるか」という質問では、このクラスを使用してください。スライスは、エピックで読み込み済みのコンテキスト、ブランチ、規約を引き継ぐため、同じ task_type の単独タスクとは別物です。引用したクラスがどちらなのかを伝えてください。両者は異なる質問に答えるものであり、どちらの場合も記録単位はエピックの行です。

使用可能なクラスがまったくない場合は、感覚に頼らず、定数ハザードモデルに基づいて人間による対応ラウンド数の軸を見積もります。エージェントが80%の信頼性を保てるのは、人間換算で約30分の作業までです。そのため、human_rounds ≈ ceil(human_equivalent_minutes / 30) とします。妥当性確認の目安として、実際のセッションでは介入回数は3.3~5.4回です。事前分布のみで算出した見積もりがこの範囲を外れる場合は、理由が必要です。

5. 実測分位点を計算する — フィットも平均も使わない

クラスの実測値を並べ替え、対数空間で、観測値そのものから直接分位点を求めます。対数正規分布などをフィッティングして曲線から分位点を読み取ってはいけません。フィッティングでは裾が系統的に過小評価されますが、実際に知りたいのは分布のその部分だけです。平均値も示してはいけません。裾の重いクラスでは、平均は典型的なケースでもリスクでもありません。

見積もりを示す前に、自分自身の偏りを確認してください。 node $SKILL/scripts/score.mjs を実行し、軸ごとの行を確認します。これはタスクではなく、この推定器についての唯一の証拠です。

  • TOO EARLY TO CALL(スコア付きのペアが5件未満)と表示された場合は、クラスの値をそのまま示し、推定器自体が未検証だと伝えてください。1、2件のデータから補正を適用してはいけません。単一の比率は観測値であり、偏りではありません。
  • n ≥ 5 で幾何平均比が報告されたら、EBS比率の再サンプリングを優先します。過去の見積もり/実測比率を単位ごと、復元抽出で再サンプリングし(100回以上抽出)、クラスの中心値を各抽出値で割ります。同じ原則です。観測値全体を使って再サンプリングし、平均は取りません。由来情報が残るよう、記録した行の source にこの方法を記載してください。

サンプル数のゲート。毎回、どの段階に該当するかを明記してください。

クラス内の n示してよい値
< 5クラスの値は何も示せません。事前分布のみであり、その旨を明記してください。
5–9P50のみです。まだ使用可能な上限値がないことを警告してください。
10–19P85です。
≥ 20P95を使用できます。

n ≈ 20 未満では、クラスの分位点も w = n/(n+κ) によって母集団の事前分布へ向けて縮小されます。n=11での個人のP85は、真値の0.31~2.26倍に及ぶため、補正値としては多少役立ちますが、それだけでは何の根拠にもなりません。calibrate.mjs は縮小済みの値を出力します。生の分位点を再計算して代わりに示すのではなく、その値を使ってください。

新しいデータ15件は、古いデータ150件より有用です。calibrate.mjs はすでに90日の半減期を適用しています。手作業で再加重してはいけません。

6. インサイドビューによる調整 — 最大1段階

対数スケール:≈15m · 1h · 3h · 1d · 3d · 1w+。

クラスの結果を動かせるのは最大1段階で、具体的な根拠を挙げる場合に限ります。「同一のPrismaマイグレーションを今月2回リリースし、どちらも2h未満でした」のように説明してください。根拠のない直感(「これはもっと簡単そうです」)による調整は認められません。2段階動かすなら、それは調整ではなく別のクラスです。ステップ4に戻ってください。

次にステップ3の象限別割引を適用します。適用する場合は、区間を狭めず、必ず広げてください。 高速化と分散は連動します。期待される改善が最も大きい象限ほど、ばらつきも最大です。「エージェントが対応してくれる」は、帯を狭める理由ではなく、広げる理由です。

失敗分岐には、成功分岐より長い時間を見積もってください。 失敗した場合の進行は、成功した場合より18~82%長くなります。失敗は安く早く済むのではなく、コストが高く、時間もかかります。

クラスのP85を既定値にしてください。P50に下げるには、裾を取り除いた緩和策を明示する必要があります。たとえば、仕様が確定している、ハーネスがある、外部依存がない、このリポジトリに前例がある、といったものです。

7. 出力 — SLE、3つの軸、単一の数値ではありません
**85% of your similar tasks finished in ≤ X agent-hours, ≤ Y human rounds, Z calendar days**
(class: <description>, n=<N>, neighbors: <task ids>)

次に、以下の順で示します。

  • P50 ライン — 「半数が ≤ … で完了」のように示し、典型的なケースを上限と並べて確認できるようにします。
  • モードの判定 — 分布は中心の周りに広がるのではなく、二峰性です。どちらのモードにあると考えるか、その理由も述べます。agent-nails-it(前例があり、テストで検証でき、影響範囲が狭い)か、integration-thrash(検証が人の目によるもの、または本番環境でしかできず、前例がなく、外部に及ぶもの)です。どちらもあり得る場合は、おおよその比率を添えて両方示します。単一の平均値は両モードの谷間に位置し、ほとんど何も説明しません。
  • 爆発的な遅延リスクのフラグ — 該当するものだけ、それぞれ名前を付けて示します。曖昧な仕様 · 人の目でしか検証できない · 影響範囲が広い · 外部の待ち時間 · リポジトリに前例がない。フラグ名と、それを引き起こした具体的な要因を示します。
  • 外部のカレンダー上の待ち時間は別に記載します。 アプリの審査、プラットフォームの承認、他者のデプロイ時間枠、人による判断などです。これは作業ではなく、何も進まない時間です。エージェント時間には決して含めません。これが、カレンダー日数とエージェント時間を別々の軸にする理由です。
  • レビュー待ちもカレンダー日数に含めます。 完了の定義がマージ済みなら、見積もりは「コードを書いた」時点で終わりではありません。速くなったのはその部分です。レビューにかかる時間は441%増加し、エージェントが作成した PR はレビュー担当者に着手してもらうまで5.3倍長く待ちます。タスクの完了に、他の人によるマージが必要な PR が含まれる場合は、レビュー担当者を待つ時間を必ず明示します。
  • 見積もりがクラスの P85 を超える場合は、必ず「分割しましょう」と伝えます。 予算を増やすより、適切なサイズにするほうが効果的です。提案するだけでなく、実際に1バケット以下の単位に分割します。

単一の数値は決して出しません。裾の重いクラスに対して点推定を行うのはカテゴリの誤りであり、人々が期日にコミットして、後で間に合わなくなる形式です。

8. 見積もりを記録する — 省略不可、後回しも不可

記録されなかった見積もりは、採点できません。 この手順によってスキルは同じことを繰り返さずに改善し、ユーザーがすでに答えを得ているために省かれがちな手順でもあります。見積もりを伝えたそのターンで実行します。

$LEDGER/ledger/estimates.ndjson に estimate.v1 の行を1つ追加します。スキーマとフィールドのルールは Ledger layout にあります。重要なのは3つの軸と、record.mjs が後から見つけられる task スラッグです。

LEDGER=${CLAUDE_ESTIMATION_DIR:-~/.claude/estimation}
cat >> $LEDGER/ledger/estimates.ndjson <<'EOF'
{"schema":"estimate.v1","estimate_id":"2026-09-01-csv-export","date":"2026-09-01","task":"TICKET-1234","ticket":"TICKET-1234","features":{"task_type":"feature","greenfield":false,"precedent_in_repo":"example-api/TICKET-1180","self_verifiable":"tests","blast_radius":9},"class":{"neighbors":["example-api/TICKET-1180","example-api/TICKET-1204"],"n":7,"gate":"p50-only"},"quoted":{"agent_hours":{"p50":3,"lo":1.5,"hi":9},"human_rounds":{"p50":12,"lo":6,"hi":30},"calendar_days":{"p50":1,"lo":1,"hi":4}},"source":"class-p50+one-bucket-precedent-adjustment"}
EOF

次に、1行でループが動き始めたことをユーザーに伝え、見積もりを締めくくります。「2026-09-01-csv-export として記録しました。完了したら record モードを実行してください。自動で採点されます。」

この行の数値に関するルールは2つあります。

  • ユーザーに伝えた内容を記録し、慎重な表現に変えないでください。 見積もり範囲をこっそり広げて台帳に記録すると、見積もり担当者が実際には得ていないカバレッジを捏造することになります。
  • 作業が始まった後に、記録済みの行を編集してはいけません。 結果を知ったうえで修正した見積もりは、もはや見積もりではありません。2つ目の行を記録し、両方を採点できるようにします。

テキストではなく、ハッシュを保存します。台帳にタスクの文章を残さず、後で record モードがハッシュを照合して、比率(実績/見積もり)と区間カバレッジを採点できるようにします。

原則 — 見積もりの形に疑問を持たれたら、これを伝えてください

所要時間 ≈ ラウンド数 ×(ラウンドごとのエージェント作業時間 + ラウンドごとの人間の待ち時間)

人間とのラウンド数が、予測を左右する最大の要因です(実測データで r ≈ 0.83)。最初のターンの 所要時間からは何も予測できません(r ≈ 0.0)。

そのため、40 分で一発で終わるタスクと、「惜しい、もう一度」と 8 ラウンドかかるタスクは、差分がどれほど似ていても比較できません。ラウンド数を見積もれば、所要時間は後からわかります。また、ラウンドごとの人間の待ち時間はユーザー自身の遅延です。記録された実績にすでに含まれているため、別途モデル化しないでください。

該当する場合は、次の 2 つの系もはっきり伝えてください。

  • 最初のターンが長いのは悪い兆候ではなく、良い兆候です。 ラウンド数が少なくなることを予測します。最初に枠組みをしっかり定めることが、その後の効率につながります。開始が遅かったからといって、タスクの所要時間を長く見積もらないでください。
  • 許可プロンプトを待つことが、回復可能なロスの最大の原因です。 タスク内で長く停止する時間の 52% は承認待ちで、中央値は 1 回あたり 32 分です。ユーザーのセットアップでツールを呼び出すたびに承認を求められるなら、午後を奪っているのはモデルの速度ではなく、それです。

行数ではなく、ラウンド数とステップ数を見積もってください。 アシスタントのステップ数と所要時間の相関は r ≈ 0.86、人間とのラウンド数との相関は r ≈ 0.82 です。一方、LOC の相関はわずか 0.53 です。差分のサイズによる見積もりは、より具体的に感じられる指標よりも予測精度が劣ります。

自己申告の高速化を、決して校正用の入力として使わないでください。 「20 分でできました」はデータではありません。認識のずれがそう言わせているだけです。台帳に記録するのは、収集した実績だけです。

モード:セットアップ

1. 構成

$LEDGER を確認します(解決順序は 台帳のレイアウト を参照)。ledger/、空の chats.ndjson/tasks.ndjson/ estimates.ndjson、およびプレースホルダーの calibration.json を作成します。既存のものは上書きしないでください。台帳にすでに行がある場合は、セットアップではなく再校正を行います。

2. 同意 — トランスクリプトに触れる前に

停止して AskUserQuestion で質問してください。 回答を得る前に、トランスクリプトを一つも読まないでください。次の内容を正確に説明します。

  • このマシン上の ~/.claude*/projects/ にある Claude Code の会話 JSONL を(両方のアカウントから)読み取ります。
  • 分類処理の呼び出しを除き、データがこのマシンの外に出ることはありません。分類処理の呼び出しは claude -p として Anthropic API に送られます。これは、ユーザーが通常のセッションでアクセスする先と同じです。
  • 保存するのはタスクの形状と所要時間です。会話のテキストは保存しません。
  • 元に戻せます:rm -rf ~/.claude/estimation

対象範囲を選べることを提案します。すべてのプロジェクト、1 つのプロジェクト、または直近 N か月です。

3. バックフィル — 決定論的な処理

トランスクリプトのルート全体に対して、1つのコマンドを実行します。すべてのプロジェクトディレクトリを走査し、各 .jsonl を逐次的に抽出します(これらのファイルは最大 140 MB になるライブ環境のため、並列処理はしません)。最後に全体を結合します。

nice -n 19 ionice -c3 node --max-old-space-size=2048 $SKILL/scripts/backfill.mjs \
  --root ~/.claude/projects

ノイズとして 5 KB 未満のトランスクリプト(同意確認のプローブ、バナーを越えて進まなかったセッション)をスキップし、その数を記録します。前回の実行以降、ファイルとサイドカーディレクトリの両方に変更がないチャットもスキップします。中断して再実行してください。チャットは抽出されるたびに追記されます。

作業用ファイルと進捗ログは /tmp/claude/task-estimator-backfill/ に置かれます。リポジトリにも台帳にも置かれません。

サブエージェントのサイドカーも解析します。委任された作業の記録はそこにあります。 <chat_id>.jsonl の隣に、ハーネスは <chat_id>/subagents/agent-*.jsonl と、各エージェントの種類を示す .meta.json を書き込みます。extract.mjs は各サイドカーに対して同じ抽出コアを実行し、その結果を親チャットの subagents オブジェクトにまとめます。対象となるのは、loc_added/loc_removed、files_edited、repos、active_min、sidecar_count、および per_type の内訳です。これは任意の改善ではありません。この Claude Code の時代では、Task/Agent の tool_result に toolStats が含まれないため、サイドカーがなければ、すべてを委任したチャットの LOC はゼロとして記録されます。また、親チャットには起動したエージェントがすべて永続化されるわけではありません(104 回起動したあるスプリントでは、2 件だけが保持されました)。次の2つのルールは例外なく適用されます。

  • 親の独自フィールドには何も加算しません。 サブエージェントの作業時間は親の経過時間内に含まれるため、それを加算すると、一度だけ使われた時間を二重に数えることになります。チャットの loc_added、files_edited、agent_active_min は変わりません。
  • subagents 内でも二重に数えません。 親が起動したエージェントについて toolStats を報告しており、同じ toolUseId のサイドカーも存在する場合、サイドカーの測定値が報告値を置き換えます。サイドカーの測定値が主要な記録です。サイドカーのない起動については、報告値が維持されます。深さ2のエージェント(別のサブエージェントによって起動されるため、親には tool_use ブロックが存在しません)は追加されます。そのため、subagents.count が起動ブロックの数を超えることがあります。

サイドカーが独立したチャットとして出力されることはありません。

すべてのチャットに repo_share も付与されます。これは合計が正確に 1 になる {repo: fraction} で、編集したファイルの数に応じてリポジトリ間で時間を按分します(親による編集とサブエージェントによる編集の両方を含みます)。何も編集していないチャットは、作業ディレクトリ(cwd)のリポジトリに全量を割り当てます。ただし、cwd が複数リポジトリのルートの場合は、ワークスペースに割り当てます。stitch.mjs はこれをタスク行の agent_hours_by_repo/subagent_hours_by_repo に反映します。リポジトリ別または分野別の質問には、必ずこれらを使用してください。 repos を使って agent_hours を合計すると、複数リポジトリの各チャットがリポジトリごとに1回ずつ数えられます。このコーパスでは、その結果、分野別の集計が約25倍に膨らみました。

プローブセッションには noise フラグが付きます。値は "permtest"(権限プローブ:echo PERMTEST_OK、「testing perms here」)または "qa-probe"(/tmp の作業用ツリーから実行されたセッション)です。実際のチャットでは権限プローブについて長く話し合うことがあるため、検出はキーワードではなく比率に基づいています。チャットのほぼ全体がプローブの場合に限り、プローブとして扱います。これらの行は台帳に残り、calibrate.mjs が分位点の計算から除外します。

4. バックフィル — セマンティックパス

チャットごとに分類を1つ実行します:digest.mjs → haiku。実行前にチャット数を数え、トークンの消費量を見積もり、表示してから続行するか確認してください。 1年分のトランスクリプトをバックフィルするのは無料ではありません。まずユーザーに数値を確認してもらいます。--estimate-only はその内容だけを表示し、それ以外は何もしません。

node $SKILL/scripts/classify.mjs --estimate-only      # count + token/cost/wall estimate
node $SKILL/scripts/classify.mjs --concurrency 3      # then, once they agree

classify.mjs はチャットごとにこの呼び出しを実行します。MAX_THINKING_TOKENS=0 とすることで、プロンプトのルールに対する精度を損なわずに、思考ありの場合の44秒、$0.025に対して、実測では1回あたり6.5秒、$0.007でした(この正確性を保つのは監査ステージです)。

node $SKILL/scripts/digest.mjs --chat <chat-id> \
  | claude -p --model claude-haiku-4-5 \
      --output-format json \
      --system-prompt "$(cat $SKILL/prompts/classify-chat.md)" \
      --tools "" --safe-mode --no-session-persistence \
  | jq -r '.result'

すべてのフラグが重要な役割を果たします。

  • --tools "" — ツールループを無効にします。これは分類器であり、テキストを読み込んでJSONを返します。
  • --safe-mode — CLAUDE.md、スキル、フック、MCP サーバーを無効にします。プロジェクトの指示で分類器が汚染されるのを防ぎ、プロンプトのプレフィックスを安定させることで、バッチでプロンプトキャッシュがヒットするようにします。認証は通常どおり機能します(--bare とは異なります)。
  • --no-session-persistence — 重要です。 これを指定しないと、分類のたびに新しいJSONLが~/.claude*/projects/に書き込まれ、次のバックフィルがそれをチャットとして採掘します。台帳が自分自身の尻尾を食べ始めてしまいます。
  • --output-format json はエンベロープを返します。モデルのテキストは.resultにあり、コードフェンスで囲まれている場合があります。解析前に```フェンスを取り除いてください。必要に応じて、プロンプトファイルのSchemaブロックを使って--json-schemaでより厳密にできます。

返されたフィールドを、classified_atとclassifier_modelとともにタスクの行へマージします。タスクには複数のチャットがあるため、マージにはルールがあります。

  • ラベルはメインチャットから取得します — mainと判定された分類を使い、エージェントがアクティブだった時間(分)で同点を解消します。ラベルは投票で決めません。バグ修正とリファクタリングの平均を取っても、どちらにもなりません。
  • outcomeは分類器ではなく、成果物によって決まります。 commits > 0またはprsが空でなければ、そのタスクは無条件でshippedです。これらのフィールドは、切り詰められたダイジェストを読むモデルではなく、gitとghの出力から取得され、標準的な「完了とはマージ済み」を正確に表す集合だからです。分類器が判定するのは、成果物では表現できない2つの結果、shipped-then-revertedとabandonedだけです(どちらもコミットが存在し、さらに別のことが起きています)。outcome_sourceにはdeterministicまたはclassifierが記録され、classifier_outcomeには元の値が保持されるため、監査でモデルの評価もできます。分類器の判定が2.3対1で悲観的すぎたため、この仕組みを設けています。sonnetによる監査では、ダイジェストにコミット行が示されていなかったため「推測するよりunknownを優先する」というルールが発動し、62行がunknown → shippedに修正された一方、逆方向は27行でした。
  • external_waitsとuser_touchpoint_kindsは、すべてのチャットを対象に統合します。 これは量と集合であり、ラベルではありません。チャット3で発生した待ち時間も、そのタスクが費やした無駄な時間です。
  • エピックは統合したまま、詳細も維持します。 複数のチャットにまたがるタスクは1行にまとめます(分割すると、台帳が自分を誇張する原因となる過少マージそのものです)。各チャット独自のラベルは、sub_deliverables[]配列にすべて保持します。複数の作業単位を報告する単一のチャットも、それぞれ別のエントリとして追加します。

バッチ処理と再開。 バッチ単位で処理し、各バッチの後に書き込み、classified_atがすでにあるタスクはスキップします。中断したバックフィルは最初からやり直すのではなく、同じコマンドを再実行して再開できなければなりません。

5. 監査 — 分類器は信頼できるか?

層化した5件に1件のサンプルを取り、最大規模のタスク、stitch_confidence が低い行、そして俳句が決定的なシグナルに反する行(コミットがないのに shipped と判定する、マージ済み PR があるのに abandoned と判定する)を多めに抽出します。

各行を sonnet と prompts/audit-task.md を使って再判定します。audit.mjs は層化を行い(3つの層はすべて対象とし、残りは決定的な1-rate 間隔で対象にするため、再実行時も同じ行が監査されます)、タスクレコードとそのチャットの要約を判定者に渡し、フィールドごとの一致率を出力します。

node $SKILL/scripts/audit.mjs --plan-only     # what would be sampled, and why
node $SKILL/scripts/audit.mjs --rate 5

フィールドごとの一致率を報告します。いずれかのフィールドの一致率が 80% 未満なら、そのフィールドを明示してください。プロンプトの見直しが必要だと伝え、calibration.json でそのフィールドを信頼できないものとしてマークします。誰も確認していない分類器は、根拠にはなりません。

この表では、次の2点を注意深く確認してください。

  • 一致率は間隔層のみについて報告されます。他の3つの層は、意図的に最も難しい行を選んでいるため、その一致率は母集団の割合を示しません。
  • outcome は、台帳に実際に記録されている値に基づいて採点されます。成果物がある行では、その値は決定的なものです。「このフィールドを信頼できるか」という問いには適切な数値ですが、分類器の評価ではありません。モデルとの比較には classifier_outcome を採点してください。

監査者による修正が認められるのは、その監査者に修正する権限があった場合のみです。次の3種類は認められず、calibrate.mjs は調整後の一致率からすべて差し引きます。reliable の判定にはこの一致率を使うため、認められない修正によってフィールドが不当に信頼できないと判定されることはありません。認められない修正が不一致の過半数を占め、かつ2件以上ある場合、そのフィールドは信頼できないのではなく unmeasured として報告されます。これはデータではなく prompts/audit-task.md の不備なので、数値をどちらも信じる前にプロンプトを修正して再監査してください。

  • off_spec_corrections — フィールド固有の列挙型に含まれない値です。異なる語彙による「修正」は、別の問いに答えたものです。
  • corrections_contradicting_deterministic_fields — commits/prs が git から得られた行で、outcome → unknown とする修正です。判定者が確認できるのは、最大6件の省略された要約です。レコードは全数を含み、要約はそのサンプルにすぎないため、「コミットを見つけられなかった」は根拠になりません。
  • self_contradicting_corrections — false でありながら、レコードにすでにある値を修正値として繰り返すものです。不一致を主張しながら、不一致の内容を示していません。

スティッチ判定をラベルの一致率と並べて確認してください。残っている不一致の大部分は、それで説明できます。 field_reliability.stitch は判定の分布と over_merged_share を報告し、各フィールドには、監査者が correct と判定した行における同じ一致率 agreement_on_well_stitched_rows も含まれます。ここで測定された結果は、task_type の一致率がスティッチ判定 correct の行では 0.51、26% が over-merged と判定された行では 0.00 です。15件でゼロというのは、五分五分の結果ではありません。答えのない問いを示す兆候です。over-merged の行には2つ以上の作業単位が含まれるため、正しい task_type は1つに定まりません。この条件付きの結果は診断情報であり、reliable の判定には意図的に反映されません。台帳には実際にその誤ったラベルが記録されているためです。これは修正すべき対象を示しています。プロンプトではなく、スティッチです。

6. エピックをスライスに分割する
node $SKILL/scripts/slice.mjs --min-chats 6        # add --dry-run --json to look first

数十件のチャットを含むタスクは、記録の単位としては適切ですが、見積もりの単位としては適切ではありません。「76時間の統合にはどれくらいかかりますか」と聞かれるのではなく、「Webhook の部分にはどれくらいかかりますか」と聞かれます。エピックを別々のタスク行に分割するのは、台帳が自分を実態以上に見せてしまい、行が更新されなくなる過剰な統合そのものです。そこで slice.mjs は、task.sub_tasks[] にビューを書き込みます。チャットはまず共有 PRごとにグループ化し、次に分類ラベルの重複と時間的な近さの両方でグループ化します(両方が必須です。同じスラッグが3週間後に現れた場合、それは同じスライスではなく、後退です)。

メンバーのチャットがすべて作業単位なしと報告したスライスは no_work としてフラグが付けられ、epic-slice の分位数から除外されます。no_work のタスク行がタスクの分位数から除外されるのとまったく同じで、理由も同じです。すべてのクラスで最下位に位置し、別の問いに答えるものだからです。スライスは sub_tasks に残り、統計からのみ除外されます。また、excluded_no_work_slices がその件数を報告します。

すべてのチャットは必ず1つのスライスにのみ属するため、各スライスの時間数とラウンド数の合計は、エピック全体の値と一致します。この不変条件があるからこそ、スライスは単なる印象ではなく、見積もりに使えます。classify.mjs(sub_deliverables を読み込みます)の後、スライスを epic-slice の参照クラスに変換する calibrate.mjs の前に実行してください。

7. キャリブレーションする
node $SKILL/scripts/calibrate.mjs --ledger $LEDGER/ledger --out $LEDGER/calibration.json

すべての分位数から除外される行は2種類あります。 noise(権限の確認、スクラッチ QA セッション)と no_work: true(分類器が作業単位なしと判定したもの。純粋な Q&A、状況確認、読むだけのセッション)です。どちらも tasks.ndjson には残りますが、統計からのみ除外されます。calibration.json は除外件数を excluded に報告するため、除外が見えなくなることはありません。

no_work の除外は、証拠に基づいて以前の判断を覆すものです。以前の考え方――「2時間読んで答えた質問には、2時間分のコストがかかった」――は、時間数については正しく、クラスについては誤りです。この台帳の426件中127件がこれに該当し、どのクラスでも最下位に集中しています。参照クラスが予測に使えるのは、その構成要素が同じ種類のものの場合だけです。これらを残したままでは、「バグ修正にはどれくらいかかるか」という問いに対して、「質問に答えるにはどれくらいかかるか」が30%を占める分布で回答することになります。質問について尋ねれば質問の数値が得られ、バグ修正について尋ねるなら、そうならないようにすべきです。

したがって、calibration.json を読み込むものはすべて、見積もりに使える件数は n_tasks であり、台帳の件数ではありません。 台帳の件数は n_tasks_in_ledger です。

続けて、クラスごとの n、母集団の事前分布、すでに n≥10 の基準を超えているクラス、除外された行数を表示してください。これにより、台帳が現時点で実際にどの問いに答えられるかをユーザーに伝えられます。

8. CLAUDE.md のフックを提案する

templates/claude-md-snippet.md を表示し、正確な差分を表示して、確認を求めてください。その後にのみ、ユーザーのグローバルな CLAUDE.md の `` マーカーの間に追記します。マーカーがすでに存在する場合は、2つ目のコピーを追記するのではなく、ブロックを置き換えてください。差分を先に表示せずに CLAUDE.md を編集してはいけません。

モード:record

実質的なタスクの終了時に実行します。ユーザーが「このタスクを記録して」と言った場合、または CLAUDE.md のスニペットが発火した場合です。

record.mjs を実行してください。タスクの行を手作業で組み立てないでください。 以下のルールはすべてすでにスクリプトに含まれています。また、手作業で間違えやすい2つの項目、つまり手戻りの統合と成果の情報源は、台帳が実態以上に良く見えてしまう原因そのものです。

SKILL=.claude/skills/task-estimator
# the session you are in right now:
nice -n 19 ionice -c3 node --max-old-space-size=2048 $SKILL/scripts/record.mjs \
  --session <session-uuid> --project <transcript-dir-name>
# or, when the transcript is simply the newest one in that project:
node $SKILL/scripts/record.mjs --latest --project -path-to-your-project

便利なフラグ:--dry-run(行を表示し、何も書き込みません。必ず最初に実行してください)、--outcome <shipped|shipped-then-reverted|abandoned|superseded|unknown>、--in-progress(作業はまだ継続中です。成果は正直に情報源を記録したうえで unknown のままになります)、照合する見積もりを指定する --estimate-id <id>、トランスクリプトに記載されていない手戻りの結合キーを指定する --ticket <KEY>、--json、--no-calibrate。

何をするのか、そして各ステップを独自に判断してはならない理由:

  1. 構造に沿って抽出し、つなぎ合わせます。 再開されたセッションは新しいタスクではなく、同じタスクの続きです。つなぎ合わせでは親エッジをたどります(ファイル内の最初の sessionId はファイル名と異なる場合があります)。経過時間を基準にグループ化することはありません。長いタスクを時刻で手作業で分割しないでください。チャットは upsert されるため、同じセッションを再記録しても冪等です。既存のタスク行は置き換えられ、重複は作られません。
  2. 手戻りは元の行に計上されます。 同じ PR またはチケットのタスク行が存在し、この作業が14日以内に行われた場合、行は統合され、統合後の行は元の task_id を保持し、統合は rework_merges[] に記録されます。コストはチャットから再計算されるため、ファイルは和集合になり、両方の行に記録された SHA も一度だけ数えられます。後続作業を新規タスクとして登録することは、履歴が正確だと静かに誤報告する最もよくある原因です。
  3. done は承認済み、またはマージ済みを意味します。 エージェントが「完了」と言うと、完了度をおよそ 7倍に誇張します。行にコミットまたは PR があると、outcome: shipped と outcome_source: "deterministic" が設定され、--outcome で上書きできません。成果物があっても維持されるのは shipped-then-reverted と abandoned だけです。成果物では表せない判定だからです。成果物がなく、回答もない場合は、楽観的な既定値ではなく、unknown になります。
  4. 抽出できない情報だけを尋ねてください。質問は2つ程度までにします。
    • マージ済みの PR から成果を確認できない場合の成果(--outcome)。
    • 外部要因による待ち時間。何が作業の停止時間だったのか、そのおおよその長さ。 その他すべて(ラウンド数、作業期間、変更したファイル、リポジトリ)はトランスクリプトから取得します。かかった時間をユーザーに尋ねないでください。それこそ、自己申告に頼る状況を置き換えるために台帳が存在する理由です。
  5. 見積もりと一致する記録があれば、スコアは自動で付けられます(estimate_id → チケット → task_desc_hash → スラッグ → 概要 → サブ成果物)。比率と軸ごとの許容範囲内外の判定が見積もり行に書き込まれ、タスク行には estimate_id が記録されるため、リンクを再推測する必要はありません。「一致する見積もりがありません」と報告された場合は、必ずユーザーに伝えてください。これはループが未完了であることを意味し、細部の問題ではありません。見積もりがまだ記録されていない(estimate モードのステップ8を修正してください)か、スラッグが一致していません。--estimate-id を使えば手動でリンクできます。
  6. エピックは再分割され、calibrate.mjs が自動的に再実行されます。 タスクが1つ完了するたびに、クラスにデータポイントが1つ加わり、次の見積もりにはその結果が反映されるはずです。既存のエピックにチャットを記録すると、そのエピックの sub_tasks も変わるため、同じ書き込み処理内で再分割されます。分割リストが古いままでは、epic-slice クラスの時間が台帳と一致しなくなります。

次に、1段落の要約をユーザーに読み返してください。測定できなかった内容(リポジトリを検出できなかった、コミットがなかった、成果の情報源が成果物ではなくユーザーだったなど)も含めます。見積もりと一致してスコアが付いた場合は、最後に score.mjs を実行し、見積もりと判定がまとめて記録されるようにしてください。

モード:再較正

stitch.mjs、slice.mjs、calibrate.mjsの順に、台帳全体に対して再実行し、その後**score.mjsを実行**します。以下の数値をすべて計算し、台帳のデータがまだ基準に達していない項目は計算しません。

node $SKILL/scripts/slice.mjs --min-chats 6      # refresh the epic sub_deliverables first
node $SKILL/scripts/calibrate.mjs --ledger $LEDGER/ledger --out $LEDGER/calibration.json
node $SKILL/scripts/score.mjs                    # add --json for the machine-readable form

出力をもとに報告します。

  • 区間カバレッジ — 実績値がある見積もりのうち、見積もったP80の範囲内に入った割合はどれくらいですか? 約80%が目安です。これより低ければ見積もり範囲が狭すぎ、高ければ広すぎます。score.mjsは現在のnに対する二項受容区間も併記します。必ずその区間も記載し、それなしに外れ値と判断してはいけません。n=1の場合、区間は0~100%です。
  • 符号付きバイアス — log(実績値 / 見積もりP50)の中央値です。過大評価と過小評価が相殺されないようにし、幾何平均比も併記します。比率を算術平均してはいけません。4倍と0.25倍は反対方向の同じ誤差であり、その算術平均(2.1倍)は存在しないバイアスを作り出します。
  • WIS-liteの内訳(n ≥ 5) — 80%区間スコアを、鋭さ+過大予測+過小予測に分解したものです。構成比を確認します。鋭さが大半を占める場合は区間が広すぎ、過小予測が大半を占める場合は見積もりが低すぎます。n = 5未満の場合は非表示となります。その場合は「非表示」と報告するのが正直な対応です。数値で代用してはいけません。
  • 実績値の記録待ちの見積もり — それぞれ、記録モードを実行せずに完了したタスクです。タスク名を挙げます。ここにループの漏れが現れます。
  • クラスごとのnと、前回以降に5/10/20の基準をクリアしたクラス。
  • 変化した項目 — 見積もり可能になったばかりのクラス、カバレッジが急落したクラスなど。

直近性(半減期90日)は、すでにcalibrate.mjsに組み込まれています。さらに減衰を適用しないでください。

プロセスの衛生管理 — 稼働中のマシンでこれらのスクリプトを実行する方法

このコーパスには数百メガバイトのトランスクリプトがあり、ユーザー自身のセッションを処理しているマシン上にあります。ここにあるルールはすべて、実際の経験から得られたものです。

  • pgrep、pkill -0、ps | grep、またはそれらを使ったループでプロセスの終了を待ってはいけません。 pgrep -f node、pgrep -f backfillなどは、チェックを実行しているプロセス自体にも一致します。シェル、サブシェル、grepなどが対象となるため、ループは一致を検出し続け、終了しません。このコーパスで、まさにこれが原因で2つのエージェントがデッドロックしました。 安全なフラグの組み合わせはありません。pgrep -v $$も使わないでください。
    • 自分が起動したプロセスを待つには、フォアグラウンドで実行するか、PIDを取得し、そのPIDだけを指定して待ちます(子プロセスにはwait <pid>を使います。ループ内ではない単発の生存確認にはkill -0 <pid>を使います)。
    • 別々の実行間で連携するには、PIDファイルを使います。record.mjsは自身のPIDを含む$LEDGER/ledger/.record.lockを書き込み、生きているプロセスがロックを保持している場合は直ちに拒否します(ポーリングはしません)。記録されたPIDのプロセスが終了していれば、ロックを引き継ぎます。--force-lockで強制的に上書きできます。そのため、実行が強制終了されても台帳がロックされたままになることはなく、プロセステーブルを走査する必要もありません。
    • 「今、バックフィルが実行中か」を確認するための、ループを使わない単発のpsスナップショットは問題ありません。危険なのはツールではなく、ループです。
  • コーパスを走査する処理(backfill.mjs、classify.mjs、audit.mjs、大きなトランスクリプトを扱う場合のrecord.mjs)には、nice -n 19 ionice -c3を指定します。ユーザーはこのマシンで作業しています。
  • ヒープの上限を設定します:node --max-old-space-size=2048。 個々のトランスクリプトは140 MBに達します。上限を設定せずに実行すると、マシンのメモリを使い切るおそれがあります。
  • 抽出処理を並列化してはいけません。 大きなトランスクリプトを一度に1つずつ、意図的に順次処理します。
  • 一時ファイルは/tmp/claude/task-estimator*/に置きます。 リポジトリにも台帳にも置いてはいけません。台帳を書き換える実行を行う前に、そこへ台帳をバックアップします。
  • 台帳への書き込みはアトミック(一時ファイルへの書き込み後にリネーム)です。そのため、同時に読み込む処理が未完成のNDJSONを参照することはありません。この状態を維持し、lib.mjsのwriteNdjsonAtomicを使います。

これらのルールを設ける理由

要点をまとめています。出典と詳細は references/ を参照してください。

  • METR RCT 2025(保守を担当するリポジトリで、エキスパート開発者16人が実際のタスク246件に取り組んだ調査)— AI を使うと 19%遅くなった一方で、自分たちは 20%速くなったと認識していました。実際の速度と認識の差は 39ポイントで、経験を積んでも自然には修正されません。その差は、大規模で成熟したリポジトリのエキスパートで最も大きく、まさにここで該当する条件です。だからこそ、自己申告を較正の入力として使うことは禁止し、台帳からデータを抽出するのであって、申告を求めることはしません。
  • 完了の水増しは約7倍 — エージェントが申告する完了率は、保守担当者のレビューを基準にした実際の完了率を過大評価し、「合格」1件あたり約26分の修正作業を隠します。完了とは、マージまたは承認された状態です。それ以外は該当しません。
  • 分数ではなくラウンド数 — 人間のラウンド数と総所要時間の相関は r ≈ 0.83 です(651セッションのコーパスでは0.82、アシスタントのステップ数ではさらに高く 0.86)。これに対し、初回ターンの所要時間との相関は r ≈ 0.0、LOC との相関はわずか 0.53 です。1ラウンドあたりのエージェントの作業時間は約9秒/ステップで安定し、ターンの中央値は約45秒です。ばらつきのすべてはラウンド数に由来します。
  • 78/22の二峰性 — 複雑度の高いタスクの78%は、見積もり工数の25%未満で完了する一方、複雑度の低いタスクの22%は **180%**を超過します。原因はコーディングよりも検証と統合です。結合分布は二峰性なので、単一の代表値は谷間に位置します。タスクがどちらのモードに属するかを見極めるうえで、レディネスゲートが最も低コストのフィルターです。大幅な超過は、受け入れ基準がない、またはテストできないことと相関します。
  • アンカリング(Løhre & Jørgensen) — 無関係な数値によって見積もりが 4~5倍動き、「無視してください」と明示されても、その数値の出所が明らかに不適格でも影響は残ります。したがって、アンカーの除去は推論の後に注意書きを添えるのではなく、推論を始める前に行います。同じ人物が同一タスクを再見積もりしても **71%**の差が出るため、記憶に基づく見積もりは決してデータとして扱いません。
  • 参照クラス予測(Kahneman/Lovallo/Flyvbjerg) — まずアウトサイドビューを用い、その後、インサイドビューによる調整は1つのバケット内に限定します。ソフトウェア開発の参照クラスでは、平均超過率が **73%**で、18%の裾野は平均 **+447%**に達します。裾の重い分布なので、デフォルトで P85、外部依存関係が関わる場合は必ず P95 を使います。
  • Evidence-Based Scheduling(Spolsky) — 見積もりと実績の比率を一組として再サンプリングします。速度を平均してはいけません。平均すると、超過を生む裾の値そのものが消えてしまうからです。手戻りは元の項目に計上します。別項目として記録すると、履歴がひそかに正確であるかのように見えてしまいます。
  • サンプルサイズのゲート(Vacanti) — 中央値には5件、観測範囲に意味を持たせるには約11件、上限を公表するには10~20件が必要です。古いデータ150件より、新しいデータ15件のほうが有用です。n=11 の個人別 P85 は、真の値の 0.31~2.26倍の範囲に及びます。そのため、n≈20 未満のデータはすべて、母集団の事前分布に向けて縮小します。
  • 当てはめた分布ではなく、実測の分位点 — 当てはめた対数正規分布は、真の中央値の ×0.72、p95 の ×1.72を示します。一方、実測値の経験分位点は、名目値50/80/90に対して、交差検証の網羅率 49.9/79.8/89.9を記録し、実質的に正確です。実績値を並べ替え、対数空間でデータから分位点を読み取ります。
  • タスクのうち、中央値の2倍以内に収まるのはわずか35%です。これほどばらつきが大きい以上、点推定は正当化できません。答えは区間です。
  • 制約は下流に移りました — タスク処理量が +33.7%増える一方で、レビュー時間は +441%、着手待ち時間は +156%、変更頻度は +861%となり、エージェントによる PR の失敗の **38%は「レビューされないまま」**です。「コードを書き終えた」時点で止まる見積もりは、速くなった部分しか測りません。また、開発者のカレンダー時間のうちコーディングが占めるのは約30~40%にすぎないため、エンドツーエンドの効果が約40%を超えるという主張は、成果ではなく測定誤差です。

コメント