# 機能設計書 32-診断情報取得

## 概要

本ドキュメントは、OpenCodeにおけるLSPサーバーからの診断情報（エラー・警告）取得機能について記載する。この機能により、AIアシスタントはコード編集時にリアルタイムでコンパイルエラーや型エラーを検知し、修正提案を行うことができる。

### 本機能の処理概要

**業務上の目的・背景**：コード編集を行う際、構文エラーや型エラーをリアルタイムで検出することは、高品質なコード生成に不可欠である。AIアシスタントがファイルを編集した後、LSPサーバーから診断情報を取得することで、生成したコードの問題を即座に検知し、自動修正や開発者への警告が可能となる。

**機能の利用シーン**：
- AIがファイルを編集した後、変更内容に構文エラーや型エラーがないか確認する
- 開発者がコードレビューを依頼した際、潜在的な問題を診断情報から抽出する
- ファイル保存時にLSP診断と連携して問題を報告する

**主要な処理内容**：
1. LSPサーバーへの`textDocument/didOpen`または`textDocument/didChange`通知
2. LSPサーバーからの`textDocument/publishDiagnostics`ノーティフィケーション受信
3. 診断情報のパス別・重要度別の管理と集約
4. 診断完了待機（デバウンス処理付き）
5. 人間可読形式への診断情報フォーマット

**関連システム・外部連携**：
- 各言語のLSPサーバー（診断情報の提供元）
- Write/Editツール（ファイル編集後の診断トリガー）
- バスイベントシステム（診断イベントの配信）

**権限による制御**：診断情報取得はLSP機能の一部として動作し、LSPが無効化されている場合は診断情報も取得されない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | セッション | 補助機能 | Editツール、Writeツールの実行結果に診断情報が含まれる |

## 機能種別

データ取得 / イベント処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| path | string | Yes | 診断対象ファイルの絶対パス | ファイルが存在すること |
| waitForDiagnostics | boolean | No | 診断完了を待つかどうか | - |

### 入力データソース

- LSPサーバー: `textDocument/publishDiagnostics`ノーティフィケーション
- ファイルシステム: 対象ファイルの内容読み取り

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| diagnostics | Map<string, Diagnostic[]> | パス別の診断情報マップ |
| Diagnostic.range | Range | 問題箇所の開始・終了位置 |
| Diagnostic.severity | number | 重要度（1:Error, 2:Warning, 3:Info, 4:Hint） |
| Diagnostic.message | string | 診断メッセージ |
| Diagnostic.source | string | 診断ソース（LSPサーバー名） |

### 出力先

- インメモリ状態: クライアント別の診断情報マップ
- バスイベント: `lsp.client.diagnostics`イベント

## 処理フロー

### 処理シーケンス

```
1. ファイルタッチ（touchFile）
   └─ 対象ファイルのLSPクライアント取得
   └─ waitForDiagnostics=trueの場合は診断待機プロミス作成

2. ファイルオープン通知
   └─ textDocument/didOpenまたはtextDocument/didChange送信
   └─ ファイルバージョン管理の更新

3. 診断情報受信
   └─ publishDiagnosticsノーティフィケーションハンドラで受信
   └─ 診断情報をパスをキーにマップに格納
   └─ lsp.client.diagnosticsイベント発行

4. 診断待機（オプション）
   └─ イベントサブスクライブによる待機
   └─ 150msデバウンス（追加診断を待つ）
   └─ 3秒タイムアウト

5. 診断情報集約
   └─ 全クライアントから診断情報を収集
   └─ パス別に結合
```

### フローチャート

```mermaid
flowchart TD
    A[touchFile呼び出し] --> B[LSPクライアント取得]
    B --> C{クライアントあり?}
    C -->|No| Z[終了]
    C -->|Yes| D{waitForDiagnostics?}
    D -->|Yes| E[waitForDiagnosticsプロミス作成]
    D -->|No| F[didOpen/didChange送信]
    E --> F
    F --> G[LSPサーバーが解析]
    G --> H[publishDiagnostics受信]
    H --> I[診断マップ更新]
    I --> J[イベント発行]
    J --> K{待機中?}
    K -->|Yes| L[150msデバウンス]
    L --> M{追加診断?}
    M -->|Yes| H
    M -->|No| N[プロミス解決]
    K -->|No| Z
    N --> Z
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-32-01 | デバウンス処理 | 診断受信後150ms待機し、追加診断がなければ完了とみなす | waitForDiagnostics時 |
| BR-32-02 | タイムアウト | 診断待機は最大3秒でタイムアウト | waitForDiagnostics時 |
| BR-32-03 | TypeScript初回除外 | TypeScriptサーバーの初回診断イベントはスキップ（初期化完了前の不完全な診断を回避） | TypeScriptサーバー |
| BR-32-04 | 重要度マッピング | 1=ERROR, 2=WARN, 3=INFO, 4=HINT | 常時 |

### 計算ロジック

**行番号変換**:
- LSP行番号（0-based）をエディタ表示用（1-based）に変換
- `line + 1`で表示用行番号を算出

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

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

本機能はデータベースを使用しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| TimeoutError | タイムアウト | 3秒以内に診断情報が返らない | 空の結果で処理続行 |
| NoClientError | クライアントなし | 対象ファイルのLSPクライアントが存在しない | 診断なしで処理続行 |

### リトライ仕様

- 診断取得失敗時のリトライは行わない（次回ファイルタッチ時に再試行）

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

該当なし

## パフォーマンス要件

- デバウンス時間: 150ms（セマンティック診断の遅延対応）
- タイムアウト: 3,000ms
- 診断情報の即座の利用可否に関わらず、UIをブロックしない設計

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

- 診断情報はプロジェクト内ファイルのみを対象とする
- 診断メッセージにはファイル内容の一部が含まれる可能性があるが、ローカル処理のため外部漏洩リスクなし

## 備考

診断重要度の定義（LSP仕様準拠）：
- 1: Error - コンパイルエラー、型エラー等
- 2: Warning - 潜在的な問題、非推奨API使用等
- 3: Information - 情報提供
- 4: Hint - コード改善の提案

---

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

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

### 推奨読解順序

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

診断情報の型定義とイベント定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | client.ts | `packages/opencode/src/lsp/client.ts` | Diagnostic型定義とイベント定義（23-40行目） |

**読解のコツ**: DiagnosticはVSCode LSP型（vscode-languageserver-types）を使用。range, severity, messageが主要プロパティ。

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

診断情報取得の公開APIを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/opencode/src/lsp/index.ts` | touchFile関数（277-289行目） |
| 2-2 | index.ts | `packages/opencode/src/lsp/index.ts` | diagnostics関数（291-301行目） |

**主要処理フロー**:
1. **277-278行目**: touchFile - ファイルパスのログ出力
2. **279行目**: getClientsで対応クライアント取得
3. **280-288行目**: 並列で各クライアントにオープン通知、診断待機

#### Step 3: 診断受信ハンドラ

LSPサーバーからの診断情報受信処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | client.ts | `packages/opencode/src/lsp/client.ts` | publishDiagnosticsハンドラ（52-62行目） |

**主要処理フロー**:
- **52行目**: `textDocument/publishDiagnostics`ノーティフィケーション受信
- **53行目**: URIからファイルパスに変換（Filesystem.normalizePath）
- **59行目**: 診断情報をマップに格納
- **60-61行目**: TypeScript初回診断の除外ロジック
- **61行目**: イベント発行

#### Step 4: 診断待機ロジック

デバウンス付きの診断完了待機処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | client.ts | `packages/opencode/src/lsp/client.ts` | waitForDiagnostics関数（209-237行目） |

**主要処理フロー**:
- **210-212行目**: パスの正規化
- **216-230行目**: イベントサブスクライブとプロミス作成
- **221-226行目**: 150msデバウンスタイマー
- **230行目**: 3秒タイムアウト

#### Step 5: 診断フォーマット

人間可読形式への変換。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | index.ts | `packages/opencode/src/lsp/index.ts` | Diagnostic.pretty関数（469-484行目） |

**主要処理フロー**:
- **471-476行目**: 重要度コードから文字列へのマッピング
- **478行目**: デフォルト重要度の設定（1=ERROR）
- **479-480行目**: 1-based行番号・列番号への変換
- **482行目**: フォーマット済み文字列の生成

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

```
LSP.touchFile(path, waitForDiagnostics)
    │
    ├─ getClients(path)
    │      └─ [クライアント一覧取得]
    │
    └─ Promise.all([...clients.map(...)])
           │
           ├─ [waitForDiagnostics=true]
           │      └─ client.waitForDiagnostics({path})
           │             │
           │             ├─ Bus.subscribe(Event.Diagnostics)
           │             └─ withTimeout(promise, 3000)
           │
           └─ client.notify.open({path})
                  │
                  ├─ connection.sendNotification("textDocument/didOpen")
                  │      [or "textDocument/didChange"]
                  │
                  └─ [LSPサーバーが解析]
                         │
                         ▼
                  connection.onNotification("publishDiagnostics")
                         │
                         ├─ diagnostics.set(path, diags)
                         └─ Bus.publish(Event.Diagnostics)
```

### データフロー図

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

ファイルパス ────────▶ クライアント取得 ────────▶ LSPサーバー選択
                            │
                            ▼
ファイル内容 ────────▶ didOpen/didChange送信 ──▶ LSPサーバーへ通知
                            │
                            ▼
                    [LSPサーバー解析]
                            │
                            ▼
                    publishDiagnostics受信
                            │
                            ├──────────────────▶ diagnosticsマップ更新
                            │
                            ├──────────────────▶ イベント発行
                            │
                            ▼
                    [デバウンス処理]
                            │
                            ▼
                    診断完了 ──────────────────▶ プロミス解決
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/opencode/src/lsp/index.ts` | ソース | touchFile、diagnostics、Diagnostic.pretty関数 |
| client.ts | `packages/opencode/src/lsp/client.ts` | ソース | publishDiagnosticsハンドラ、waitForDiagnostics実装 |
| write.ts | `packages/opencode/src/tool/write.ts` | ソース | ファイル書き込み後の診断トリガー |
| edit.ts | `packages/opencode/src/tool/edit.ts` | ソース | ファイル編集後の診断トリガー |
