# AI駆動開発（AIDD）のチーム運用｜証跡からSkill・Ruleを継続改善する

> AI駆動開発（AIDD）をチームで継続改善する実践方法を解説します。Claude Code・CodexのHooksによる証跡収集、Skill・Ruleの改訂、Git feature flowで開発ブランチを汚さない同期、週次レポートと数週間後の効果確認まで、実装と運用を紹介します。

Canonical URL: https://labs.eastbraver.com/blog/aidd-with-continuous-growth
Published: 2026-09-15T13:00:00+09:00
Category: ai-development
Tags: ai-agent, agentic-infra, enterprise-ai, evaluation, software-engineering

私がAI駆動開発を取り入れている現場では、**Git feature flowを採用しています。**
この運用は、AIDDを取り入れる以前から続けています。
短期で完了する開発と長期にわたる開発が混在し、その間に発生する不具合修正も待たせず継続的にリリースするためです。

普段、メンバーが作業するのは主に次のブランチです。

| ブランチ | 用途 |
|---|---|
| `feature/*` | 機能開発。短期・長期の作業を含む |
| `fix/*` | 通常の不具合修正 |
| `hotfix/*` | 緊急の修正 |

作業をブランチごとに切り分け、完成した変更をレビューして本流（`main`）へマージします。
長期の開発を進めている間も、小さな機能開発は完了次第リリースできます。
ただ、ブランチを分けても統合の負担は残ります。
長期にわたる作業ブランチへ定期的に本流を取り込み、変更も小さく分けておいた方が後から楽です。

この現場にAIエージェントを取り入れてから、コード以外にもチームで共有したいものが増えてきました。
Skill、Rule、`CLAUDE.md`、`AGENTS.md`といったAI支援開発の基盤（指示や手順書）です。

「その仕様は違います」「前に伝えた条件が抜けています」「必要な検証が終わっていません」。
その場でAIの誤りを訂正しても、別のブランチや別のメンバーの環境で同じ訂正を繰り返していたら、せっかく得た経験や知見をチーム全体の学びとして積み上げられません。

そこで私は、**日々の証跡をHooksで自動収集し、専用ブランチで基盤を改訂し、各メンバーの作業場所へファイルだけを同期する**という運用を組んでいます。

先行記事の[「Agent Skillsの作り方 履歴から設計して評価する」](/blog/agent-skills-design-evaluation)では、実行履歴からSkillの候補を見つけ、現在の実装と照合して裏付けを取り、実用的な手順と評価ケースへ落とし込む流れを整理しました。
また[「AIに仕事を任せても学習の循環まで手放してはいけない」](/blog/ai-learning-loop-retention)では、ログを蓄積した後も、何を選び、どう検証し、採用するかは人間が判断し続ける必要があると書きました。
この記事では、そうした考え方を日々の開発現場で破綻なく回すために、運用と実装をどうつないでいるのかを書きます。

> 確認基準日時：2026年9月15日 03:00（日本時間）。以下のコードは、構成を説明するために用意したmacOS／Linux向けの実装例です。実際に使用している案件のソースをそのまま公開するものではありません。Bash・Git・jqと標準的なshellコマンドを使います。

この記事で扱うのは、**証跡の自動収集と専用ブランチへの記録**と、**開発基盤の改訂と作業環境への同期**です。
**Bunのアーキテクチャを参考にした横断的ガードや、`CLAUDE.md`・`AGENTS.md`の指示とシェルスクリプトによる制御の組み合わせは、別の記事で詳しく書きます。**
危険操作を防ぐ既存の仕組みは残したまま、その手前にこの記事の観測用コードを追加します。

## 開発と証跡と基盤改訂を別々のブランチで管理する

| ブランチ | 管理するもの | 更新する方法 |
|---|---|---|
| `main`と通常の作業ブランチ | コード、テスト、業務仕様、Hook設定、運用shell | 通常の開発・レビュー |
| `aidd/evidence/[user-name]` | 利用者ごとの構造化証跡 | Hookから起動されるshellによる自動commit・push |
| `aidd/main` | 最新のmainと、Skill・Rule・指示ファイル・改訂記録 | mainのmergeと、専用の改訂作業 |

### AIDDを支える三つのGit領域

Hook設定と証跡送信shellはmainとその派生ブランチでGit追跡されます。利用者別の証跡は専用ブランチに記録し、改訂済みの基盤ファイルだけをaidd/mainから開発作業場所のignore対象パスへ同期します。

![AIDDを支える三つのGit領域](/images/blog/aidd-with-continuous-growth/git-responsibility-map.svg "1200x675")

- mainとそこから派生するfeature・fix・hotfixブランチでは、コード、テスト、業務仕様、HookのJSON設定、証跡送信shell、同期shellをGit追跡します。
- 各作業ブランチのHookはmain系で共有されたevidence.shを呼び出し、利用者専用のworktreeを経由して構造化証跡のJSONだけをaidd/evidence/<user-name>へcommit・pushします。
- mainの更新はaidd/mainへmergeし、そこでSkill・Rule・指示ファイルと改訂記録を正典として追跡します。
- sync.shは検証済みの基盤ファイルだけを開発作業場所のignore対象パスへ配置し、開発ブランチのHEADやindexを進めません。

*設定とshellをmain系で共有し 証跡と改訂正典だけを専用ブランチへ分離する*

- Source: この記事で示す運用構成
- Method: 追跡対象とデータ経路の静的整理
- Environment: mainとその派生ブランチ aidd/evidence/<user-name> aidd/main
`git worktree`は、同じGitリポジトリから複数の作業ツリーを同時にチェックアウトできる機能です。
証跡用worktreeは開発用worktreeとは別の作業ツリーとステージングエリア（インデックス、次のコミット対象）を持つため、証跡の自動コミットに開発中の差分が混ざる事故を防げます（詳細は[Git公式のworktreeドキュメント](https://git-scm.com/docs/git-worktree)を参照してください）。

**証跡用worktreeを用意する一番の目的は、開発ブランチを汚さずに、バックグラウンドで専用ブランチへ証跡を記録し続けることです。**
開発メンバーが毎回手作業で用意する必要はありません。
Hookから呼び出されるシェルスクリプトが必要に応じて作成し、その後も同じworktreeを再利用します。

開発基盤の正典（改訂・配布の原本）は、`aidd/main`です。
証跡を記録するブランチとは役割が違うため、ここは明確に分けています。

### ブランチも作業場所も利用者ごとに分ける

この記事での証跡の記録先は、**`aidd/evidence/[user-name]`**です。
太郎と花子の証跡を同じブランチに混在させることはありません。

| 対象 | 太郎の例 | 花子の例 |
|---|---|---|
| 証跡ブランチ | `aidd/evidence/taro` | `aidd/evidence/hanako` |
| ローカルの専用worktree | `…/worktrees/aidd/evidence/taro/` | `…/worktrees/aidd/evidence/hanako/` |
| 送信待ち・ロック | `…/state/taro/` | `…/state/hanako/` |

この実装例では、対象リポジトリを識別するハッシュ値もパスに含めています。
利用者が同じなら、Claude CodeやCodex、複数のターミナルセッション、複数の開発worktreeから実行しても、**専用の証跡worktreeを1つだけ再利用**します。
`feature/*`ブランチが増えるたびに証跡ブランチまで増やす構成にはしません。

Gitでは、同じブランチを複数のworktreeで同時にチェックアウトできません。
同じ利用者からの並行アクセスは専用worktreeへ集約し、ファイルロックで直列に処理します。
利用者が異なれば、ブランチ、ローカル作業場所、送信待ちキュー、排他ロックも別々です。

同じ利用者が別端末や別cloneから送信すると、ローカルの作業場所は異なっても、送信先のリモートブランチは同じになります。
この場合に起こり得るpush競合への対処は後述します。
この実装例ではリモートとの履歴が分岐したら手元の証跡を保持して処理を止め、force pushで相手の履歴を上書きしません。

## 設定とshellはmainで追跡し基盤はaidd/mainで改訂する

AI関連のファイルは、役割に応じて追跡先を分けます。

**Hookを設定するJSON、証跡を送信するシェルスクリプト、基盤を同期するシェルスクリプトは、mainブランチで追跡（コミット）してチーム全体で共有します。**
メンバーがリポジトリをcloneした時点で、誰でも同じ入口と運用手順を使えるようにするためです。

| 対象 | main・feature・fix・hotfix | aidd/main |
|---|---|---|
| コード・テスト・業務仕様 | 追跡 | mainから取り込み、追跡 |
| `.gitignore` | 追跡 | 共通の管理ブロックを追跡 |
| `.claude/settings.json`、`.codex/hooks.json` | **追跡** | mainから取り込み、追跡 |
| Hook・証跡送信・同期のshellと補助処理 | **追跡** | mainから取り込み、追跡 |
| `.claude/skills/`、`.claude/rules/` | ignoreし、同期して使う | **追跡** |
| `CLAUDE.md`、`AGENTS.md` | ignoreし、同期して使う | **追跡** |
| `.agents/skills`の参照リンク | ignoreし、同期して使う | この実装例ではリンクも追跡 |
| 評価ケース、採否の記録、週次改訂レポート | 通常の基盤同期では配らない | 追跡 |
| 個人の認証情報、送信待ち、一時ファイル | 追跡しない | 追跡しない |

### 開発ブランチ上の配置を分ける

`CLAUDE.md`や`AGENTS.md`も同様に、改訂環境で検証済みの完成ファイルをGitに登録します。共通の原稿からスクリプト等で生成する構成であっても、各作業ブランチへ配布する成果物は`aidd/main`側で追跡します。

開発ブランチ側における配置構成は、次のようになります（`scripts/aidd/`などのディレクトリ名は説明用です）。

| 開発ブランチ上のパス | 追跡状態 | 役割 |
|---|---|---|
| `src/`、`tests/` | mainと派生ブランチで追跡 | アプリケーションと検証 |
| `scripts/aidd/run.sh` | mainと派生ブランチで追跡 | Hook用shellを呼べる環境でCLIを起動 |
| `scripts/aidd/evidence.sh` | mainと派生ブランチで追跡 | 証跡作成・自動commit・push |
| `scripts/aidd/sync.sh`、`sync-files.sh` | mainと派生ブランチで追跡 | 基盤同期と旧配布物の片付け |
| `.claude/settings.json`、`.codex/hooks.json` | mainと派生ブランチで追跡 | Hook設定 |
| `.claude/skills/`、`.claude/rules/` | 非追跡 | 同期するSkillとRuleの実体 |
| `.agents/skills` | 非追跡 | `.claude/skills`を参照するリンク |
| `CLAUDE.md`、`AGENTS.md` | 非追跡 | 同期する指示 |
| `.gitignore` | mainと派生ブランチで追跡 | 追跡対象と同期対象の境界 |

### Hook設定と運用shellを基盤同期に混ぜない

> [!IMPORTANT] Hookの設定やガード本体のスクリプトは、Skillの同期処理には混ぜません。
> 改訂が必要になったら、通常のプルリクエストとコードレビューを経て`main`を更新し、それを`aidd/main`へ取り込みます。
> 開発基盤の同期処理から、その基盤を守る安全設定まで書き換えられないようにするためです。

## .gitignoreは共通にし移行の順序を決めておく

### 初回移行で変える追跡対象と変えない追跡対象

Skill・Rule・指示ファイルの正典だけをaidd/mainへ移します。Hook設定と証跡送信・同期のshellは、移行後もmainとその派生ブランチでGit追跡します。

| 観点 | 移行前 | 移行後 |
| --- | --- | --- |
| HookのJSON設定 | mainでGit追跡 | mainとその派生ブランチでGit追跡を継続 |
| 証跡送信・同期のshell | mainでGit追跡 | mainとその派生ブランチでGit追跡を継続 |
| Skill・Rule・指示ファイル | mainでGit追跡 | main側ではignoreし aidd/mainだけでGit追跡 |
| 開発作業場所の基盤ファイル | ブランチの追跡状態に従う | aidd/mainからignore対象パスへ同期して利用 |

- Source: この記事の初回移行手順
- Method: 移行前後の追跡状態を同じ対象ごとに比較
- Environment: mainとその派生ブランチ aidd/main
### 同じ除外設定でも追跡状態は分けられる

`main`の更新を`aidd/main`へ継続的にマージするため、`aidd/main`側だけで`.gitignore`の記述を削除するような二重管理は採りません。

**両方のブランチで同じ除外設定を使い、`aidd/main`側だけで対象ファイルを明示的にGit登録します。**
Gitのignore設定は、すでにインデックスへ登録されているファイルには適用されません（この仕様は[Git公式のgitignoreドキュメント](https://git-scm.com/docs/gitignore)にも明記されています）。

```gitignore
# AIDDから同期して使うファイル
/.claude/skills/
/.claude/rules/
/.agents/skills
/CLAUDE.md
/AGENTS.md

# 改訂側で管理する記録
/.aidd/ground/

# 証跡ブランチだけで登録するデータ
/.aidd/evidence/
```

ここで注意したいのは、`.claude/`や`.aidd/`といったディレクトリ全体を除外しないことです。
チームで共有すべき設定ファイルまで無視されてしまいます。
`GEMINI.md`やディレクトリ階層ごとの指示ファイルを配布するなら、そのパスを除外設定と同期対象へ明示的に追加します。

`aidd/main`でファイルを初めて管理対象に加える際は、対象パスを個別に指定して`git add -f`（強制追加）を実行します（[Git公式のgit-addドキュメント](https://git-scm.com/docs/git-add)を参照）。

```bash
# aidd/mainの改訂作業場所で実行する例

# 追跡済みの変更・削除
git add -u -- .claude/skills .claude/rules .agents/skills CLAUDE.md AGENTS.md

# 新規ファイルの登録
git add -f -- .claude/skills/example/SKILL.md
```

新しく追加した参照資料や評価ケースも同じです。
改訂作業者のローカル端末にしかなく、Gitで追跡されていなければ各メンバーの環境には届きません。
`git add -f .`で作業ツリーを一括登録せず、意図したファイルだけが追跡対象に入っているかを`git status`などで確認します。

### 初回は「mainから追跡を外しその後に改訂側で登録する」

すでに`main`でSkillや指示ファイルをGit管理しているリポジトリは、`.gitignore`にパスを追加するだけでは移行できません。
`git rm --cached`を実行するとローカル作業ツリーのファイルは残りますが、コミット上は削除扱いになります。
そのコミットを他の開発ブランチへ取り込んだときに、作業ツリーのファイルが消える場合もあります（[Git公式のgit-rmドキュメント](https://git-scm.com/docs/git-rm)を参照）。
そのため、移行する順序を先に決めておきます。

| タイミング | 行うこと |
|---|---|
| 移行前 | Skill・Rule・指示ファイルを退避し、未commit・非追跡の変更も保全する |
| mainの移行 | 設定とshellを残し、共通ignoreの追加と基盤ファイルの追跡解除を行う |
| aidd/mainの新設 | 移行後のmainから作成し、退避した基盤を改訂側だけで登録する |
| 各作業ブランチの移行 | 移行済みmainを取り込み、基盤が非追跡になったことを確認して同期する |

すでに`aidd/main`がある場合は、最初にマージ前の正典ファイルを保全します。
`main`側で削除コミットを作ると、改訂側で未変更のファイルにも削除が取り込まれる点に注意が必要です。
改訂側で編集済みなら、削除と変更がコンフリクト（競合）します。
`main`のコード更新を取り込みつつ、基盤の正典ファイルには明示的な保持が必要です（[Git公式のgit-mergeドキュメント](https://git-scm.com/docs/git-merge)にあるブランチ統合の手順に沿って対処します）。

長期間残っている`feature/*`だけでなく、移行前に作った`fix/*`や`hotfix/*`にも古い追跡状態が残り得ます。
同期スクリプトで強引にもみ消さず、先に各ブランチを移行しておきます。
後から古いブランチを`main`へマージするときに基盤ファイルを再び追跡しないよう、CI検査やフックも用意しておくと安心です。

### mainの更新は基盤の改訂環境へ継続的に取り込む

移行後は共通の`.gitignore`を維持したまま、`main`の最新コミットを定期的に`aidd/main`へマージします。
アプリケーションのコードや仕様、テストコードだけでなく、Hook設定や運用シェルスクリプトの更新も取り込みます。

### mainの更新を基盤改訂へ取り込む流れ

mainの更新はaidd/mainへmergeしただけでは基盤へ反映されません。コードと正典を照合して必要な改訂と評価を終えた後に、検証済みのaidd/mainを共有します。

- mainのコード・仕様・テスト・Hook設定・運用shellの更新をaidd/mainへmergeします。
- コードと正典と共通ignoreを照合し、参照先・検証コマンド・手順への影響を確認します。
- 必要な基盤改訂と評価を行い、検証済みのaidd/mainを共有します。

*mainの変更を確認と評価を通して検証済みの基盤へ反映する*

`main`を取り込んでも、SkillやRuleの記述までは自動で最新仕様に追従しません。
APIのシグネチャやディレクトリ構造、業務仕様が変わったら、それらを参照する手順書（Skill）も人とAIで見直します。

開発メンバーが迷わず最新版を同期できるよう、リモートで共有する`aidd/main`には検証と動作確認を終えた基盤だけを置きます。
検討中や実験中の下書きは、全員の同期対象にしません。
緊急の修正は検証が終わり次第プッシュして共有できるため、週次の改訂日まで待つ必要はありません。

## Skillの実体は.claude/skillsにまとめる

私は以前、`.claude/skills`をシンボリックリンクにしていたとき、当時のClaude CodeがSkillを正常に認識しないトラブルに遭遇しました...。
どのバージョンまでの挙動だったのか、正確な境界までは追い切れていません。
この経験から、現在は**`.claude/skills`に実体ディレクトリを配置する構成**に統一しています。

CodexやGemini CLIには、`.agents/skills`からこの実体を参照させます。
ここでいうリンクとは、ファイルシステムのシンボリックリンクです。
ツールのバージョンによってシンボリックリンクの解決仕様が異なる場合もあるため、導入時に実際の読み込み挙動を確認します。

### Skillの実体と参照リンク

Skillの実体はプロジェクト内の.claude/skillsに置きます。CodexやGemini CLIは、同じ場所を指す.agents/skillsのシンボリックリンクから参照します。

- 同期shellがaidd/mainで検証済みのSkill実体を開発作業場所の.claude/skillsへ配置します。
- .agents/skillsは同一プロジェクト内の.claude/skillsを参照し、別の改訂作業場所へ直接つなぎません。

*一つのSkill実体をプロジェクト内の参照リンクから共有する*

リンクの参照先は、同じプロジェクト内の`.claude/skills`です。
`aidd/main`で改訂作業をしている別ディレクトリへ直接リンクを張らず、同期スクリプトが手元に配置したファイルを参照させます。

Skillファイルを共有しても、すべてのCLIがプロジェクト指示（Rule）を同じ方法で解釈するとは限りません。
ツールごとの指示ファイル（`CLAUDE.md`や`AGENTS.md`など）を`aidd/main`で一元管理し、それぞれに合った形式で指示・適用範囲・参照関係を整えます。

## 同期はignore対象の場所へファイルを配置するだけ

| Gitへの作用 | mergeまたはcherry-pick | この記事の`sync.sh` |
|---|---|---|
| 開発ブランチのHEAD | コミットを取り込んで進める | 進めない |
| ステージングエリア | 取り込む変更に応じて更新される | `--staged`を付けず更新しない |
| 作業ツリー | コミットの内容を反映する | 指定したignore対象パスだけを配置する |
| 同期元 | 選んだコミットやブランチ | 検証済みの`origin/aidd/main` |

### 開発ブランチへcommitを取り込む操作ではない

開発メンバーが実行するのは、`main`で共有されているシンプルな同期用シェルスクリプトです。

```bash
./scripts/aidd/sync.sh
```

スクリプト内部で行っているのは、実質的に次の3ステップだけです。

### 基盤ファイルだけを同期する三段階

同期はorigin/aidd/mainの最新情報を取得し、指定された基盤ファイルだけを取り出します。開発ブランチの履歴やstageを更新せず、現在の作業場所にあるignore対象パスへ配置します。

- origin/aidd/mainの最新情報をfetchします。
- Skill・Rule・指示ファイルなど同期対象として指定したパスだけを取り出します。
- 開発ブランチのHEADとindexを変えず、現在の作業場所のignore対象パスへ配置します。

*fetchした正典から指定ファイルだけを作業場所へ配置する*

**これは`git merge`でも`git cherry-pick`でもありません。開発ブランチのHEADやインデックスは変わらず、履歴にコミットは1つも加わりません。同期されたファイルを開発者がコミットする必要もありません。**
コミットハッシュを選ぶ手順を挟まず、検証済みの最新基盤を作業ツリーへ配置します。

Gitから指定したリビジョンのファイルを取り出す操作と、開発ブランチの履歴にコミットを加える操作は別です。
デバッグや追跡のために同期元リビジョンをログへ残しても、ブランチの履歴管理とは混同しないようにします。

### ファイルだけを配置するGit操作

新規・更新ファイルを取り出すコアロジックは、次のとおりです。
実行前には、対象パスが非追跡状態であり、共通設定どおり`.gitignore`で除外されていることをスクリプト内で確認します。

```bash
#!/usr/bin/env bash
set -euo pipefail

cd "$(git rev-parse --show-toplevel)"

git fetch --no-tags origin \
  refs/heads/aidd/main:refs/remotes/origin/aidd/main

# 作業ファイルだけを配置する。--stagedは付けない。
git restore --source=origin/aidd/main --worktree -- \
  .claude/skills \
  .claude/rules \
  .agents/skills \
  CLAUDE.md \
  AGENTS.md
```

これは、リモートの最新`origin/aidd/main`から必要なファイルだけを作業ツリーへ配置する処理です。
ローカルで作業中のブランチを`aidd/main`へ切り替えたり、HEADを動かしたりする必要はありません（詳細は[git fetch](https://git-scm.com/docs/git-fetch)および[git restore](https://git-scm.com/docs/git-restore)のドキュメントを参照してください）。

ここで重要なのは、**`git checkout aidd/main -- <path>`によるファイル指定チェックアウトでは、作業ツリーと一緒にインデックス（ステージングエリア）も更新される**という点です。
`.gitignore`で除外しているパスでもインデックスに載ります。
作業ツリーにファイルだけを配置し、開発者のステージングエリアを汚さないため、この実装例では`git restore --worktree`を使います（[git checkoutの仕様](https://git-scm.com/docs/git-checkout)との決定的な違いがここにあります）。

新しい版で削除・廃止したSkillを、開発者の環境に残さない処理も必要です。
付属の同期スクリプトでは、前回配布したファイル一覧とハッシュを照合し、配布対象から外れた管理ファイルだけを削除します。
`.claude/`ディレクトリごと削除すると、`main`で追跡しているHook設定ファイルまで巻き込むため、対象を厳密に限定します。

### ブランチ切り替え後も同じコマンドで揃える

開発基盤のファイルは開発ブランチの追跡対象外です。
新しいclone環境や別のworktreeでは、`./scripts/aidd/sync.sh`の実行が作業開始前の手順です。
作業中にブランチを切り替えた後も、この1コマンドで必要な基盤を最新状態に揃えられます。

**`.gitignore`で除外された未追跡ファイルは、通常のブランチ切り替え（`git switch`や`git checkout`）では消えず、作業ツリーに残るのが基本です。**
ただし、`aidd/main`のように該当ファイルを追跡しているブランチから、追跡していない開発ブランチへ切り替えて戻った場合などは、Gitの処理によってファイルが作業ツリーから削除されることがあります。
検証環境でも、この挙動の違いを確認しています。

| 作業場所の状態 | 基盤ファイルの状態 | 対応 |
|---|---|---|
| 新しいcloneまたは新しいworktree | まだ配置されていない | `./scripts/aidd/sync.sh`を実行 |
| 非追跡の開発ブランチ間を通常切り替え | 通常は作業ツリーに残る | 更新時または不明なときに同じコマンドで再同期 |
| `aidd/main`から開発ブランチへ戻る | Gitの切り替えで削除される場合がある | 同じコマンドで再同期 |
| 長期間放置した開発ブランチ | 基盤だけ最新でもコードは古い | コード側は別途mergeまたはrebase |

現場では「ファイルが残っているはずだ」と思い込まず、1コマンドで確実に再同期できるようにしておきます。
長期間放置した`feature`ブランチへ最新基盤を同期しても、アプリケーションのコードまでは新しくなりません。
コード側には、別途マージやリベースが必要です。

### 同期後の指示は新しいセッションで読む

もう一つ注意したいのは、ファイルの更新と、起動中のAIエージェントへの反映は別だという点です。
`CLAUDE.md`や`AGENTS.md`のような常時参照する指示ファイルを更新しても、起動中のセッションには古い指示が残っていることがあります。
確実に反映させるには、CLIを再起動するか、新しいセッションへ切り替えます。

> [!NOTE] 基盤ファイルの同期完了と、起動中セッションへの指示反映は別々に確認します。指示ファイルを更新した後は、CLIを再起動するか新しいセッションへ切り替えます。

## どのHookから何を観測するか

### Hookイベントを共通の構造化証跡へ変える流れ

Claude CodeとCodexのイベント名やペイロードの違いは、main系で共有するshellが吸収します。各イベントはセッション中に逐次保存され、共通形式のJSONとして利用者別の証跡ブランチへ送られます。

- セッション開始・入力・ツール実行前後・停止などのHookが製品固有のJSONをshellへ渡します。
- shellは製品名とイベント特性を保ったまま、承認された共通項目だけを構造化JSONへ正規化します。
- JSONをローカルへ先に保全し、利用者別の証跡worktreeから専用ブランチへ順次送信します。

*製品ごとのHook入力を共通形式へ正規化して順次送信する*

### 中核にする発火点

| Hook | 発火する場面 | 狙い |
|---|---|---|
| `SessionStart` | セッションの開始・再開など | 実行環境と作業場所を識別し、未送信の証跡も再確認する |
| `UserPromptSubmit` | 入力を処理する前 | 是正指示・誤り訂正・要件追加を調べる入口にする |
| `PreToolUse` | ツール実行前 | 操作の試行を記録する。拒否判定は別の既存ガードが担当する |
| `PostToolUse` | ツール実行後 | 操作と結果、変更、検証を関連付ける |
| `Stop` | 応答を終える場面 | 完了報告と、実際の検証結果を照合する |

これらはClaude CodeやCodexに備わっているHooks機能です。
似た名前のイベントでも、ツールによってペイロードのJSON形式や終了ステータスの扱いには違いがあります。
シェル側では、その違いを吸収した共通の構造化証跡フォーマットへ正規化しています。

補助の発火点の使い分けは、次のとおりです。

| Hook | 補う情報 |
|---|---|
| `PermissionRequest` | 承認要求が発生した事実。拒否の確定とは分ける |
| `SubagentStart`／`SubagentStop` | 親子エージェントの作業の対応 |
| `PreCompact`／`PostCompact` | コンテキスト圧縮の前後という境界 |
| `SessionEnd` | 正常な終了と未完了・欠測の切り分け |

セッション終了時のフックだけに頼らず、途中で発生したイベントもバックグラウンドで順次送信します。
エージェントのクラッシュやターミナルの強制終了が起きても、そこまでの証跡を残せます。

### 製品ごとの違いを残す

Claude Codeでは、`InstructionsLoaded`で指示ファイルの読み込み、`UserPromptExpansion`でユーザーが入力したSkillプロンプトの展開、Tool実行イベントの`Skill`でAIからの自律的な呼び出し、`PostToolUseFailure`でツール実行の失敗を検知できます。
各ツールの公式リファレンスを確認し、イベントの違いに合わせて使い分ける必要があります。

Codexでは、シェルコマンドの実行は`Bash`、パッチによるコード編集は`apply_patch`というツール名で処理されます。
`PostToolUse`フックはシェルコマンドが非ゼロ（エラー）で終了しても発火するため、イベントが発生しただけでコマンド成功とは判断できません。
ユーザーによる処理中断（Ctrl+Cなど）を検知するには、`Interrupt`イベントも捕捉します。

| 製品 | 固有の観測点または名前 | 記録時の注意 |
|---|---|---|
| Claude Code | `InstructionsLoaded`、`UserPromptExpansion`、`Skill`、`PostToolUseFailure` | 指示の読み込み、Skillの展開・呼び出し、ツール失敗を区別する |
| Codex | `Bash`、`apply_patch`、`PostToolUse`、`Interrupt` | `PostToolUse`の発火だけで成功とせず、非ゼロ終了と中断を区別する |

存在しないイベントを推測でフック設定に加えたり、フックから取得できない情報をAIエージェントの自己申告（プロンプトでの返答）で補ったりしてはいけません。
指示ファイルを読み込んだ記録だけでは、AIがその指示に従ったことまで確認できません。

## 集めるデータを改訂の判断につなげる

| データ | 内容 | 用途 |
|---|---|---|
| 識別・関連付け | 利用者、案件、セッション、親子agent、ツール呼び出し、イベントID | 入力・操作・訂正を一つの事例として辿る |
| 時刻・収集範囲 | 発生時刻、対象期間、CLI版、取得件数、未取得範囲 | 期間指定の全量分析と欠測確認 |
| 要求と訂正 | 既存要求、期待、実際の行動、訂正内容、発信元 | 誤り訂正と新規要件を区別する |
| 基盤とコードの状態 | 対象コード、利用したSkill・Rule、同期内容、読込状態 | 古い前提、未同期、版の不一致を見分ける |
| 操作・ガード判定 | 操作分類、対象、拒否理由、実行結果 | 手順抜け、危険操作、過剰な拒否を調べる |
| 検証 | 対象状態、検証フェーズ、期待結果、実結果 | 完了報告の裏付けと改訂前後の比較 |
| 成功・適用機会 | 該当手順が必要だった作業と成功事例 | 再発率を計算する分母 |
| 改訂・採否・効果 | 原因候補、判断、理由、変更、後日の結果 | 改善が有効だったかを追う |

### 事実と分析と判断を分離する

> [!IMPORTANT] フックが捉えた客観的事実、テストランナーの実行結果、AIによる分析、人間による採否の判断は分けて記録します。
> 取得できなかった項目を推測で埋めず、未知（未確認）として扱います。

訂正の意味は、ユーザーの入力だけでは分かりません。
直前のAIの応答や、それまでに合意していた条件と照合する必要があります。
タイムスタンプやプロンプトの文字数だけを見ても、根本原因は特定できません。
ただし、会話ログやソースコードの全文、コマンドの入出力をそのままGitブランチへコミットして共有するのは避けます。

共有するのは、機密情報を除去した承認済みの構造化証跡とサマリーレポートだけです。
認証情報や顧客データを送信ログへ含めず、AIが作る要約にも機密性の高い業務情報が混ざり得る前提が欠かせません。

検証結果を評価するときは、失敗することが正常な場面との区別が必要です。
テスト駆動開発（TDD）で意図的にテストを失敗させるRed工程や、コードを変化させてテストの検知力を測るミューテーションテストまで不具合に数えると、評価を誤ります。

## Hookは短い入口にしGit操作は呼び出すshellにまとめる

HookのJSONに、`git worktree`、JSON作成、commit、pushまで並べる必要はありません。
JSONに置くのは、**どの発火点で、どのshellを呼ぶか**だけです。

### Hookを短い入口に限定する責務分離

HookのJSONは発火点と呼び出すshellだけを定義します。利用者の解決、専用worktree、JSON作成、commit、pushはmain系でGit追跡するevidence.shへ集約します。

- Claude CodeまたはCodexのHook設定が、入力元を表す引数とともにevidence.shを呼び出します。
- evidence.shが利用者を解決し、専用worktreeでJSONの作成・commit・pushを順に処理します。

*設定は呼び出しだけを持ち Git操作は共有shellへ集約する*

### 短いコマンドを作業ディレクトリに依存させない

コマンドを短くしても、単純な相対パスではサブディレクトリで動いたHookからshellを見つけられない場合があります。
Claude CodeのHookは現在のディレクトリ、CodexのHookはセッションの作業ディレクトリから実行されるためです。

この実装例では、**mainで共有する小さな起動用shellが、`scripts/aidd/`をコマンドの探索先であるPATHへ追加してからCLIを起動**します。
これなら、JSONにGitルートを求める長い式を毎回書かずに済みます。

`scripts/aidd/run.sh`の例です。

```bash
#!/usr/bin/env bash
# CLIの起動環境にだけ、mainで共有したHook用shellの場所を追加します。
# 例: bash scripts/aidd/run.sh claude
set -euo pipefail
case "${1:-}" in
  claude|codex) ;;
  *) echo '使い方: run.sh claude|codex [CLIの引数...]' >&2; exit 64 ;;
esac
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
export PATH="$script_dir:$PATH"
exec "$@"
```

CLIを起動する入口は、例えば次の形です。
PATHの変更はプロジェクト全体へ恒久的に加えず、このCLIと子プロセスの環境だけに限定します。

```bash
bash scripts/aidd/run.sh claude
bash scripts/aidd/run.sh codex
```

Hookから名前で呼ぶ`evidence.sh`は、main側で実行権限も含めて登録します。
初回に`chmod +x scripts/aidd/evidence.sh`を実行し、その属性もGitへ保存する形です。

既存の起動処理があるなら、そこへ同じ環境設定を組み込めます。
IDEなど別の入口からCLIを起動するときも、同じPATHが引き継がれることを確認します。
**短いコマンドだけをコピーして、起動環境の設定を省いてはいけません。**
起動用shellの役割は、Hookの呼び出し先を短くするための環境設定だけです。

### Claude CodeのJSON設定

`.claude/settings.json`へ追加する、中核イベントの観測設定です。

```json
{
  "hooks": {
    "SessionStart": [{"hooks": [
      {"type": "command", "command": "evidence.sh claude", "timeout": 5}
    ]}],
    "UserPromptSubmit": [{"hooks": [
      {"type": "command", "command": "evidence.sh claude", "timeout": 5}
    ]}],
    "PreToolUse": [{"hooks": [
      {"type": "command", "command": "evidence.sh claude", "timeout": 5}
    ]}],
    "PostToolUse": [{"hooks": [
      {"type": "command", "command": "evidence.sh claude", "timeout": 5}
    ]}],
    "Stop": [{"hooks": [
      {"type": "command", "command": "evidence.sh claude", "timeout": 5}
    ]}]
  }
}
```

### CodexのJSON設定

`.codex/hooks.json`にも、同じ送信shellを呼ぶ設定を置きます。
引数で入力元を区別します。

```json
{
  "hooks": {
    "SessionStart": [{"hooks": [
      {"type": "command", "command": "evidence.sh codex", "timeout": 5}
    ]}],
    "UserPromptSubmit": [{"hooks": [
      {"type": "command", "command": "evidence.sh codex", "timeout": 5}
    ]}],
    "PreToolUse": [{"hooks": [
      {"type": "command", "command": "evidence.sh codex", "timeout": 5}
    ]}],
    "PostToolUse": [{"hooks": [
      {"type": "command", "command": "evidence.sh codex", "timeout": 5}
    ]}],
    "Stop": [{"hooks": [
      {"type": "command", "command": "evidence.sh codex", "timeout": 5}
    ]}]
  }
}
```

### 観測とガードを順序依存させない

この2つのJSONは、**証跡収集を既存環境へ安全に追加するための設定例**です。
既存の設定ファイルや、すでに運用している`PreToolUse`の危険操作ガードを上書きするものではありません。
観測用フックはツールの実行可否（許可・拒否）を判断せず、プロンプトへ指示を加えるような標準出力も出しません。

| 処理 | 担当すること | 担当しないこと |
|---|---|---|
| 観測用Hook | 発生したイベントを構造化証跡へ残す | ツールの許可・拒否と危険操作の防止 |
| 既存の危険操作ガード | 操作を評価して必要な拒否を行う | 観測用Hookの正常終了を前提にすること |
| `evidence.sh` | 入力を保全して証跡用ブランチへ記録する | 拒否理由を終了コードで代行すること |

同じイベントに複数のHookを登録しても、設定ファイルに書いた順番で実行される保証はありません。
観測用フックの正常終了をガードの前提にせず、ガードの拒否理由を観測側の終了コードで表すような結合も避けます。
ガードと観測は、互いの実行順に依存しない構成です。

この実装例の`evidence.sh`は、Hookから渡された入力を手元の一時ファイルへ先に退避する作りです。
その後に自分自身をバックグラウンドで起動し、Git操作とpushを進めます。
そのためフック設定に`async: true`を付けなくても、エージェントとの対話を待たせません。
追加の発火点を含む観測設定は、付属の設定例にまとめています。

プロジェクトの信頼設定や、Hookを実行する権限の承認は別途必要です。
リポジトリをcloneしただけでは、すべてのHookが自動で有効になるとは限りません。
各ツールの公式ドキュメントに沿って、初回セットアップ時にフックが発火することを確認してください。

## worktree作成からJSON出力とcommit・pushまで担うshell

### 証跡を開発差分から分離して送信する流れ

Hook入力は現在の開発作業場所でローカルキューへ保全した後、利用者専用worktreeへ渡します。送信shellは証跡JSONだけをcommitし、利用者別のaidd/evidenceブランチへpushします。

- Hook発火時の入力から承認された項目を抽出し、利用者別のpendingへ先に保存します。
- ロックを取得して利用者専用worktreeを作成または再利用し、リモート履歴との関係を確認します。
- .aidd/evidence配下のJSONだけをstage・commitし、対応する利用者別ブランチへpushします。
- push成功後に送信済みのpendingだけを削除し、失敗時は再送できる状態で保持します。

*開発中の差分を触らず 証跡JSONだけを専用worktreeから送信する*

### Gitの利用者情報から証跡ブランチを決める

私の運用では、利用者にブランチ名を入力させていません。
名前を生成するのは、Gitの利用者情報を読むshellです。

この実装例でも、`user.name`から利用可能な文字列を作り、同名の利用者を区別する短いハッシュ値を付けます。
これは設定上の識別名であり、Gitホスティングの認証アカウントを証明するものではありません（[Git公式のconfigドキュメント](https://git-scm.com/docs/git-config)にある設定と認証の分離を意識します）。

例えば`taro-<短い識別値>`を利用者識別子とすると、証跡ブランチ名は`aidd/evidence/taro-<短い識別値>`です。
コマンドへ利用者名を毎回渡す必要はありません。
Hook発火時のGit利用者情報を使い、バックグラウンドでの送信途中に別の利用者情報へ変わらないようにしています。

ローカル環境における配置構成は、次のように案件（リポジトリ）と利用者の両方で直交して分離します。

| ローカルパスまたはGitブランチ | 分離の単位 | 内容 |
|---|---|---|
| `~/.local/state/aidd/<案件ID>/` | リポジトリ | 案件ごとの状態とworktreeの親 |
| `state/<user-name>/pending/` | 利用者 | 未送信のJSON |
| `state/<user-name>/writer.lock` | 利用者 | 証跡送信の直列化 |
| `state/<user-name>/delivery.log` | 利用者 | 送信結果と失敗 |
| `worktrees/aidd/evidence/<user-name>/` | 利用者 | 証跡専用の作業ツリー |
| `worktrees/aidd/evidence/<user-name>/.aidd/evidence/` | 利用者 | 証跡ブランチで追跡するJSON |
| `aidd/evidence/<user-name>` | 利用者 | リモートへ送るGitブランチ |

ここで、`aidd/evidence/<user-name>`は**Gitブランチ名**、`worktrees/aidd/evidence/<user-name>/`は**端末内の専用作業ツリー**、`.aidd/evidence/`は**その作業場所の中でGit追跡する証跡ディレクトリ**です。
実装では、この3つを混同しません。

### 証跡送信の実装例

次の`evidence.sh`が、JSONの抽出・作成、worktreeの作成と再利用、commit、pushまでを一貫して担います。
最初の証跡ブランチは、リモートに公開済みの`main`から派生させます。
開発者の手元にある未公開featureブランチの変更を混ぜないためです。
この実装例のworktreeには起点となった`main`のファイルもありますが、証跡送信処理が新たに作成・コミットするのは`.aidd/evidence/`配下のJSONだけです。
このworktreeをアプリケーション開発には使いません。

**このスクリプトが収集するのは、送信処理を確認するための最小限の構造化データです。自然言語による訂正の文脈解析やSkill不足の判定は、シェルスクリプトの役割に含めません。**
それらは、後述する履歴分析レポートや改訂支援Skillで扱います。

```bash
#!/usr/bin/env bash
# macOS / Linux向けの証跡送信例。Bash・Git・jqと標準的なshellコマンドが必要です。
# 通常: evidence.sh claude|codex < hook.json
# 再送: evidence.sh --flush （元の開発リポジトリから実行）
set -euo pipefail
umask 077
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_PREFIX
export GIT_TERMINAL_PROMPT=0

script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
mode=${1:-}
if [[ "$mode" == --send ]]; then
  [[ $# -eq 4 ]] || exit 64
  cd -- "$2"
fi
root=$(git rev-parse --show-toplevel)
cd "$root"
for cmd in git jq sed tr; do command -v "$cmd" >/dev/null; done
mode=${1:-}
case "$mode" in claude|codex|--send|--flush) ;; *) exit 64 ;; esac
[[ $(git config --local --bool aidd.evidence.enabled || true) == true ]] || exit 0
case "$(git branch --show-current)" in aidd/evidence/*) exit 0 ;; esac

sha256_hex() {
  local output
  if command -v sha256sum >/dev/null 2>&1; then
    output=$(sha256sum)
  elif command -v shasum >/dev/null 2>&1; then
    output=$(shasum -a 256)
  else
    echo 'sha256sumまたはshasumが必要です。' >&2
    return 1
  fi
  printf '%s\n' "${output%% *}"
}

new_event_id() {
  if command -v uuidgen >/dev/null 2>&1; then
    uuidgen | tr '[:upper:]' '[:lower:]'
  elif [[ -r /proc/sys/kernel/random/uuid ]]; then
    tr '[:upper:]' '[:lower:]' < /proc/sys/kernel/random/uuid
  else
    echo 'uuidgenまたは/proc/sys/kernel/random/uuidが必要です。' >&2
    return 1
  fi
}

# 裏で送信する際も、Hook発火時の利用者情報を使います。
if [[ "$mode" == --send ]]; then
  name=$3
  email=$4
else
  name=$(git config --get user.name)
  email=$(git config --get user.email)
fi
[[ -n "$name" && -n "$email" ]] || { echo 'Gitの利用者設定が必要です。' >&2; exit 1; }
common=$(git rev-parse --git-common-dir)
common=$(cd "$common" && pwd -P)
repo_hash=$(printf '%s' "$common" | sha256_hex)
actor_hash=$(printf '%s\0%s' "$name" "$email" | sha256_hex)
repo_id=${repo_hash:0:20}
slug=$(printf '%s' "$name" | LC_ALL=C tr -cs 'A-Za-z0-9_-' '-' | sed 's/^[-_]*//; s/[-_]*$//')
slug=${slug:0:36}
slug=${slug:-user}
uid=${actor_hash:0:12}
actor="$slug-$uid"
branch="aidd/evidence/$actor"
git check-ref-format --branch "$branch" >/dev/null
project_state="${XDG_STATE_HOME:-$HOME/.local/state}/aidd/$repo_id"
state="$project_state/state/$actor"
worktree="$project_state/worktrees/aidd/evidence/$actor"
mkdir -p "$state/pending" "$(dirname "$worktree")"

# 共有先は導入時に確認してローカル設定へ固定します。
approved=$(git config --local --get aidd.evidence.remote-url || true)
actual=$(git remote get-url --push --all origin)
[[ "$actual" != *$'\n'* ]] || {
  echo '送信先が複数あります。証跡の送信先を一つにしてください。' >&2; exit 1;
}
[[ -n "$approved" && "$actual" == "$approved" ]] || {
  echo '証跡の送信先が未承認、または変更されています。' >&2; exit 1;
}

if [[ "$mode" == claude || "$mode" == codex ]]; then
  id=$(new_event_id)
  tmp=$(mktemp "$state/pending/.event.XXXXXX")
  trap 'rm -f "$tmp"' EXIT
  # 本文・コマンド・ツール出力は複製せず、限定した項目だけを抽出します。
  jq -e --arg id "$id" --arg provider "$mode" --arg actor "$actor" \
    --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
    --arg branch "$(git branch --show-current)" \
    --arg head "$(git rev-parse HEAD)" '
    def safe_id:
      if type == "string" and length <= 128 and test("^[A-Za-z0-9_.:-]+$")
      then . else null end;
    if type != "object" then error("object required") else . end |
    . as $input |
    ["SessionStart","UserPromptSubmit","PreToolUse","PostToolUse",
     "PostToolUseFailure","Stop","SessionEnd","PermissionRequest",
     "SubagentStart","SubagentStop","PreCompact","PostCompact",
     "InstructionsLoaded","UserPromptExpansion","ConfigChange",
     "StopFailure","Interrupt"] as $events |
    if ($events | index($input.hook_event_name)) == null
    then error("unsupported event") else . end |
    {
      schema_version: 1, event_id: $id, observed_at: $at,
      provider: $provider, actor_key: $actor,
      hook_event_name: .hook_event_name,
      session_id: (.session_id | safe_id),
      turn_id: (.turn_id | safe_id),
      tool_use_id: (.tool_use_id | safe_id),
      tool_name: (if (["Bash","Read","Write","Edit","Skill","apply_patch"] | index($input.tool_name)) != null
                  then $input.tool_name else null end),
      prompt_chars: (if (.prompt | type) == "string" then (.prompt | length) else null end),
      application_branch: $branch, application_head: $head,
      result: (if .hook_event_name == "PostToolUseFailure" then "failed" else "unknown" end),
      semantic_analysis: "not_run", raw_text_stored: false
    }' > "$tmp"
  mv "$tmp" "$state/pending/$id.json"
  trap - EXIT
  # 入力を保全してから、同じshellが裏でworktree・commit・pushを担当します。
  nohup bash "$script_dir/evidence.sh" --send "$root" "$name" "$email" \
    </dev/null >>"$state/delivery.log" 2>&1 &
  exit 0
fi

# 同じ利用者の証跡worktreeへ同時にGit操作しないためのロック。
# 強制終了で残ったロックは、稼働プロセスを確認してから担当者が解消します。
locked=false
for ((i=0; i<300; i++)); do
  if mkdir "$state/writer.lock" 2>/dev/null; then locked=true; break; fi
  sleep 0.2
done
[[ "$locked" == true ]] || { echo '送信待ちです。ロックを確認してください。' >&2; exit 1; }
trap 'rmdir "$state/writer.lock"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
shopt -s nullglob
batch=("$state/pending/"*.json)
((${#batch[@]})) || exit 0

remote_ref="refs/remotes/origin/$branch"
if git ls-remote --exit-code --heads origin "refs/heads/$branch" >"$state/remote.txt"; then
  git fetch --no-tags origin "refs/heads/$branch:$remote_ref"
  base="$remote_ref"
else
  rc=$?
  [[ "$rc" == 2 ]] || exit "$rc"
  git fetch --no-tags origin refs/heads/main:refs/remotes/origin/main
  base=refs/remotes/origin/main
fi

if [[ ! -e "$worktree" ]]; then
  # 既存の同名ローカルブランチを、勝手に別の場所から奪いません。
  if git show-ref --verify --quiet "refs/heads/$branch"; then
    echo '同名の証跡ブランチが存在します。配置を確認してください。' >&2; exit 1
  fi
  git worktree add -b "$branch" "$worktree" "$base"
fi
[[ $(git -C "$worktree" rev-parse --show-toplevel) == "$worktree" ]]
[[ $(git -C "$worktree" branch --show-current) == "$branch" ]]
wt_common=$(git -C "$worktree" rev-parse --git-common-dir)
[[ $(cd "$worktree" && cd "$wt_common" && pwd -P) == "$common" ]]

# crashなどによる未処理差分は勝手に消さず停止します。pendingは残ります。
[[ -z $(git -C "$worktree" status --porcelain --untracked-files=normal) ]] || {
  echo '証跡worktreeに未処理の差分があります。' >&2; exit 1;
}
if git show-ref --verify --quiet "$remote_ref"; then
  if ! git -C "$worktree" merge-base --is-ancestor "$remote_ref" HEAD; then
    if git -C "$worktree" merge-base --is-ancestor HEAD "$remote_ref"; then
      git -C "$worktree" merge --ff-only "$remote_ref"
    else
      echo '別端末との履歴競合です。証跡を保持して停止します。' >&2; exit 1
    fi
  fi
fi

[[ ! -L "$worktree/.aidd" && ! -L "$worktree/.aidd/evidence" ]]
mkdir -p "$worktree/.aidd/evidence"
for file in "${batch[@]}"; do
  rel=".aidd/evidence/$(basename "$file")"
  [[ ! -L "$worktree/$rel" ]]
  if [[ -e "$worktree/$rel" ]]; then
    cmp -s "$file" "$worktree/$rel" || { echo '同一IDの内容が不一致です。' >&2; exit 1; }
  else
    cp "$file" "$worktree/$rel"
  fi
  git -C "$worktree" add -f -- "$rel"
done
if ! git -C "$worktree" diff --cached --quiet; then
  git -C "$worktree" -c user.name="$name" -c user.email="$email" \
    commit -m 'aidd: record structured evidence'
fi

# 今回送る履歴が、証跡の追加だけかを確認します。
[[ -z $(git -C "$worktree" rev-list --merges "$base..HEAD") ]]
while IFS= read -r commit; do
  while IFS= read -r changed; do
    [[ "$changed" =~ ^\.aidd/evidence/[0-9a-f-]+\.json$ ]] || {
      echo '証跡以外の履歴が混入しています。' >&2; exit 1;
    }
  done < <(git -C "$worktree" diff-tree --no-commit-id --name-only -r "$commit")
done < <(git -C "$worktree" rev-list "$base..HEAD")
git -C "$worktree" push --no-follow-tags origin "HEAD:refs/heads/$branch"
# push成功後に、今回送信した待機ファイルだけを除去します。
rm -- "${batch[@]}"
```

### 送信先を固定してから有効化する

導入時には、送信先リポジトリと保存項目を確認してから証跡送信を有効にします。
この初期設定をプロジェクトのセットアップ手順に組み込めば、開発メンバーが内部のGit操作を意識する必要はありません。

```bash
# originが承認済みの案件リポジトリであることを先に確認する
git remote -v
git config --local aidd.evidence.remote-url "$(git remote get-url --push origin)"
git config --local aidd.evidence.enabled true
```

会話本文をJSONへ複製していなくても、Gitのコミットメタデータには設定した名前やメールアドレスが残ります。
完全な匿名化を保証する処理ではない点に注意してください。

### 裏で動かしても欠測と失敗は残す

証跡送信は、開発メンバーの作業を待たせないようにバックグラウンドで実行します。
ただし、裏で動かすからといって失敗まで隠してはいけません。

### 証跡送信が失敗したときの保持と停止

ネットワークやリモートの一時的な失敗ではpendingを保持し、次のHookまたは手動flushで再送します。履歴分岐、未処理差分、古いロックのように安全な自動解決ができない状態では、データを消さず担当者の確認まで停止します。

- 証跡JSONをpendingへ永続化してから、利用者別ロックを取得して送信を試みます。
- pushの一時的な失敗ではpendingを残し、次回のHook発火またはevidence.sh --flushで再送します。
- リモートとの履歴分岐、証跡worktreeの未処理差分、古いロックではforce pushや自動削除を行わず停止します。

*再試行できる失敗と人が確認すべき競合を分ける*

同じworktreeへの並行アクセスは、ロックで直列に処理する設計です。
ネットワーク切断やリモートの拒否でpushに失敗したら、未送信のJSONをローカルの待機キュー（`pending/`）に残します。
再送のタイミングは、次のフック発火時か、手動で`evidence.sh --flush`を実行したときです。
同じ利用者が複数端末で作業してリモートと履歴が分岐した場合は、force pushで上書きせずに処理を止め、競合を記録します。

この実装例では、強制終了などで残った古いロックや、予期しないGit操作による未コミット差分をスクリプトが消去・初期化しません。
異常時はログを残して処理を止め、担当者が状況を確認して復旧できるようにしています。
チームの規模に応じて、イベントのバッチ送信、ディスク容量の上限管理、送信失敗時の通知も追加します。

`nohup`でバックグラウンド実行しても、CLIプロセスの終了やマシンの再起動をまたいで100%確実に送信が完了するとは限りません。
だから私は、まずローカルディスクに生データを保存し、失敗しても再試行できる構造を選んでいます。

## 横断的ガードは別記事で扱う

この記事の前提は、既存の危険操作ブロックです。
**Bunを参考にした横断的ガードと、`CLAUDE.md`・`AGENTS.md`に書く規律をshellでどう支えるかは、別の記事に分けます。**
複数のCLIや編集経路を対象にする制御を、証跡送信shellの役割に混ぜないためです。

| 境界 | この記事で扱う責務 | この記事で扱わない責務 |
|---|---|---|
| 観測用`PreToolUse` | 操作を試みた事実の記録 | 危険操作の許可・拒否 |
| 証跡shell | 利用者別ブランチへの証跡JSONのcommit・push | `main`や`aidd/main`への任意push |
| 同期shell | 決めた基盤ファイルをignore対象パスへ配置 | 通常開発側での基盤の直接改訂 |
| 横断的ガード | 既存の拒否設定を維持する前提 | 詳細な実装と評価は別記事 |

通常の開発側では基盤を読み取って使い、改訂は専用の作業場所で行います。
決めたファイルを開発側へ配置するのは、同期shellだけです。
証跡shellには、**解決した利用者の`aidd/evidence/<user-name>`と、証跡パスへの自動commit・pushだけ**を担当させます。
証跡を送る権限と、mainやaidd/mainへ任意にpushする権限は分けます。

この記事の`PreToolUse`設定は観測用であり、危険操作を拒否するガードの完成実装ではありません。
既存の拒否設定を維持したまま追加し、ガードの詳細と評価は別の記事で扱います。

## 訂正をそのままRule追加に変えない

証跡から判断するときは、次の段階を分けます。

### Skillが結果へ至るまでの診断段階

Skillの存在だけでは、必要な場面で利用されて結果が検証されたことを示せません。発見、選択、本文供給、手順実行、結果検証の各段階を分けて原因を探します。

- Skillが存在しても、候補として見つかり適用対象として選択されるまでは手順が供給されません。
- 本文が供給された後も、必要な手順が実行され、その結果が検証されるまで成功とは判断しません。

*Skillの存在から結果検証までを段階ごとに切り分ける*

「Skillがあるのに使われなかった」という事実だけでは、無視したのか、見つけられなかったのか、適用条件が違ったのかを確定できません。

### 失敗した段階から直す場所を選ぶ

| 診断候補 | 主に見直す場所 |
|---|---|
| 必要な業務条件が未定義 | 仕様・参照資料 |
| 再利用すべき手順がない | Skillの新設 |
| 該当Skillが見つからない・選ばれない | description・索引・起動条件 |
| 利用したSkillに手順が足りない | 本文・参照資料・補助script |
| 横断的な規律が曖昧 | Rule・CLAUDE.md・AGENTS.md |
| 要求を認識しても手順を実行しない | 検証ゲート・実行制御 |
| 古い内容や指示の矛盾がある | main取り込み・同期・指示整理 |
| 根拠を取得できていない | 収集処理 |

### 訂正か要件変更かを区別する

> [!IMPORTANT] ユーザーが強い口調で指示しただけで、それを「重大な失敗」に数えてはいけません。
> 途中で要件を追加・変更したケースや、Hookによる自動ループの継続も、人間が介入して誤りを正した訂正とは別です。

基盤の改訂は、特定した原因に対する最小限の修正から始めるのが基本です。
旧バージョンとの挙動比較、類似タスクへの影響、本来は発動しない文脈での誤起動、既存の振る舞いを壊すデグレ（回帰）がないかを確かめます。
プロンプトやRuleへ注意書きを足すだけではありません。
効果が薄れたRuleを削除する、役割が重なるSkillを統合する、自然言語の注意書きから同じ条件で繰り返し確認できる自動テストやガードへ責務を移すといった判断も必要です。

## 私が実際に回しているレポートと週次改訂

### 証跡を次の仕事に活かす週次改訂の循環

期間内の取得可能な履歴全量を分析し、根拠と原因候補を人間が採否判断できる形へ整えます。改訂後は同じリポジトリに判断と評価を残し、数週間後の実効性確認で得た結果を次回の分析に反映します。

- 各メンバーが指定期間の取得可能な履歴全量を分析し、欠測を含むレポートを作ります。
- 証跡とレポートを最新のmainに照らし、テックリードがYes・No・別案を選べる単位へ整理します。
- 採用・不採用・保留の理由、改訂内容、評価結果、追跡項目をaidd/mainへ記録します。
- 数週間後に実際の効果と副作用を確認し、その結果を次回の分析に反映します。

*全量分析から採否と改訂を経て後日の効果確認へつなぐ*

### 各自が期間を指定し履歴全量からレポートを作る

各メンバーの開発環境には、カスタムコマンドのように呼び出せるレポート作成用Skillを配布しています。
プロジェクト名と集計期間を指定すると、その期間に記録されたClaude CodeやCodexの実行履歴を分析し、構造化レポートを出力します。

ここで言う「全量」は、目についた数件やキーワード検索で見つかった断片的なログではありません。
**指定した期間に発生した、取得可能な対象セッションの全体**です。
長いコンテキストの履歴は分割して読み込み、最後に全体の傾向を集約する作りです。
対象の総件数、正常に解析できた件数、パース失敗や除外の件数も残し、欠測があればレポートに明記します。

普段の対象は、前回の改訂以降に記録された全履歴です。
必要に応じて、対象を過去数週間から数ヶ月まで広げます。
先行記事で書いた「初回の基盤構築では全履歴を精査し、その後は前回からの差分を中心に見直す」という進め方を自動化したものです。

- [ ] 対象期間と最後に確認したイベントを記録する
- [ ] 取得可能な対象セッションの総件数を残す
- [ ] 正常に解析できた件数を残す
- [ ] パース失敗と除外の件数を残す
- [ ] 取得できなかった範囲を欠測として明記する

### 証跡全量とレポートからYes／No／別案を選ぶ

基盤を改訂するテックリード（私自身）の環境には、分析と改訂に特化した専用Skillを用意しています。
対象期間の全証跡と各メンバーのレポートを入力にし、最新の`main`を取り込んだクリーンな環境で、AIエージェントと対話しながら是正候補を洗い出します。

### 是正候補を人間の採否判断へ変える流れ

AIが改善案を並べるだけでは改訂を決めません。事象、根拠、原因候補、修正箇所、効果確認方法を順に揃えた後で、人間がYes・No・別案を選びます。

- 何が起きたかを証跡から特定し、その判断を支える根拠を確認します。
- 原因候補と修正箇所を対応させ、何を観測すれば効果を確認できるかを定めます。
- 判断材料が揃った単位ごとに、人間がYes・No・別案を選びます。

*根拠と評価方法を揃えてから人間が採否を決める*

AIに「改善できそうな点」を並べさせただけで、改訂したつもりになってはいけません。
各項目を人間が責任を持って判断できる単位に分け、採用するかどうかを一つずつ選べる状態まで整えます。
検証材料が足りないものや、すぐに対応する優先度ではないものは、保留（次回以降の観察対象）です。

### 採用・不採用・保留の理由を残す

採用した改善案だけでなく、**不採用や保留にした案も、その理由と一緒に改訂ブランチ上の台帳（`.aidd/ground/decisions/`）へ記録・コミット**しています。
理由を残さないと、次週や次月の分析でAIが同じ筋の悪い修正案をまた持ち出すからです。

議論の末に別案を採用したときも、当初案を上書きして消しません。
当初案のどこに課題があり、なぜ別案を選んだのかを記録します。
保留した項目には「どの事象が何件発生したら再検討するか」「次回のモデル更新時に再検証する」といった見直しの条件も明記します。

| 判断 | 必ず残す内容 | 次に使う場面 |
|---|---|---|
| 採用 | 採用理由・変更内容・評価結果 | 回帰評価と後日の効果確認 |
| 不採用 | 却下した提案とその理由 | 同じ案を根拠なく蒸し返さないための照合 |
| 保留 | 現時点で決めない理由・再検討条件 | 指定件数の事象発生や次回モデル更新 |
| 別案 | 当初案の課題・別案を選んだ理由 | 判断経緯の再確認 |

採用案を評価するときは、議論のきっかけになった失敗事例だけでなく、過去の成功例と失敗例にも回帰テストを実行します。
目先の評価を通すために、テストの合格基準を都合よく緩めることはしません。
評価基準そのものを見直す必要があるなら、独立した技術判断として理由を台帳に記録します。

### 数週間後に効果を確かめる

基盤ファイルを更新した日に「これで改善できたはずだ」と完了扱いにはしません。
数週間後に、対象の訂正が実際に減ったか、新設・改訂したSkillが狙った場面で呼び出されたかを確認します。
追加した指示によって不要な確認や過剰なガード拒否が増え、開発速度を落としていないかも追跡データから検証します。

### 改訂後の効果を数週間後に判定する流れ

改訂当日の合格だけでは効果が続くかを判断できません。作業量、難易度、モデル、CLI、同期状態を照合した後で、維持・追加調整・Revert・別案を選びます。

- 数週間分の訂正、Skillの呼び出し、不要な確認、過剰なガード拒否を追跡します。
- 作業量、タスク難易度、LLMモデル、CLI、基盤の同期状態を照合し、同じ作業機会があったかを確かめます。
- 狙った改善が確認できれば維持し、確認できなければ追加調整・Revert・別案を選びます。

*条件差を照合してから改訂の実効性と副作用を判定する*

改訂後に同じ作業が発生していないなら、改善の効果があったとは言えません。
効果は、チームの作業量、タスクの難易度、LLMモデルのバージョン、CLIの変更、開発基盤の同期状態を照合した上で判断します。
狙った改善が見られなければ、追加調整、変更のロールバック（Revert）、別案への切り替えから次の手を選びます。

### 週次の改訂作業をレポートとして保存する

週次の改訂で一つのレポートにまとめるのは、分析対象の範囲、是正候補、採否とその根拠、コードやプロンプトの変更、テスト結果、保留事項、数週間後に確認する項目です。
過去に行った改訂の効果確認も、同じリポジトリの台帳に残します。

| `aidd/main`上のパス | 保存する内容 |
|---|---|
| `.aidd/ground/reports/` | 分析レポート・週次改訂レポート |
| `.aidd/ground/decisions/` | 採用・非採用・保留と理由 |
| `.aidd/ground/evals/` | 改訂前後の確認ケース |
| `.aidd/ground/follow-ups/` | 数週間後の効果確認 |

レポート作成Skillや改訂支援Skillは、他の業務Skillと同じように同期スクリプトから各開発者へ配布します。
一方、Skillが出力した個別レポートや過去の判断台帳は改訂専用ブランチ（`aidd/main`）だけで管理し、通常の開発ブランチには配りません。

開発メンバーが`feature/*`や`fix/*`の作業ツリーで改訂用Skillを呼び出しても、その場では開発基盤を直接編集・更新できません。
日々の作業場所では調査と候補抽出、専用ブランチでは改訂と検証、各作業ツリーには検証済みファイルの同期だけを行います。
この3つを分けるのが、スクリプトの制約です。

## 開発履歴を汚さずに改善を次の仕事に活かす

短期の改修、長期の機能開発、突発的な不具合対応が並行するfeature flowで、開発基盤の改訂コミットをすべての作業ブランチへ毎回マージする運用は続きません。

**Hook設定と運用シェルスクリプトを追跡・共有する場所は`main`です。日々の証跡は利用者専用worktreeで作り、`aidd/evidence/<user-name>`へ自動でcommit・pushします。開発基盤の正典は`aidd/main`で改訂・検証し、各メンバーは最新の基盤を`.gitignore`対象のパスへ1コマンドで同期します。**

この分離があるから、期間を指定した履歴全量の分析、Yes／No／別案による採否、不採用や保留の理由の記録、数週間後の効果確認を一つの流れにできます。
その結果を週次レポートに残し、次の改訂へ知見をつなげていく流れです。

チームへ導入するときの最初の作業は、**ブランチごとの追跡対象の切り分けと、安全な初期移行**です。
次に**既存の危険操作ガードを維持したHook設定、安全な自動送信、ステージを汚さない部分同期**を検証します。
その土台が整った後に、**採否理由の台帳記録と後日の効果確認**を定着させていきます。

- [ ] ブランチごとの追跡対象を切り分けて安全に初期移行する
- [ ] 既存ガードを維持したHook設定と安全な自動送信を検証する
- [ ] ステージを汚さない部分同期を検証する
- [ ] 採否理由の台帳記録と後日の効果追跡を定着させる

私たちが目指すべきは、収集するログの量を誇ることでも、プロンプトの文字数やRuleファイルの数をやみくもに増やすことでもありません。**一度現場で支払った失敗と訂正のコストを無駄にせず開発基盤の改善に活かし、次の自分やチームメンバーが同じ失敗や訂正を繰り返さなくて済むようにすること**です...！
