# 機能設計書 39-ファイル変更追跡

## 概要

本ドキュメントは、OpenCodeにおけるgitベースのファイル変更追跡機能（スナップショット機能）について記載する。この機能により、AIアシスタントが行った各操作前の状態を保存し、変更履歴の管理とリバート機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：AIアシスタントがコードを編集する際、予期しない変更や問題が発生する可能性がある。スナップショット機能により、各操作前のファイル状態を保存し、必要に応じて以前の状態に戻すことができる。これにより、AIアシスタントの変更を安全に試行錯誤できる環境を提供する。

**機能の利用シーン**：
- AIがファイルを編集する前に、現在の状態をスナップショットとして保存する
- 編集結果が期待通りでない場合に、以前のスナップショットにリバートする
- セッション内での全変更を確認し、特定のファイルのみを元に戻す

**主要な処理内容**：
1. スナップショットの初期化（gitリポジトリ初期化）
2. 現在の状態をツリーオブジェクトとして保存（track）
3. スナップショット間のファイル変更検出（patch）
4. 以前の状態への復元（restore）
5. 特定ファイルのリバート（revert）
6. 定期的なクリーンアップ（gc）

**関連システム・外部連携**：
- Git（スナップショットの保存・管理）
- プロジェクトのgitignore（除外パターンの継承）

**権限による制御**：スナップショット機能は設定ファイルで`snapshot: false`により無効化可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | セッション画面 | 補助機能 | SessionReviewTabによる変更差分の表示、スナップショットベースの変更追跡 |

## 機能種別

バージョン管理 / 状態管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| snapshot (設定) | boolean | No | スナップショット機能の有効/無効 | デフォルトtrue |
| hash | string | Yes (patch/restore) | スナップショットのハッシュ | 有効なgitハッシュ |
| patches | Patch[] | Yes (revert) | リバート対象のパッチ一覧 | - |

### 入力データソース

- ファイルシステム: ワークツリーの現在状態
- プロジェクトのgitリポジトリ: 除外パターン（info/exclude）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| hash | string | スナップショットのツリーハッシュ |
| Patch | object | 変更パッチ情報 |
| Patch.hash | string | スナップショットハッシュ |
| Patch.files | string[] | 変更されたファイルパス一覧 |

### 出力先

- スナップショットgitリポジトリ: `$DATA_DIR/snapshot/{project_id}`

## 処理フロー

### 処理シーケンス

```
1. 初期化（init + track）
   └─ スケジューラにクリーンアップタスク登録
   └─ gitリポジトリの初期化（存在しない場合）
   └─ autocrlf設定の無効化
   └─ 現在の状態をインデックスに追加
   └─ write-treeでツリーハッシュ取得

2. パッチ取得（patch）
   └─ 現在の状態をインデックスに追加
   └─ git diff --name-only で変更ファイル一覧取得
   └─ Patchオブジェクト返却

3. 復元（restore）
   └─ read-treeでツリーをインデックスに読み込み
   └─ checkout-indexで作業ツリーに展開

4. リバート（revert）
   └─ 各パッチのファイルについて
   └─ git checkout {hash} -- {file}で復元
   └─ ファイルがスナップショットに存在しない場合は削除

5. クリーンアップ（cleanup）
   └─ git gc --prune=7.days実行
```

### フローチャート

```mermaid
flowchart TD
    A[track呼び出し] --> B{VCS=git?}
    B -->|No| Z[処理終了]
    B -->|Yes| C{snapshot有効?}
    C -->|No| Z
    C -->|Yes| D{gitdir存在?}
    D -->|No| E[git init]
    E --> F[autocrlf設定]
    D -->|Yes| G[excludeファイル同期]
    F --> G
    G --> H[git add .]
    H --> I[git write-tree]
    I --> J[ハッシュ返却]

    K[patch呼び出し] --> L[git add .]
    L --> M[git diff --name-only hash]
    M --> N[Patchオブジェクト生成]
    N --> O[返却]

    P[restore呼び出し] --> Q[git read-tree hash]
    Q --> R[git checkout-index -a -f]

    S[revert呼び出し] --> T[各ファイルについて]
    T --> U[git checkout hash -- file]
    U --> V{checkout成功?}
    V -->|Yes| W[次のファイルへ]
    V -->|No| X{スナップショットに存在?}
    X -->|No| Y[ファイル削除]
    X -->|Yes| W
    Y --> W
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-39-01 | gitプロジェクトのみ | VCSがgitのプロジェクトでのみスナップショットを作成 | 常時 |
| BR-39-02 | ACPクライアント除外 | OPENCODE_CLIENT=acpの場合はスナップショット無効 | 常時 |
| BR-39-03 | exclude同期 | プロジェクトのgit exclude設定をスナップショットリポジトリに同期 | track時 |
| BR-39-04 | autocrlf無効化 | Windowsでの改行コード変換を防止するためautocrlf=false | 初期化時 |
| BR-39-05 | クリーンアップ間隔 | 1時間ごとにgc --prune=7.daysを実行 | 定期実行 |

### 計算ロジック

**スナップショットパス**:
```javascript
const gitdir = path.join(Global.Path.data, "snapshot", project.id)
```

## データベース操作仕様

### 操作別データベース影響一覧

本機能はgitリポジトリをデータストアとして使用。

**ディレクトリ構造**:
```
$DATA_DIR/
  snapshot/
    {project_id}/
      objects/
      refs/
      info/exclude
```

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| GitDiffError | 差分エラー | git diffが失敗 | 空のパッチを返却 |
| RestoreError | 復元エラー | read-tree/checkout-indexが失敗 | エラーログ出力 |
| CheckoutError | チェックアウトエラー | checkout {hash} -- {file}が失敗 | ファイル存在確認、存在しなければ削除 |

### リトライ仕様

- 各操作の失敗時に自動リトライは行わない

## トランザクション仕様

該当なし（gitの原子性に依存）

## パフォーマンス要件

- クリーンアップ（gc）は1時間ごとにバックグラウンドで実行
- gc --prune=7.daysにより7日以上前のオブジェクトを削除

## セキュリティ考慮事項

- スナップショットはユーザーのデータディレクトリに保存
- プロジェクトのgitignoreを継承することで機密ファイルの除外を維持

## 備考

スナップショットの制限:
- gitプロジェクトでのみ動作
- ACPクライアントでは無効
- 大規模リポジトリでは初回のgit addに時間がかかる場合がある

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

スナップショット関連の型定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/opencode/src/snapshot/index.ts` | Patch型定義（79-83行目） |

**読解のコツ**: Patchはhashとfiles（変更ファイルパス配列）のみを持つシンプルな構造。

#### Step 2: 初期化とスケジューラを理解する

スナップショット機能の初期化。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/opencode/src/snapshot/index.ts` | init関数（17-24行目） |
| 2-2 | index.ts | `packages/opencode/src/snapshot/index.ts` | cleanup関数（26-49行目） |

**主要処理フロー**:
- **17-24行目**: Scheduler.registerによるクリーンアップタスク登録
- **27行目**: VCS=git確認
- **36行目**: gc --prune=7.days実行

#### Step 3: track（スナップショット作成）を理解する

現在状態の保存。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/opencode/src/snapshot/index.ts` | track関数（51-77行目） |
| 3-2 | index.ts | `packages/opencode/src/snapshot/index.ts` | add関数（257-259行目） |
| 3-3 | index.ts | `packages/opencode/src/snapshot/index.ts` | syncExclude関数（262-274行目） |

**主要処理フロー**:
- **52行目**: VCS=git, OPENCODE_CLIENT確認
- **56-68行目**: gitdir初期化（git init, autocrlf設定）
- **69行目**: add関数呼び出し
- **70-76行目**: write-treeでハッシュ取得

#### Step 4: patch（変更検出）を理解する

スナップショット間の差分取得。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.ts | `packages/opencode/src/snapshot/index.ts` | patch関数（85-110行目） |

**主要処理フロー**:
- **87行目**: add関数で現在状態をインデックスに追加
- **88-93行目**: git diff --name-only {hash}で変更ファイル取得
- **100-108行目**: ファイルパスの結合と正規化

#### Step 5: restore/revert（復元）を理解する

以前の状態への復元。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | index.ts | `packages/opencode/src/snapshot/index.ts` | restore関数（112-129行目） |
| 5-2 | index.ts | `packages/opencode/src/snapshot/index.ts` | revert関数（131-161行目） |

**主要処理フロー**:
- **115-119行目**: read-tree + checkout-index -a -fで復元
- **134-158行目**: 各ファイルについてcheckout {hash} -- {file}
- **144-155行目**: checkout失敗時のls-tree確認とファイル削除

### プログラム呼び出し階層図

```
Snapshot.init()
    │
    └─ Scheduler.register({id: "snapshot.cleanup", ...})
           │
           └─ cleanup() [1時間ごと]
                  │
                  └─ git gc --prune=7.days

Snapshot.track()
    │
    ├─ gitdir() - スナップショットリポジトリパス取得
    │
    ├─ [初回] git init + git config core.autocrlf false
    │
    ├─ add(git)
    │      │
    │      ├─ syncExclude(git) - excludeファイル同期
    │      │
    │      └─ git add .
    │
    └─ git write-tree → ハッシュ返却

Snapshot.patch(hash)
    │
    ├─ add(git)
    │
    └─ git diff --name-only {hash} → Patch返却

Snapshot.restore(hash)
    │
    ├─ git read-tree {hash}
    │
    └─ git checkout-index -a -f

Snapshot.revert(patches)
    │
    └─ [各patch.filesについて]
           │
           ├─ git checkout {patch.hash} -- {file}
           │
           └─ [失敗時] git ls-tree確認 → 存在しなければファイル削除
```

### データフロー図

```
[入力]                    [処理]                         [出力]

ワークツリー ────────▶ git add . ─────────────────▶ インデックス
                            │
                            ▼
                    git write-tree
                            │
                            ▼
                    ツリーハッシュ ────────────────▶ hash

hash ───────────────▶ git diff --name-only ────────▶ 変更ファイル一覧
                            │
                            ▼
                    Patch {hash, files}

hash ───────────────▶ git read-tree ───────────────▶ インデックス復元
                            │
                            ▼
                    git checkout-index ─────────────▶ ワークツリー復元
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/opencode/src/snapshot/index.ts` | ソース | スナップショット全機能（track, patch, restore, revert, cleanup） |
| instance.ts | `packages/opencode/src/project/instance.ts` | ソース | プロジェクト情報（VCS, directory, worktree） |
| global.ts | `packages/opencode/src/global.ts` | ソース | Global.Path.data（スナップショット保存先） |
| scheduler.ts | `packages/opencode/src/scheduler.ts` | ソース | 定期タスクスケジューラ |
