Agent Trace × Jujutsu でたどる AI コードの来歴

Agent Trace × Jujutsu でたどる AI コードの来歴

2026.06.11

Introduction

生成 AI が日常的にコードを書く時代になり、コードベースには
「人が書いたコード」と「AIが書いたコード」が混在するようになりました。
最近はコードのほとんど/全てをAIエージェントが書いている状態も珍しくなく、
レビュー時にも保守時にも、コードの履歴をどう扱うかは課題となっています。

現状のVCS(git/jujutsu等)はそれに対して明確化する機能はありません。
git blamejj file annotate の author に出すのは「エージェントを操作した人間」であり、
どの行が AI で、どのモデルが書いて、どの対話の結果なのかを区別する仕組みがありません。
また、エージェント側のセッション履歴はプロダクトごとに仕様があり、横断的に追えません。
レビューで「ここは AI によるコードだから云々」、保守で「このコードの意図をAIとのログから確認したい」
などの要件は、今のところ手作業になります。

この課題を解決する方向性のひとつが 「Agent Trace」 です。
ThoughtWorks Technology Radar でも Assess として取り上げられました

本記事では以下について解説します。

  • Agent Trace 仕様の概要
  • Claude Code の hook で trace を書く参考実装
  • TUIツール trace を読む参考実装

この記事では、AI AgentはClaude Code、VCSにjujutsu(以下jj)を使います。
※jjについてはこちらなどを参照

About Agent Trace

Agent Trace は Cursor が 2026 年 1 月に公開した RFC v0.1.0 のオープンなデータ仕様で、
README によれば Cognition(Devin)や Google Jules が策定に協力したパートナーとして挙がっています。
製品ではなく純粋なデータ仕様という立場で、
「どの行を / どの AI が / どの会話で書いたか」を記録するため、
ベンダー中立なフォーマットを目指しています。

ちなみに Cursor 自身は、製品内に 「AI Share of Committed Code」というダッシュボードを持っています。
コミットされた行のうち AI が書いた割合を、行ごとの diff signature をもとに集計するもの(Team / Enterprise 向け)です。
ただしこれは Cursor 製品内に閉じた機能で、Agent Trace とは公式には別物です。
Agent Trace はそこから「行レベルで AI 帰属を記録する」という発想だけを切り出し、
特定 IDE に依存しない共通フォーマットにしたもの、と捉えると位置づけが掴みやすいです。

データモデル(TraceRecord)

TraceRecordは、「1 record = 1 回」の編集イベントです。
JSONL などに追記していく形式で、参照実装 は sidecar JSONL(.agent-trace/traces.jsonl)に書き出します。
スキーマは schemas.ts で定義されています。

{
  "version": "0.1.0",
  "id": "uuid",
  "timestamp": "2026-06-05T14:20:00Z",
  "vcs": { "type": "jj", "revision": "xqnktzmlworukplnyrropmtzylsuxxlv" },
  "tool": { "name": "claude-code", "version": "2.x" },
  "files": [{
    "path": "src/app/navigation.rs",
    "conversations": [{
      "url": "https://…/session/abc123",          // ← 会話への経路
      "contributor": { "type": "ai", "model_id": "anthropic/claude-opus-4-8" },
      "ranges": [{ "start_line": 29, "end_line": 52 }]
    }]
  }],
  "metadata": { "com.example.custom": "…" }        // ベンダー拡張
}

データモデルは以下のような属性を持ちます。(抜粋)

  • contributor*: human / ai / mixed / unknown の 4 値 + 任意の model_id
  • vcs.type: git / jj / hg / svn
  • 保存場所は仕様で未定義(ローカルファイル / git notes / DB など自由)
    ※参照実装は sidecar JSONL
  • 行番号は「記録時 revision における位置」
    ※追跡用に content_hash定義されている

構成

[agent host]           [仕様]               [VCS]                     [reader]
Claude Code / Cursor → TraceRecord 生成 →→→ revision に紐付け →→→→→→→→→ blame/annotate 的照会
(hook で編集を捕捉)      (JSONL 追記)         (git SHA / jj change ID)   (未整備)

trace を書くのは AI エージェント側(hook)で、VCS は識別子を提供するのみ。
trace を読む(query/viewer)側は、Git AI のような個別実装はあるものの、標準ツールはまだ整っていません。

jjで実現する方法

jj 自体は agent hook を持たないため、
現状はjj リポジトリ上で動く AI エージェントに trace を書かせて
jj の識別子で紐付ける構成にします。
今回は以下のように実装してみました。

agent host の hook を使う

Claude Code を起動するリポジトリの .claude/settings.json
PostToolUse 等の hook を登録し、ファイル編集のたびに
「trace を書き出すスクリプト」を起動します。

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{ "type": "command", "command": "<trace 書き出しスクリプト>" }]
    }],
    "SessionStart": [ /* 同様 */ ],
    "SessionEnd":   [ /* 同様 */ ]
  }
}

スクリプトがやることは、
「hook の payload から編集情報を取り出し、TraceRecord を生成して
.agent-trace/traces.jsonl に 1 行追記する」
です。Cursor 公式の参照実装 は TypeScriptですが、
言語・実装は問いません。
※本記事ではBash で最小実装したものを使用

なお、ファイル編集後に発火する hook(post-edit hook)を持つエージェントなら、同じ構成を応用できます。

jj VCS adapter

参照実装の getVcsInfo()git rev-parse HEAD を実行する作りになっており、
jj リポジトリでは revision の紐付けが正しく取れません。
そこで スクリプトの中でjj コマンドを実行してchange ID を取り、
TraceRecord の vcs.revision に埋め込むようにします。

# working copy change の change IDを取得
REV=$(jj log -r @ --no-graph --ignore-working-copy -T 'change_id')
# → 例: "xqnktzmlworukplnyrropmtzylsuxxlv"

TraceRecord には↓のように設定されます。

"vcs": { "type": "jj", "revision": "xqnktzmlworukplnyrropmtzylsuxxlv" }

実装の具体は後述する Bash hook(REV=$(jj -R "$ROOT" log ...) の行)を参照してください。
注意点は以下。

項目 内容
change ID を使う(commit ID ではない) jj の commit ID は amend/rebase などで変わるが、change ID は安定している。ただし squash で change 自体が畳まれると、元の change を指す trace は孤立
--ignore-working-copy を付ける hook から jj を実行すると snapshot が走り、編集途中のファイルが意図せず commit に取り込まれる副作用を避ける
git fallback は非推奨 colocated repo でも git rev-parse HEAD は jj の @ ではなく親 change(@-)の commit を指すことがあり、編集した change とずれる。rewrite でも壊れるので change ID を使うべき
working copy の扱い jj では編集したものが即 @ に反映されるので、編集時点の @ の change ID を記録すれば「この change の作業中に書かれた」とわかる

保存先は参照実装どおりのパス(<jj root>/.agent-trace/traces.jsonl)に追記していきます。
書き終わった trace を後から「この行は AI が書いたのか?どの会話なのか?」と辿るには、
jj 標準コマンドと汎用ツールで以下のように確認できます。

# 1. 気になる行 → change ID
#    jj file annotate が行ごとに「最後にその行を書いた change の ID」を返す
jj file annotate src/app/navigation.rs

# 2. change ID → trace record
#    annotate は短縮 ID、record はフル長なので prefix で grep
grep '"revision":"xqnktzml' .agent-trace/traces.jsonl | jq .

# 3. record の URL を開く(このデモ hook は related[].url にセッション URL を出す。
#    仕様では会話そのものを指す conversations[].url も定義されている)
#    その行が生成された会話セッションにそのまま飛べる

これで「コード行 → change ID → record → 会話 URL」とたどることができます。

Use jj

jj が有利な点

Agent Trace の注意点として、参照する revision が書き換わると
行範囲や紐付けが古くなってしまうことがありますが、jj を使うことで緩和できます。
jj の change ID は rebase や amend をしても変わらないので、
AI エージェントの「生成 → 人間が手直し → rebase」というフローでも
trace の指す revision が壊れにくく、追跡できます。
(ただし jj squash のように change そのものが別の change へ畳まれるケースでは、
その change を指していた trace は孤立。後述の孤立 trace 検出で拾えます)

また、jj は working copy が常に commit(@)になっているので、
編集イベントがその場で @ の change ID に紐付きます。
git で発生する
「commit するまで編集が revision に紐付かない(hook 時点の HEAD は編集前の親)」
という状態が、jj では発生しません。

さらに、jj evolog や op log で操作履歴を追えるので、
匿名ブランチや rebase/squash が多発しても後から辿りやすくなります。

このため、jj は Agent Trace と相性が良さそうです。

注意点

jj split で change を 2 つに分割した場合、trace は分割前の change ID を指したままで、
どちらに帰属させるかは仕様で決まっていません。
change ID が定まっていても、その後の編集で行位置はずれていくので、
長期的に履歴を追うには仕様の content_hash を生成する writer が必要ですが、
現状の参照実装は値を出力していません。
同一の change ID が複数 commit を持つ状態の場合、「どの variant の行か」が曖昧になります。

ツールもまだ揃っておらず、仕様にも未確定の部分が多く残っている点は
念頭に置いておきたいところです。

jj ワークフローの流れ

trace を書く時は、jj リポジトリで Claude Code に実装させると
hook が .agent-trace/traces.jsonl に record を追記します。
(その時点の @ の change ID 付き)

そのあと jj rebase / describe で歴史を整えても、
change ID が維持される範囲では trace を追跡しやすいです。
jj squash で change を畳んだ場合は、その change の trace が孤立するので、
後述の検出で拾います。

後日、「この実装はなぜこうなってるのか?」と思った時、
jj file annotate で対象行 → change ID を引いて、trace の conversation URL から
当時の AI セッションを開いて意図を確認できます。
レビュー時も、change ごとに「AI 寄与あり/なし」「モデルは何か」が分かるので、
それに応じて人が判断可能です。

Try Agent Trace

Agent Traceの概要について解説したので、
次は jj リポジトリで trace を作成して読んでみましょう。
今回は「動かして肌触りを掴む」レベルの最小実装で確認してみます。
※仕様完全準拠するには参照実装の完全移植が必要

Claude Code の hook で trace を出力する

Claude Code の PostToolUse フックで、Write / Edit が走るたびに
.agent-trace/traces.jsonl に 1 行 JSON を追記します。
jj VCS adapter として jj log -r @ で change ID を取得し、vcs.type: "jj" で書き込みます。

.claude/hooks/agent-trace-jj.shは以下のように作成。

#!/bin/bash
# PostToolUse (Write|Edit) で実行。.agent-trace/traces.jsonl に append する
IN=$(cat)
FILE=$(echo "$IN" | jq -r '.tool_input.file_path // empty')
SESSION=$(echo "$IN" | jq -r '.session_id // empty')
[ -z "$FILE" ] && exit 0

# 編集対象が jj 管理下のリポジトリかを判定
ROOT=$(cd "$(dirname "$FILE")" 2>/dev/null && jj root 2>/dev/null) || exit 0

# 現在の working copy change の change ID (rewrite 後も安定)
# --ignore-working-copy で snapshot を回さない (編集途中の取り込みを防ぐ)
REV=$(jj -R "$ROOT" log -r @ --no-graph --ignore-working-copy \
        -T 'change_id' 2>/dev/null) || exit 0

REL=${FILE#"$ROOT"/}
mkdir -p "$ROOT/.agent-trace"

jq -cn \
  --arg id "$(uuidgen | tr 'A-Z' 'a-z')" \
  --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --arg rev "$REV" --arg file "$REL" --arg session "$SESSION" '
{
  version: "0.1.0",
  id: $id,
  timestamp: $ts,
  vcs: { type: "jj", revision: $rev },
  tool: { name: "claude-code" },
  files: [{
    path: $file,
    conversations: [{
      contributor: { type: "ai", model_id: "anthropic/claude-opus-4-8" },
      related: [{ type: "session", url: ("claude-code://session/" + $session) }]
    }]
  }]
}' >> "$ROOT/.agent-trace/traces.jsonl"
exit 0

.claude/settings.json へ以下のように登録。

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{ "type": "command", "command": "bash .claude/hooks/agent-trace-jj.sh" }]
    }]
  }
}

この hook は Write / Edit のツール呼び出しごとに発火するので、
同じファイルを何度か編集するセッションでは同じ change ID で複数 record が並びます。
MultiEdit も拾いたい場合は matcher を "Write|Edit|MultiEdit" とする

.gitignore.agent-trace/ を入れ、
trace 追記で working copy が dirty にならないようにします
※jjでも .gitignore をそのまま 適用可能

なお上のスクリプトは model_id を固定値で書いています。PostToolUse の payload に
.model は無いため、実モデルを記録したい場合は payload の
transcript_path(セッションの JSONL)から最後の
assistant ターンの .message.model を取得する必要があります。

動作確認

テスト用の jj リポジトリを用意し、先程の hook を設定して Claude Code に
src/trace/mod.rssrc/app/navigation.rs を作って」と頼んだ結果が以下。
Write のたびに .agent-trace/traces.jsonl へ record が 1 行ずつ追記されました。

# 時刻 / change ID / ファイル / モデルを確認
$ jq -r '"\(.timestamp)  \(.vcs.revision[0:8])  \(.files[0].path)  \(.files[0].conversations[0].contributor.model_id)"' \
    .agent-trace/traces.jsonl
2026-06-10T02:02:52Z  pvowmytz  src/trace/mod.rs       anthropic/claude-opus-4-8
2026-06-10T02:02:55Z  pvowmytz  src/app/navigation.rs  anthropic/claude-opus-4-8

change ID がどちらも pvowmytz で同じになってます。
jj は working copy が常に @ なので、
同じ作業中に書かれた両ファイルが同じchange に紐付いています。

次に、jj file annotate で行 → change ID を見て、
その change ID で trace records を検索すれば「この行は AI が書いたのか」を確認できます。

# 行 → change ID
$ jj file annotate src/trace/mod.rs
pvowmytz nakamura 2026-06-10  1: //! トレース記録モジュール
pvowmytz nakamura 2026-06-10  2:
pvowmytz nakamura 2026-06-10  3: /// 1件のトレースイベント
...

# change ID → trace record
# annotate 表示は短縮形、record はフル長なので prefix で grep
$ grep '"revision":"pvowmytz' .agent-trace/traces.jsonl | head -1 | jq .
{
  "version": "0.1.0",
  "id": "b15609b7-9df3-4695-96ca-0ea2062e6f0e",
  "timestamp": "2026-06-10T02:02:52Z",
  "vcs": { "type": "jj", "revision": "pvowmytzxwmuqqowssmuskwxxpzlwsqk" },
  "tool": { "name": "claude-code" },
  "files": [{
    "path": "src/trace/mod.rs",
    "conversations": [{
      "contributor": { "type": "ai", "model_id": "anthropic/claude-opus-4-8" },
      "related": [{ "type": "session", "url": "claude-code://session/<SESSION_ID>" }]
    }]
  }]
}

「指定行 → 指定change → AI が claude-opus-4-8 で書いたセッション」を確認できます。
なお、この簡易 hook は ranges(行範囲)を出していないので、record の粒度はファイル単位です。
ranges は仕様上 conversation の必須フィールドなので、この時点の record は
RFC 非準拠の最小デモです。

TUIツールで読む(RFC reader 部分の最小実装)

hook によって trace(.agent-trace/traces.jsonl)は出力されるようになりました。
ですが、書き出せただけで、それを人が確認する手段がありません。

jj file annotategrepjq でも確認できますが、量が増えるとすぐに限界が来ます。
そこで、自作の jj 用 TUI ツール(以下tij) に Agent Trace 機能を組み込んでみました。
※RFC の reader 部分のうち、jj ワークフローで使う機能だけを最小実装したものです

tij が提供する Agent Trace 機能は以下です。

操作 効果
Log View AI 寄与のある change に [AI] バッジを表示([AI?] は git SHA 経由で change ID に解決できないケース)
:show-traces 選択中の change の trace records を一覧(time / tool / model / files)
Enter on record conversation URL をクリップボードへコピー
jj config set --repo tij.agent-trace.path <path> trace ファイルのパスをカスタマイズ

これで、以下のような処理が TUI 上で完結します。

  1. 気になる行を見つける
  2. jj file annotate で change を特定
  3. Log View で [AI] バッジを確認
  4. :show-traces から会話 URL をコピー

agent-trace-2.png

[AI] バッジと行マーカー(

さきほどのファイル単位 hook(ranges なし)で書いた trace を tij で開くと、
Log View の [AI] バッジは出ますが、Diff View 側の 行マーカー(、どの行が AI か)は出ません。
(行マーカーはrange が空だと出ない)

tij の表示 必要なデータ ranges なし hook
Log View の [AI] バッジ change に record があればOK 出る
Diff View の 行マーカー ranges[](行範囲)が必須 出ない

行範囲も出してみましょう。
Claude Code の PostToolUse payload には 行情報がそのまま入っているので、これを使います。

# AI が書いた行範囲 (ranges) を算出
#  Edit : structuredPatch の追加行(+)の new-file 行番号から hunk ごとに min..max
#  Write: structuredPatch が無い/空のときは content 全体を 1..N で代用
#  ※ tool_response の形状は tool 依存なので、両者を見て fallback する作りにしている
RANGES=$(printf '%s' "$IN" | jq -c '
  def hunk_adds:
    . as $h
    | [ foreach (($h.lines)//[])[] as $l (
          { ln: (($h.newStart)-1), cur: null };
          if ($l|startswith("-")) then { ln: .ln, cur: null }
          else { ln: (.ln+1),
                 cur: (if ($l|startswith("+")) then (.ln+1) else null end) } end;
          .cur ) ]
      | map(select(.!=null));
  if ((.tool_response.structuredPatch)//[] | length) > 0 then
    [ .tool_response.structuredPatch[] | hunk_adds | select(length>0)
      | {start_line: min, end_line: max} ]
  elif (.tool_name=="Write") then
    [ { start_line: 1, end_line: ((.tool_input.content)//"" | split("\n") | length) } ]
  else [] end
')

あとは record 生成時に conversations[].ranges
--argjson ranges "$RANGES" で差し込むだけです。

テストリポジトリで「src/util.rs を作って関数を1つ書く(Write)→ もう1つ追記する(Edit)」
をやらせると、以下のようになりました。

$ jq -r '"\(.files[0].path)  range=\(.files[0].conversations[0].ranges)"' \
    .agent-trace/traces.jsonl
src/util.rs  range=[{"start_line":1,"end_line":5}]   # Write: add() を書いた全体
src/util.rs  range=[{"start_line":5,"end_line":9}]   # Edit : multiply() を追記した分

この状態で tij の Diff View(Single + color-words)を開くと、
src/util.rs の AI 行にマーカーが出ます(gutter の縦線)。
[AI] バッジ(ファイル単位)に加えて、行レベルでどこが AI 記述か見えるようになりました。

agent-trace-1.png

ですが、現状、行範囲は目安です。記録後の編集で行はずれていくので、
「だいたいこの辺が AI」くらいのヒントとして見るのが正解です。
追従させるなら、仕様の content_hash(行内容のハッシュ)を hook で出して行番号ではなく
内容で再アンカーする必要があります(今回の hook も参照実装も未対応)。

現状の tij の reader 機能は以下。

  • vcs.type: "jj"(change ID 紐付け)と vcs.type: "git"(SHA 紐付け、[AI?] バッジ)の両方を読み込み
  • tool.name / tool.versioncontributor.model_idconversations[].url / related[].url の表示・コピー
  • ranges[] を使った Diff View の行マーカー overlay(上で実際に を出したもの)
  • content_hash 追跡、署名検証は未対応(upstream の RFC 自体が議論中なので様子見)

end-to-end testing

設計の話だけだと「で、実際に動くの?」が残るので、
テスト用jj リポジトリに Claude Code の hook を仕込み、AI 生成 → 人間修正 → tij
で表示まで確認してみます。

セットアップ

新規 jj リポジトリに、「Try Agent Trace」で作った jj-aware hook を
.claude/hooks/agent-trace-jj.sh として置き、.claude/settings.json
PostToolUse(Write|Edit) に接続。.gitignore.agent-trace/
除外(除外しないと trace 追記が working copy を dirty にする)。

hook の要点は先述のとおりで、編集のたびに「現在の @ の change ID」を vcs.type:"jj" で記録します。

3 つのシナリオ

次の 4 change を作りました(うち 3 つが検証対象)。

change 操作 trace
initial README 人間が README 作成 なし
feat: add calc (AI) AI が calc.py を生成(hook 発火) calc.py 1..10 / ai
docs: usage notes (human) 人間が README 編集(hook なし) なし
feat: greeter (AI) + manual tweak (human) AI が greet.py 生成 → 人間が同ファイルに shout() 追記 greet.py 1..6 / ai

最後の change が「同一ファイル・同一コミットで AI と人間が混在」のケースです。
AI が書いたのは 1..6 行、人間が後から足した shout() は 8..11 行目になります。

tij での表示結果

tij を起動して Log View を見ると、期待どおりに出ました。

agent-trace-3.png

change 表示 解釈
feat: greeter (AI) + manual tweak (human) [AI] AI 寄与の record が 1 件でもあれば付く(混在でも [AI]
docs: usage notes (human) バッジなし trace が無いので何も出ない
feat: add calc (AI) [AI] AI record あり
initial README バッジなし

change 数が増えてくると「AI が触った change だけ見たい」が出てくるので、
command palette の filter-ai で Log View を AI 寄与 change のみに絞れます
※もう一度実行で解除

そして混在 change(greet.py)の Diff View では、AI が記録した 1..6 行にだけ
ガターが付き、人間が足した shout()(8..11 行)には付きませんでした。

agent-trace-4.png

change 単位のバッジは「AI が混ざっているか」、
行単位のオーバーレイで「どこが AI / どこが人間か」が見えるようになりました。
レビュー時に「AI 行は精読、手書き行は流し読み」を 1 画面で確認できます。

結果

  • 末尾追記なら range は正確: 人間が shout() をファイル末尾に足したので、AI の記録 range 1..6 は正しい AI 行を指す。
  • 中間挿入だとドリフト: もし人間が AI 行の間に挿入していたら、記録済み range 1..6 はズレる(writer は再記録しないため)。content_hash 実装するまで残る制約。
  • hook はファイル単位の粗い range: デモ hook は「新ファイルの 1..N」を 1 range として記録するだけ。precise な per-edit range は edit の old/new diff が要る(参照実装がやっている)。tij はどちらの粒度の record も読めます。

Summary

Agent Trace は「どの行を・どの AI が・どの会話で書いたか」を記録するためのデータ仕様です。
必要なのは、
agent host の hook・change ID を書く小さな adapter・sidecar JSONL
の3点です。

jjの場合、change ID が rewrite に強いので、AI が書いた版を人間が手直ししても
trace が壊れにくく、change が abandon されたときも孤立として機械的に拾えるので
比較的相性が良さそうです。
jj split 時の帰属や、編集による行ドリフトは課題ですが

Agent Trace はまだ RFC(v0.1.0)で参照実装も小規模、
jj や git といった VCS 本体は Agent Trace を取り込んでいません。
ですがコーディングエージェントが当たり前になった今、「AI が書いた行をどう識別・追跡するか」は
いずれ誰かが答えるべき問いで、その共通フォーマットとして Agent Trace が固まる可能性はあります。

仕様が落ち着き、Cursor 以外の主要エージェントや VCS 側が hook / reader 仕様に対応すれば、
外側のラッパーで個別に書いている処理は、標準ツールに吸収されていくかもしれません。
Agent Traceの今後の流れに注目しておきましょう。

Appendix

hook 全文

コピペで使える最終版。(jj adapter + 実モデル取得 + ranges 出力)
.claude/hooks/agent-trace-jj.sh として置き、
本文のとおり .claude/settings.jsonPostToolUse(Write|Edit) に接続してください。
jq / uuidgen / jj(>= 0.42.0)が PATH にあれば動きます。

#!/bin/bash
# PostToolUse (Write|Edit) で実行。.agent-trace/traces.jsonl に append する
IN=$(cat)
FILE=$(echo "$IN" | jq -r '.tool_input.file_path // empty')
SESSION=$(echo "$IN" | jq -r '.session_id // empty')
[ -z "$FILE" ] && exit 0

# 編集対象が jj 管理下のリポジトリかを判定
ROOT=$(cd "$(dirname "$FILE")" 2>/dev/null && jj root 2>/dev/null) || exit 0

# 現在の working copy change の change ID (rewrite 後も安定)
# --ignore-working-copy で snapshot を回さない (編集途中の取り込みを防ぐ)
REV=$(jj -R "$ROOT" log -r @ --no-graph --ignore-working-copy \
        -T 'change_id' 2>/dev/null) || exit 0

# model_id: transcript の最後の assistant ターンから実モデルを拾う
TRANSCRIPT=$(printf '%s' "$IN" | jq -r '.transcript_path // empty')
RAW=$(jq -rs 'map(select(.type=="assistant" and .message.model
           and .message.model != "<synthetic>")) | last.message.model // empty' \
      "$TRANSCRIPT" 2>/dev/null)
BASE=$(printf '%s' "${RAW:-claude-opus-4-8}" | sed -E 's/-[0-9]{8}$//')
case "$BASE" in claude-*) MODEL="anthropic/$BASE";; *) MODEL="$BASE";; esac

# AI が書いた行範囲 (ranges) を算出
#  Edit : structuredPatch の追加行(+)の new-file 行番号から hunk ごとに min..max
#  Write: structuredPatch が無い/空のときは content 全体を 1..N で代用 (新規=全行 AI)
#  ※ tool_response の形状は tool 依存。両者を見て fallback する
RANGES=$(printf '%s' "$IN" | jq -c '
  def hunk_adds:
    . as $h
    | [ foreach (($h.lines)//[])[] as $l (
          { ln: (($h.newStart)-1), cur: null };
          if ($l|startswith("-")) then { ln: .ln, cur: null }
          else { ln: (.ln+1), cur: (if ($l|startswith("+")) then (.ln+1) else null end) } end;
          .cur ) ]
      | map(select(.!=null));
  if ((.tool_response.structuredPatch)//[] | length) > 0 then
    [ .tool_response.structuredPatch[] | hunk_adds | select(length>0) | {start_line: min, end_line: max} ]
  elif (.tool_name=="Write") then
    [ { start_line: 1, end_line: ((.tool_input.content)//"" | split("\n") | length) } ]
  else [] end
')

# realpath で正規化してから相対パスを得る (macOS の /tmp→/private/tmp 対策)
ABS=$(cd "$(dirname "$FILE")" && pwd -P)/$(basename "$FILE")
REL=${ABS#"$ROOT"/}
mkdir -p "$ROOT/.agent-trace"

jq -cn \
  --arg id "$(uuidgen | tr 'A-Z' 'a-z')" \
  --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --arg rev "$REV" --arg file "$REL" --arg session "$SESSION" --arg model "$MODEL" \
  --argjson ranges "$RANGES" '
{
  version: "0.1.0",
  id: $id,
  timestamp: $ts,
  vcs: { type: "jj", revision: $rev },
  tool: { name: "claude-code" },
  files: [{
    path: $file,
    conversations: [{
      contributor: { type: "ai", model_id: $model },
      ranges: $ranges,
      related: [{ type: "session", url: ("claude-code://session/" + $session) }]
    }]
  }]
}' >> "$ROOT/.agent-trace/traces.jsonl"
exit 0

なお id / timestampuuidgen / date を使うため、
厳密な再現生成(同じ入力→同じ出力)はしません。
冪等性が必要なら payload の tool_use_id などを使う手法があります(未検証)。

References


国内企業 AI活用実態調査2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

国内企業 AI活用実態調査2026

無料でダウンロードする

この記事をシェアする

関連記事