# 機能設計書 9-Edit ツール

## 概要

本ドキュメントは、OpenCodeシステムにおけるEdit ツールの機能設計を記述する。Edit ツールは、ファイルの文字列置換による編集機能を提供し、複数の置換戦略（完全一致、行トリム、ブロックアンカー、空白正規化等）をサポートしている。

### 本機能の処理概要

Edit ツールは、指定されたファイル内の文字列を別の文字列に置換する。単純な完全一致だけでなく、空白やインデントの違いを吸収する柔軟なマッチング戦略を持ち、AIによるコード編集でよく発生するフォーマットの微差を許容する。

**業務上の目的・背景**：コード編集において、AIが生成した置換文字列がファイル内の実際の文字列と完全に一致しないことがある（インデント、空白、改行の違いなど）。Edit ツールは、複数のマッチング戦略を順番に試行することで、これらの差異を吸収し、意図した編集を成功させる。

**機能の利用シーン**：バグ修正、機能追加、リファクタリングなど、既存ファイルの部分的な編集。「この関数にエラーハンドリングを追加して」「この変数名を変更して」などの指示に対応。

**主要な処理内容**：
1. ファイルパスの検証と正規化
2. 外部ディレクトリ権限チェック
3. ファイル読み取り時刻の検証（FileTime.assert）
4. 複数の置換戦略による文字列マッチング
5. ファイル書き込みと差分生成
6. LSP診断情報の取得とエラー検出

**関連システム・外部連携**：LSP（Language Server Protocol）との連携により、編集後のファイルに対する診断情報（エラー、警告）を取得し、編集結果の妥当性を検証する。

**権限による制御**：edit権限でファイルパスパターンをチェック。外部ディレクトリへのアクセスはexternal_directory権限で制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | セッション | 補助機能 | Editコンポーネントによるファイル編集差分表示 |

## 機能種別

ファイル編集 / 文字列置換

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| filePath | string | Yes | 編集対象ファイルの絶対パス | 有効なファイルパス |
| oldString | string | Yes | 置換対象の文字列 | newStringと異なること |
| newString | string | Yes | 置換後の文字列 | oldStringと異なること |
| replaceAll | boolean | No | 全置換フラグ（デフォルトfalse） | boolean型 |

### 入力データソース

- エージェントからのツール呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| diff | string | 生成された差分（unified diff形式） |
| filediff | Snapshot.FileDiff | 追加/削除行数を含む差分情報 |
| diagnostics | object | LSP診断情報 |

### 出力先

- ツール結果としてエージェントに返却
- ファイルシステムへの書き込み
- スナップショットへの記録

## 処理フロー

### 処理シーケンス

```
1. パラメータ検証
   └─ filePath、oldString、newStringの検証
2. パス正規化
   └─ 絶対パスへの変換
3. 外部ディレクトリ権限チェック
   └─ assertExternalDirectory()
4. ファイルロック取得
   └─ FileTime.withLock()
5. ファイル読み取り時刻検証
   └─ FileTime.assert()
6. 置換戦略の順次適用
   └─ 9種類のReplacerを順番に試行
7. 権限チェック
   └─ edit権限でパターンチェック
8. ファイル書き込み
   └─ Filesystem.write()
9. LSP診断取得
   └─ LSP.diagnostics()でエラー検出
10. 結果返却
    └─ 差分と診断情報を返却
```

### フローチャート

```mermaid
flowchart TD
    A[パラメータ受信] --> B{oldString == newString?}
    B -->|Yes| C[エラー: 変更なし]
    B -->|No| D[パス正規化]
    D --> E[外部ディレクトリチェック]
    E --> F[ファイルロック取得]
    F --> G{ファイル存在?}
    G -->|No| H[新規作成処理]
    G -->|Yes| I[ファイル読み込み]
    I --> J[FileTime検証]
    J --> K[Replacer順次適用]
    K --> L{マッチ成功?}
    L -->|No| M[エラー: 見つからない]
    L -->|Yes| N{複数マッチ?}
    N -->|Yes| O{replaceAll?}
    O -->|No| P[エラー: 複数マッチ]
    O -->|Yes| Q[全置換実行]
    N -->|No| R[単一置換実行]
    Q --> S[ファイル書き込み]
    R --> S
    H --> S
    S --> T[LSP診断取得]
    T --> U[結果返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 同一文字列禁止 | oldStringとnewStringが同一の場合はエラー | 常時 |
| BR-02 | 一意マッチ | replaceAll=falseの場合、oldStringは一意である必要がある | replaceAll未設定時 |
| BR-03 | 新規作成 | oldStringが空の場合、ファイル新規作成 | oldString == "" |
| BR-04 | ファイル時刻検証 | 最後の読み取り以降に変更されていないことを確認 | 既存ファイル編集時 |
| BR-05 | 診断エラー通知 | LSPエラー検出時は結果に含めて通知 | 編集完了時 |
| BR-06 | 診断制限 | 1ファイルあたり最大20件のエラーを表示 | エラー多数時 |

### 計算ロジック

- 差分計算: createTwoFilesPatch()で unified diff生成
- 追加/削除行数: diffLines()で計算

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ツール結果保存 | message_part | UPDATE | 編集結果のメタデータ保存 |

### テーブル別操作詳細

#### message_part

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | state.metadata.diff | 差分テキスト | 編集完了時 |
| UPDATE | state.metadata.filediff | 追加/削除行数 | 編集完了時 |
| UPDATE | state.metadata.diagnostics | LSP診断情報 | 編集完了時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 変更なしエラー | oldString == newString | パラメータ修正を要求 |
| - | ファイル未検出 | ファイルが存在しない | パス確認を要求 |
| - | ディレクトリエラー | パスがディレクトリ | ファイルパス指定を要求 |
| - | マッチ未検出 | oldStringが見つからない | より正確な文字列指定を要求 |
| - | 複数マッチ | 複数箇所でマッチ | より多くのコンテキストを要求 |
| - | DeniedError | 権限ルールによる拒否 | エラーメッセージ返却 |

### リトライ仕様

編集操作はリトライなし。失敗時はエラー状態を返却し、エージェントが修正して再試行。

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

FileTime.withLock()でファイルロックを取得し、読み込み・編集・書き込みをアトミックに実行。

## パフォーマンス要件

- 複数のReplacerを効率的に順次試行
- LSP診断の非同期取得

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

- 外部ディレクトリアクセスの権限チェック
- ファイルパスパターンに基づく権限制御
- ファイル時刻検証による競合状態検出

## 備考

Edit ツールは、9種類のReplacer戦略を持つ：SimpleReplacer、LineTrimmedReplacer、BlockAnchorReplacer、WhitespaceNormalizedReplacer、IndentationFlexibleReplacer、EscapeNormalizedReplacer、TrimmedBoundaryReplacer、ContextAwareReplacer、MultiOccurrenceReplacer。

---

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

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

### 推奨読解順序

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

Editツールのパラメータスキーマと出力構造を理解。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | edit.ts | `packages/opencode/src/tool/edit.ts` | EditTool定義とパラメータスキーマ（27-34行目） |
| 1-2 | tool.ts | `packages/opencode/src/tool/tool.ts` | Tool.define()の基盤構造 |

**読解のコツ**: zodスキーマでfilePath、oldString、newString、replaceAllを定義している点を確認。

#### Step 2: 実行フローを理解する

execute関数の処理フローを把握。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | edit.ts | `packages/opencode/src/tool/edit.ts` | execute関数（35-154行目） |

**主要処理フロー**:
1. **36-42行目**: パラメータ検証（同一文字列チェック）
2. **44-45行目**: パス正規化とexternal_directoryチェック
3. **50-74行目**: 新規作成処理（oldString === ""の場合）
4. **76-81行目**: ファイル読み込みとFileTime検証
5. **81行目**: replace()関数で置換実行
6. **132-142行目**: LSP診断取得とエラー通知

#### Step 3: Replacer戦略を理解する

複数のマッチング戦略を把握。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | edit.ts | `packages/opencode/src/tool/edit.ts` | replace()関数（617-654行目） |
| 3-2 | edit.ts | `packages/opencode/src/tool/edit.ts` | 各Replacer実装（183-579行目） |

**主要処理フロー**:
- **624-634行目**: 9種類のReplacerを順番に試行
- **183-185行目**: SimpleReplacer（完全一致）
- **187-225行目**: LineTrimmedReplacer（行トリムマッチ）
- **227-360行目**: BlockAnchorReplacer（ブロックアンカーマッチ）
- **362-404行目**: WhitespaceNormalizedReplacer（空白正規化マッチ）
- **406-432行目**: IndentationFlexibleReplacer（インデント柔軟マッチ）

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

```
EditTool.execute() [edit.ts]
    │
    ├─ assertExternalDirectory()
    │      └─ 外部ディレクトリ権限チェック
    │
    ├─ FileTime.withLock()
    │      │
    │      ├─ FileTime.assert()
    │      │      └─ ファイル時刻検証
    │      │
    │      ├─ Filesystem.readText()
    │      │      └─ ファイル読み込み
    │      │
    │      ├─ replace()
    │      │      │
    │      │      ├─ SimpleReplacer
    │      │      ├─ LineTrimmedReplacer
    │      │      ├─ BlockAnchorReplacer
    │      │      ├─ WhitespaceNormalizedReplacer
    │      │      ├─ IndentationFlexibleReplacer
    │      │      ├─ EscapeNormalizedReplacer
    │      │      ├─ TrimmedBoundaryReplacer
    │      │      ├─ ContextAwareReplacer
    │      │      └─ MultiOccurrenceReplacer
    │      │
    │      └─ Filesystem.write()
    │             └─ ファイル書き込み
    │
    └─ LSP.diagnostics()
           └─ 編集後の診断情報取得
```

### データフロー図

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

filePath ───▶ パス正規化
    │               │
    │               └──▶ 権限チェック
    │
oldString ───▶                 ┌──▶ 差分生成
    │              ├──▶ 置換実行 ──┤
newString ───▶     │              └──▶ filediff計算
    │              │
replaceAll ───▶    └──▶ Replacer選択
                              │
                              ├──▶ SimpleReplacer
                              ├──▶ LineTrimmedReplacer
                              ├──▶ BlockAnchorReplacer
                              └──▶ ...
                                      │
                              ファイル書き込み
                                      │
                              LSP診断取得 ───▶ diagnostics
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| edit.ts | `packages/opencode/src/tool/edit.ts` | ソース | Editツール実装（メイン） |
| edit.txt | `packages/opencode/src/tool/edit.txt` | テンプレート | ツール説明文 |
| tool.ts | `packages/opencode/src/tool/tool.ts` | ソース | ツール基盤定義 |
| external-directory.ts | `packages/opencode/src/tool/external-directory.ts` | ソース | 外部ディレクトリ権限チェック |
| time.ts | `packages/opencode/src/file/time.ts` | ソース | ファイル時刻管理（FileTime） |
| filesystem.ts | `packages/opencode/src/util/filesystem.ts` | ソース | ファイルシステム操作 |
| index.ts | `packages/opencode/src/lsp/index.ts` | ソース | LSP診断取得 |
