mcpField
B
backfill

backfill

github.com/scaccogatto生産性433343 回インストール今日更新
リポジトリ

backfill について

backfillは、GitのコミットとClaudeのセッション記録からOKFナレッジバンドルを再構築するスキルです。決定的なイベント抽出と監査可能なLLMリプレイを組み合わせ、チェックポイントからの再開やカバレッジ検証にも対応します。

主な機能

  1. 01Gitとセッションイベントの抽出
  2. 02時系列に沿ったイベントの再生
  3. 03並列分析と逐次統合
  4. 04チェックポイントからの再開
  5. 05イベント網羅性とスキーマの検証

活用シーン

  1. 01リポジトリ履歴からOKFバンドルを再構築
  2. 02保存済みチェックポイントから再開
  3. 03イベントがバンドルに反映されたか検証

インストール

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

$npx skills add https://github.com/scaccogatto/okf-skills --skill backfill

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

同梱ファイルscripts/okf_backfill_events.py

詳細

履歴から OKF バンドルを再構築する

このスキルは、リポジトリの意思決定履歴(git コミットと Claude セッションのトランスクリプト)を再生し、okf スキルの Stop フックが最初から有効だった場合と同じように、OKF ナレッジバンドルを再構築します。

抽出レイヤーは決定論的です(同じリポジトリなら、バイト単位で同一のイベントになります)。一方、再生レイヤーは LLM ループであり、再生と監査は可能ですが、バイト単位では同一になりません(タイムスタンプや要約は実行ごとに変わります)。イベントスキーマ(§1)とスキップルール(§3)を参照してください。

1. イベントスキーマ

git コミットとセッションのターンはイベントになります(JSONL、1 行に 1 イベント、タイムスタンプ、次にソースの順でソート):

{"id":"git:<sha>","source":"git","ts":"<ISO8601 Z>","sha":"...","author":"...","subject":"...","body":"...","files":[{"path":"...","add":N,"del":N}]}
{"id":"session:<file>:<lineno>","source":"session","ts":"<ISO8601 Z>","user":"...","outcome":"...","title":"...","branch":"...","skip":"..."}
  • Git イベントは、git log --first-parent --numstat から取得します。
  • セッションイベントでは、各ユーザーメッセージと、そのターンにおけるアシスタントのテキストブロックのうち最後のもの(まとめ)を組み合わせます。~/.claude/projects/<repo-slug>/*.jsonl とワークツリーのサブディレクトリから抽出します。
  • タイムスタンプは、末尾が Z の UTC ISO 8601 文字列に正規化します。
  • skip フィールド(任意、抽出時に追加):シグナルの弱いイベントを示します。§3 を参照してください。再生時に上書きされることはありません。

2. プロトコル:Preflight → Extract → Bootstrap → Replay(Map+Reduce)→ Finalize

Preflight:バンドルを再構築できるか確認する
if [ -d <repo>/.okf ] && ! [ -f <repo>/.okf/.backfill-state.json ]; then
  echo "ERROR: .okf/ exists but is incomplete. Delete it to rebuild from scratch, or pass --resume to continue from the last checkpoint."
  exit 1
fi

.backfill-state.json が存在する場合はカーソルから再開します。存在しない場合は、新規にブートストラップします。

Extract:イベントストリームを生成する
uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" <repo-dir> \
  --out events.jsonl \
  [--no-sessions] [--sessions-dir ~/.claude/projects] \
  [--max-text 2000] [--skip-globs "vendor/**"]

スクラッチパッドに events.jsonl を書き込みます(コミットはしません)。次を報告します:

  • 抽出したイベントの総数、ソースごとの件数、ルールごとのスキップ件数。
Bootstrap:バンドルを初期化する(新規の場合のみ)

.okf/ を次の内容で作成します:

mkdir -p <repo>/.okf
echo 'okf_version: "0.2"' > <repo>/.okf/index.md
echo '# Update Log' > <repo>/.okf/log.md

カーソル(.okf/.backfill-state.json):

{"last_id": null, "done": 0}
Replay:2 フェーズの Map/Reduce プロトコル

意味的な深さと退化防止ルールを徹底するため、再生は 2 つのフェーズに分かれています:

Phase 1:Map(並列分析、イベントごと)

ライブイベント(skip フィールドのないイベント)を対象に、Claude Code の Workflow agentType パラメーターを使い、okf:event-analyzer エージェントを 4~10 件ずつのウェーブで起動します(コストに関する注記は§5を参照)。各アナライザーには、イベント ID、events.jsonl のパス、リポジトリのパス、出力ディレクトリ、エミッターパスを渡します。また、次の処理を行います:

  • jq で自身のイベント JSON を取得します(オーケストレーターが割り当てるのは ID のみで、内容は渡しません)。
  • Git の証拠は、上限付き差分エミッター(§6)を介してのみ読み取ります。セッションイベントでは、ほかに呼び出しは必要ありません。
  • スクラッチパッドに analyses/<event-id>.md(イベント ID の : を - に置き換えたもの)を書き込みます。truncated フラグはエミッターの最終行からコピーします。
  • イベントごとの件数を 1 行で返信します。分析内容は返信に含めません。
  • .okf/ には触れません。
  • 再開可能です。すでに analyses/ にあるイベントはスキップします。

割り当てルール(events.jsonl に基づく決定論的なルールで、内容は読み取りません):ライブイベントがセッションのターン、または numstat の変更行数合計が 60 行以下のコミットであれば、小規模とします。小規模イベントは時系列順に、アナライザー呼び出し 1 回あたり 8 件ずつまとめます。大規模なコミットは 1 回の呼び出しにつき 1 件とします。このリポジトリでの測定結果(benchmark/map-tier/RESULTS.md):呼び出し回数は 147 回から 57 回になり、同じティアで Map フェーズのコストを 3 分の 1 削減しました。イベントごとの指標に低下はなく、イベント間の情報混入もありません。

jq -c 'select(.skip==null) | {id, small: (.source=="session" or (([.files[]?|.add+.del]|add)//0) <= 60)}' events.jsonl

ホスト非依存のフォールバック(Workflow がサポートされていない場合):agents/event-analyzer.md と同じシステムプロンプトを使って汎用サブエージェントをイベントごとに起動し、分析結果をスクラッチパッドに収集します。

フェーズ 2:統合(逐次的に折りたたみ、エージェント 1 つ)

単一の okf:bundle-weaver エージェントを実行し、次の作業を行います。

  • すべての分析を時系列順に読み込みます
  • それらを .okf/ に統合し、コンセプトを更新または作成します
  • 劣化防止ルール(§4)を適用します
  • ライフサイクルイベント(コンセプトの作成、非推奨化、置き換え)のみについて、日付入りの箇条書きを log.md に追加します。通常の更新の「理由」はコンセプト本文に記載します
  • 再開できるよう、カーソル(.okf/.backfill-state.json)を管理します
  • .okf/ に書き込む唯一のアクターとなります
  • 件数(統合、作成、更新、箇条書き、競合、置き換え、切り詰められた入力)を 1 行で返信します。バンドルを返信に含めてはいけません

再開時の動作: どちらのフェーズも再開に対応しています。フェーズ 1 では分析済みのイベント ID をスキップし、フェーズ 2 ではカーソルの last_id から処理を再開します。

ドメインの事前把握(参考情報)

マップ作成フェーズを開始する前に、リポジトリの README とディレクトリ構造を読み、コンセプト(スキル、インテグレーション、意思決定など)の分類案を作成します。この事前把握はあくまで参考情報であり、アナライザーがより適切な候補名を出力しやすくするためのものです。イベントが漏れないことの保証は事前把握ではなく、Finalize(§5)の決定論的な網羅性チェックによって得られます。候補名のない分析もウィーバーに渡されます。

コンテキストの整理(オーケストレーター)

オーケストレーターが扱うのは ID と件数のみで、コンテンツは扱いません。イベント、分析、差分はエージェントが読み込みます。オーケストレーターが読み込むのは次の内容です。

wc -l events.jsonl                                   # total events
jq -r 'select(.skip==null) | .id' events.jsonl       # live ids to dispatch
ls analyses | wc -l                                  # analyses written

オーケストレーターから events.jsonl を cat したり、分析を開いたりしてはいけません。コンテキストに入った内容には、その後のすべてのターンでフロンティア料金が課金されます。このルーティングはそれを防ぐためのものです。

Finalize:検証と後処理
  1. 各ディレクトリの index.md を手作業で作成します(okf_init.py は実行しないでください。プレースホルダーファイルが作成されるためです)。各ディレクトリに # Section を 1 つ、各コンセプトに箇条書きを 1 つ記述します。各コンセプトのフロントマター(SPEC §8)から取得した説明を使い、* [Title](file.md) - description の形式にします。ルートの index.md は okf_version: "0.2" のフロントマターを維持し、すべてのサブディレクトリにリンクします。

  2. 最後のログエントリを追加します(今日の日付を使用し、既存の日付セクションがある場合はその末尾に追加します)。

    ## 2026-09-01
    
    - **Backfill**: reconstructed from N events by okf-backfill/0.9.5
    
  3. カーソルを削除します。

    rm -f .okf/.backfill-state.json
    
  4. 劣化防止ルールのセルフチェックを実行します。

    # No concept filenames derived from change type
    ! find .okf -name "*.md" | grep -E "merge-pull-request|(^|/)(feat|fix|chore|docs)[:-]"
    
    # No non-kebab-case characters in filenames
    ! find .okf -name "*.md" | grep -E "[^a-z0-9/.-]"
    
    # No identical consecutive log bullets
    awk 'p==$0 && /^- / {exit 1} {p=$0}' .okf/log.md
    
    # Every concept declares its lifecycle (absent status reads as `stable`, SPEC §5.4)
    [ -z "$(grep -rL '^status:' --include='*.md' .okf | grep -v '/index.md$\|/log.md$')" ]
    

    これらは字句的なチェックであり、バンドルが置き換えられた状態を主張していても検出できません。このチェックは意味的なものであり、分析とディスク上の以前のコンセプトの両方を参照できる折りたたみ時に、ウィーバーが行う必要があります(§4 ルール 5)。

  5. 網羅性チェックを実行し、すべてのイベントがマッピングされていることを確認します。

    uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" \
      --check-coverage events.jsonl .okf
    

    終了コード 0 は、すべての有効なイベント(スキップされていないもの)がバンドルの sources またはログに含まれていることを意味します。終了コード 1 とマッピングされていない ID のリストが出力された場合、ウィーバーが一部のイベントをスキップしています。修正して再実行してください。

  6. バンドルのスキーマを検証します。

    uv run "${CLAUDE_SKILL_DIR}/../validate/scripts/okf_validate.py" .okf --strict
    

    完了する前に、すべてのエラーを修正してください。

  7. 結果のバンドルを報告します。

    • ディレクトリごとのコンセプト数
    • ログエントリのサンプル(最初と最後)
    • 網羅性チェックの結果(すべてのイベントをマッピング済み)
    • 検証結果(合格/不合格、警告)
    • 置き換えの解決数(ウィーバーの superseded 件数)と非推奨コンセプト数: grep -rl '^status: deprecated' --include='*.md' .okf | wc -l
    • フェーズごとに起動したエージェント数(アナライザー、ウィーバーの呼び出し回数)、およびホストから報告される場合はフェーズごとのトークン数
    • 切り詰められた分析の数:grep -l '^truncated: true' analyses/*.md | wc -l。その隣に、デフォルト上限を超える完全な差分を持つ Git イベントの決定論的な推定数を記載します。 jq -r 'select(.skip==null and .source=="git") | [.files[]|.add+.del] | add' events.jsonl | awk '$1>300' | wc -l

3. スキップルール

以下のルールに一致するイベントには skip: <rule-id> が付与され、リプレイ時にスキップされます(カーソルは進み、ログエントリーは記録されません)。

RuleConditionRationale
paths-only-generatedGit のコミットがロックファイルまたは生成コード(vendor/, node_modules/, dist/, .min., *.lock など)のみに触れているノイズ:ビルドの副産物であり、ナレッジコンテンツがないため
merge-no-filesファイル変更のない Git マージコミット(first-parent のみ)ハウスキーピング
session-command-noiseセッションのユーザーメッセージがスラッシュコマンド、または有用な結果を伴わない 20 文字未満のメッセージ一時的な UI/雑談

リポジトリ固有のパターンには、--skip-globs "extra/**" を指定して最初のルールを拡張します。

4. 劣化防止ルール

アナライザーとウィーバーは、バンドルがコミットの機械的な羅列や、偶然生じた分類体系へと劣化するのを防ぐため、以下のルールを適用します。

  1. コンセプト名には変更ではなく、ドメインエンティティを記述します。 次は禁止です。

    • Git の subject から派生したファイル名:merge-pull-request-#2-....md、feat:-add-docs-skill.md、fix:-handle-edge-case.md
    • 単独のアクション語:update.md、fix.md、add-feature.md
    • kebab-case 以外:スペース、アンダースコア、大文字、特殊文字(使用可能なのは [a-z0-9-] のみ)

    例:subject が「feat: add presales pipeline」のコミットが、あるドメインコンセプトに触れているとします。 コンセプトには、変更内容ではなくエンティティを表す presales-pipeline.md を使用します。変更履歴は frontmatter の sources と本文の ## History に記載し、log.md にはライフサイクルイベントのみを記録します。

  2. 新規作成より更新を優先します。 各イベントで、どのコンセプトに触れるかを分析します。コンセプトがすでに存在し、分析内容に合致する場合は、その sources と本文を更新します。分析によって、まだ記録されていない独立したドメインエンティティが明らかになった場合にのみ、新しいコンセプトを作成します。

  3. ログの箇条書きはライフサイクルイベントであり、その意図を説明します。 コンセプトの作成、非推奨化、または置き換えが行われた場合にのみ箇条書きを追加します。通常の更新では追加しません。箇条書きでは、コミット本文またはセッションの結果から「なぜ?」に答えます。subject をそのまま書き直すだけにしてはいけません。

    • 悪い例:- Added presales-pipeline.md feature
    • 良い例:- **Creation**: [Presales pipeline](...), 引き継ぎポイントを明確にするために体系化
  4. 同一の箇条書きを連続して記載しないでください。 同じコンセプトに関する箇条書きが連続する場合、更新内容がログに漏れています。

    - Feature X update
    - Feature X update
    - Feature X update
    

    代わりに箇条書きは一切追加しません。更新内容はコンセプトの本文と sources に記載します。

  5. 変更の取り消しは成長ではありません。 リプレイは履歴を順にたどるため、後のイベントによって、それ以前の内容が定期的に覆されます(フェーズの削除、上限の変更、目標の格下げなど)。「新規作成より更新を優先する」はマージのルールであり、この問題については何も述べていません。このルールだけを適用すると、バンドル内の 1 つまたは 2 つのコンセプトに、現在の状態として両方の内容が記載されたままになります。ウィーバーは書き込む前にバンドル全体を grep で検索し、矛盾があれば追記ではなく置き換えを行います。本文には現在有効な内容を記載し、以前の内容は日付付きの行として ## History の下に移します。また、新しいコンセプトに置き換えられた古いコンセプトには status: deprecated を設定し、後継へのリンクを追加します。非推奨のコンセプトにも sources は保持するため、カバレッジは維持されます。Finalize のガードではこれを検出できません。ガードは字句的な検証であり、置き換えられた主張も形式上は正しいためです。

  6. 再構築されたコンセプトのステータスは draft とし、stable にはしません。 SPEC §5.4 では、status がない場合は stable(「利用可能な状態」)として扱います。誰も読み返していないバンドルは draft(「まだレビューされておらず、不完全な可能性がある」)です。ウィーバーはすべてのコンセプトに status: を記述します。

5. 実装上の注意点

  • 決定性:抽出結果はバイト単位で同一ですが、再生結果は同一ではありません(時間、LLM のばらつき)。 どちらも監査可能です。events.jsonl は決定的で、マップ分析は保存され再開可能です。 リデュースループは人間が読める形式で、カーソルに基づいています。
  • プライバシー:events.jsonl はスクラッチパッドに保存され、コミットされることはありません。セッションのターンテキストは、抽出前に先頭と末尾を残して切り詰められます。
  • コストに関する注意:詳細な再生では、根拠を抽出するため、各 git コミットにつき上限付きの diff を 1 つ読み込みます(§6)。イベント数が 500 を超える履歴では、サブ範囲に分割して順次再生するか、--skip-globs を使ってシグナルの弱いパス(例:ベンダー依存関係、生成コード)を除外することを検討してください。マップフェーズは 4〜10 件の並列処理で進みます。ゲートベンチマークでは、同時実行数が多い場合の一斉障害により 299 回中 260 回が失敗し、同時実行数 4 で完了しました(benchmark/gate/RESULTS.md)。リデュースは順次処理ですが、コストははるかに低くなります(概念はすでに分析済みです)。
  • 今後のソース(未実装):GitHub の PR/issue テキスト、リリースノート、CI/デプロイログ — いずれも MVP 後に追加する任意の機能です。Lore プロトコル(arXiv 2603.15566):git トレーラー(構造化された意思決定メタデータ)を採用しているリポジトリでは、抽出されたイベントの body に、トレーラーがすでに機械可読形式で含まれています。アナライザーとウィーバーは、diff の内容から一般的に推論するのではなく、トレーラーを「理由」の主要な情報源として使う必要があります。
  • 信頼メタデータ:generated.by はバックフィルエージェント(okf-backfill/0.9.5)を示しており、人間によるレビュー済み(human:...)とは主張していません。概念は正しく unverified(SPEC §5.3)かつ status: draft(§5.4)です。2 つの軸は独立しています。信頼性は誰が述べたかを示し、ライフサイクルは再読されたかどうかを示します。ここで status の書き込みは任意ではありません。指定されていない場合、§5.4 では stable として扱われるため、誰も再読していないバンドルが、利用可能な状態だと自ら宣言することになります。マップフェーズの分析は作業用成果物(リデュース中の監査可能性のために保存)であり、ウィーバーの出力が正規のバンドルです。

6. 上限付き diff エミッター

アナライザーは生の git show を読み込みません。ハーネスはツールの出力が長いと無作為に切り詰めるため(ハンクの途中、文字数基準、ホスト依存)、低コストのワーカーは切り詰めに気付かない可能性があります。代わりに、エクストラクターが diff 全体を読み込み、決定的なサンプルを出力します。

uv run "${CLAUDE_SKILL_DIR}/scripts/okf_backfill_events.py" <repo-dir> --show <sha> \
  [--only <path>] [--max-diff-lines 300] [--per-file-lines 120] [--max-line-chars 400] \
  [--skip-globs "vendor/**"]
  • 最初の親との差分を使うため、マージコミットでも抽出された numstat と一致します。
  • 統計情報は常に完全です。上限に達してもエンティティは保持されます。
  • パッチはファイルごとに上限が設定され(深さより幅を優先)、ハンクまたはファイルの境界で切られ、切断箇所ごとに角括弧付きのマーカーが付加されます。--max-line-chars より長い行はマーカー付きで短縮されます(生成された 1 行のファイルは「1 行」でも、数百 KB になる場合があります)。
  • 生成ファイル(ロックファイル、vendor/**、--skip-globs)のパッチは、複数の種類が混在するコミットでも省略されます。その統計行は残ります。
  • 最終行は固定形式です:[diff: shown=X total=Y files_shown=A files_total=B truncated=true|false]。 アナライザーは truncated をフロントマターにコピーし、finalize が件数を報告します。
  • 切り詰められた diff に根拠が隠れている場合に許可される追加の取得方法は、--only <path> だけです。同じ上限ロジックで、単一ファイルを取得します。

デフォルト設定では、余裕を持ってハーネスの制限内に 1 回の呼び出しが収まります。リポジトリに合わせてフラグを調整してください。

アナライザーのティアはインフラストラクチャではなく設定です。agents/event-analyzer.md のフロントマターに model と effort が記載されています。ファイルをフォークしてマップフェーズのティアを変更してください。スキルはエージェントを名前で解決し、それ以外は何も変更されません。デフォルトが haiku なのは、マップティアベンチマーク(benchmark/map-tier/RESULTS.md)で、コミットあたりのマップコストが半分未満でありながら、sonnet と同等の結果が得られたためです。ただし、エージェントファイル内の明示的な指示 2 つ(フラグがコピーする要約行、主張では意図を意図として報告すること)は維持する必要があります。

コメント