# 機能設計書 42-自動フォーマット

## 概要

本ドキュメントは、OpenCodeにおける自動フォーマット機能の設計を記述する。ファイル編集後に自動的にフォーマッターを実行し、コードの一貫性を維持する機能を提供する。

### 本機能の処理概要

自動フォーマット機能は、AIエージェントやユーザーによるファイル編集イベントを監視し、編集されたファイルの拡張子に応じて適切なフォーマッターを自動実行する。プロジェクトで利用可能なフォーマッター（Prettier、Biome、gofmt等）を自動検出し、設定に基づいて実行する。

**業務上の目的・背景**：AIエージェントがコードを生成・編集する際、プロジェクトのコーディングスタイルに必ずしも準拠しないことがある。自動フォーマット機能により、編集後のコードがプロジェクトの規約に自動的に整形され、手動でのフォーマット作業が不要になる。また、チーム開発においてコードスタイルの一貫性を保つことで、レビュー時の指摘事項を減らし、開発効率を向上させる。

**機能の利用シーン**：
- AIエージェントがコードを編集した直後の自動整形
- Write/Editツールによるファイル変更後のフォーマット適用
- 新規ファイル作成時のスタイル統一

**主要な処理内容**：
1. ファイル編集イベント（File.Event.Edited）の監視
2. 編集ファイルの拡張子に対応するフォーマッターの検索
3. フォーマッターの有効性チェック（プロジェクトでの利用可能性確認）
4. フォーマッターコマンドの実行
5. 実行結果のログ出力

**関連システム・外部連携**：
- Prettier: Node.jsベースのフォーマッター（bun x prettier）
- Biome: Rust製の高速フォーマッター（bun x @biomejs/biome）
- gofmt: Go言語標準フォーマッター
- その他20以上の言語固有フォーマッター

**権限による制御**：設定ファイルで`formatter: false`と指定することで全体を無効化可能。個別のフォーマッターも`disabled: true`で無効化できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面関連なし（バックグラウンド処理） |

## 機能種別

自動処理 / ファイル変換処理

## 入力仕様

### 入力パラメータ（イベント）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| file | string | Yes | 編集されたファイルの絶対パス | 有効なファイルパス |

### 入力データソース

- Bus経由で受信するFile.Event.Editedイベント
- 設定ファイル（opencode.json/opencode.jsonc）のformatter設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| status | Status[] | 各フォーマッターの状態（API経由で取得可能） |

### Status型定義

```typescript
Status = {
  name: string,        // フォーマッター名
  extensions: string[], // 対応拡張子
  enabled: boolean      // 有効/無効状態
}
```

### 出力先

- フォーマットされたファイル（元ファイルを上書き）
- ログ出力

## 処理フロー

### 処理シーケンス

```
1. 初期化（Format.init）
   └─ Bus.subscribe でファイル編集イベントを監視開始

2. ファイル編集イベント受信
   └─ File.Event.Edited イベントからファイルパスを取得

3. フォーマッター選択
   ├─ ファイル拡張子を取得
   ├─ 拡張子に対応するフォーマッターを検索
   └─ 各フォーマッターの有効性をチェック

4. フォーマッター実行
   ├─ コマンドを構築（$FILE をファイルパスに置換）
   ├─ Bun.spawn でプロセス実行
   └─ 終了コードを確認

5. 結果処理
   ├─ 成功時：ログ出力
   └─ 失敗時：エラーログ出力
```

### フローチャート

```mermaid
flowchart TD
    A[ファイル編集イベント受信] --> B[ファイル拡張子を取得]
    B --> C[対応フォーマッターを検索]
    C --> D{フォーマッターあり?}
    D -->|No| E[処理終了]
    D -->|Yes| F[次のフォーマッターを取得]
    F --> G{有効?}
    G -->|No| H{他にフォーマッターあり?}
    G -->|Yes| I[コマンド構築]
    I --> J[Bun.spawn実行]
    J --> K{成功?}
    K -->|Yes| L[ログ出力]
    K -->|No| M[エラーログ出力]
    L --> H
    M --> H
    H -->|Yes| F
    H -->|No| E
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-42-01 | 全体無効化 | config.formatter === false で全フォーマッター無効化 | 設定ファイル確認時 |
| BR-42-02 | 個別無効化 | formatter[name].disabled === true で特定フォーマッター無効化 | フォーマッター検索時 |
| BR-42-03 | 拡張子マッチング | ファイル拡張子と extensions 配列のマッチングで適用判定 | フォーマッター選択時 |
| BR-42-04 | 有効性チェック | enabled() がtrueを返すフォーマッターのみ実行 | 実行前 |
| BR-42-05 | カスタムフォーマッター | 設定でcommand配列を指定することでカスタムフォーマッター追加可能 | 設定ファイル読み込み時 |

### 計算ロジック

フォーマッター選択ロジック：
1. ファイル拡張子を取得
2. 全フォーマッターの extensions をチェック
3. マッチするフォーマッターの enabled() を呼び出し
4. true を返すもののみを実行対象とする

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

本機能はデータベースを操作しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 実行失敗 | フォーマッターコマンドが非0で終了 | エラーログ出力、次のフォーマッターへ |
| - | spawn失敗 | Bun.spawn でエラー発生 | catch でエラーログ出力 |

### リトライ仕様

リトライは実装されていない。エラー発生時はログ出力のみ。

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

ファイル単位での処理。フォーマッターの実行失敗が他のフォーマッターや他のファイルに影響しない。

## パフォーマンス要件

- フォーマッター実行は非同期で行われる
- 編集イベントごとに対応するフォーマッターが順次実行される

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

- フォーマッターコマンドは設定ファイルで指定可能なため、信頼できる設定のみ使用すること
- 環境変数もフォーマッター設定で指定可能

## 備考

対応フォーマッター一覧（組み込み）：
- **JavaScript/TypeScript**: prettier, biome, oxfmt
- **Go**: gofmt
- **Python**: ruff, uv format
- **Rust**: rustfmt
- **その他**: zig, clang-format, ktlint, rubocop, dart, terraform, shfmt, nixfmt 等

---

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

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

### 推奨読解順序

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

フォーマッター情報の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | formatter.ts | `packages/opencode/src/format/formatter.ts` | Info インターフェース（7-13行目）でフォーマッター定義を確認 |
| 1-2 | index.ts | `packages/opencode/src/format/index.ts` | Status型（15-24行目）でAPI出力形式を確認 |

**読解のコツ**: `Info` インターフェースはname, command, environment, extensions, enabled() を持つ。enabled() は非同期関数でプロジェクトでの利用可能性を判定。

#### Step 2: エントリーポイントを理解する

初期化とイベント購読の流れを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/opencode/src/format/index.ts` | init関数（103-136行目）がエントリーポイント |

**主要処理フロー**:
1. **103行目**: init関数の開始
2. **105行目**: Bus.subscribe でFile.Event.Editedを監視
3. **106-134行目**: イベントハンドラ内でフォーマッター実行

#### Step 3: 状態管理を理解する

フォーマッターの検出と有効性管理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/opencode/src/format/index.ts` | state関数（26-64行目）でフォーマッター状態管理 |
| 3-2 | index.ts | `packages/opencode/src/format/index.ts` | isEnabled関数（66-74行目）で有効性キャッシュ |
| 3-3 | index.ts | `packages/opencode/src/format/index.ts` | getFormatter関数（76-87行目）で拡張子マッチング |

**主要処理フロー**:
- **26-64行目（state）**: 設定からフォーマッター情報を構築
- **31-37行目**: formatter === false で全無効化
- **42-57行目**: カスタムフォーマッター設定のマージ

#### Step 4: 組み込みフォーマッターを理解する

各言語のフォーマッター定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | formatter.ts | `packages/opencode/src/format/formatter.ts` | prettier（33-79行目）: package.json依存チェック |
| 4-2 | formatter.ts | `packages/opencode/src/format/formatter.ts` | biome（103-147行目）: 設定ファイル存在チェック |
| 4-3 | formatter.ts | `packages/opencode/src/format/formatter.ts` | gofmt（15-22行目）: which コマンドでパス確認 |

**読解のコツ**: 各フォーマッターの enabled() は異なる判定ロジックを持つ。prettierはpackage.json依存、biomeは設定ファイル存在、gofmtはバイナリ存在で判定。

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

```
Format.init()
    │
    └─ Bus.subscribe(File.Event.Edited)
           │
           └─ イベントハンドラ
                  │
                  ├─ getFormatter(ext)
                  │      ├─ state() - フォーマッター一覧取得
                  │      └─ isEnabled(item) - 有効性確認
                  │
                  └─ Bun.spawn({cmd: item.command})
                         └─ フォーマッターコマンド実行
```

### データフロー図

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

File.Event.Edited
       │
       ▼
ファイルパス ───▶ getFormatter()
                     │
                     ├─ 拡張子マッチング
                     ├─ enabled()チェック
                     │
                     ▼
               Bun.spawn() ───▶ フォーマット済みファイル
                                      │
                                      ▼
                                  ログ出力
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/opencode/src/format/index.ts` | ソース | フォーマット機能のメインモジュール |
| formatter.ts | `packages/opencode/src/format/formatter.ts` | ソース | 組み込みフォーマッターの定義 |
| config.ts | `packages/opencode/src/config/config.ts` | ソース | 設定情報の取得（formatter設定） |
| file.ts | `packages/opencode/src/file/index.ts` | ソース | File.Event.Editedイベントの発行元 |
| bus.ts | `packages/opencode/src/bus/index.ts` | ソース | イベントバス |
