# 機能設計書 49-プロジェクト検出

## 概要

本ドキュメントは、OpenCodeにおけるプロジェクト検出機能の設計を記述する。Gitリポジトリルートの自動検出とプロジェクト識別を行い、セッションやスナップショットの管理単位を決定する機能を提供する。

### 本機能の処理概要

プロジェクト検出機能は、指定されたディレクトリから上位に向かって.gitディレクトリを検索し、Gitリポジトリのルートを特定する。ルートコミットのハッシュからプロジェクトIDを生成し、同一リポジトリを一意に識別する。また、git worktreeやサブモジュールの構成にも対応し、正確なワークツリー境界を判定する。

**業務上の目的・背景**：OpenCodeはセッションやスナップショットをプロジェクト単位で管理する。プロジェクト検出により、異なるディレクトリから起動しても同一プロジェクトとして認識され、セッション履歴が共有される。また、git worktreeを使用した複数作業ディレクトリでの開発もサポートされ、チーム開発での一貫した体験を提供する。

**機能の利用シーン**：
- OpenCode起動時のプロジェクト自動認識
- セッションのプロジェクト間分離
- スナップショットの保存先決定
- 設定ファイルの検索範囲決定

**主要な処理内容**：
1. .gitディレクトリの検索（上方向へのfindUp）
2. GitリポジトリのルートコミットからプロジェクトID生成
3. git worktree/サブモジュール対応のワークツリー境界判定
4. プロジェクト情報のデータベース保存
5. 既存セッションのプロジェクトマイグレーション

**関連システム・外部連携**：
- Git: バージョン管理システム
- SQLite: プロジェクト情報の永続化
- Filesystem: ディレクトリ検索

**権限による制御**：特になし（ファイルシステムアクセス権限に依存）。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | ホーム画面 | 主機能 | 最近開いたプロジェクト一覧の表示、プロジェクトディレクトリの自動検出 |
| 5 | ディレクトリレイアウト | 主機能 | ディレクトリパラメータからのプロジェクト識別 |

## 機能種別

検出処理 / データ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| directory | string | Yes | 起点ディレクトリ | 有効なパス |

### 入力データソース

- ファイルシステム（.gitディレクトリ検索）
- Gitコマンド出力
- データベース（既存プロジェクト情報）

## 出力仕様

### 出力データ（Project.Info型）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | プロジェクトID（ルートコミットハッシュ） |
| worktree | string | ワークツリールートパス |
| vcs | "git" \| undefined | バージョン管理システム種別 |
| name | string \| undefined | プロジェクト名 |
| icon | object \| undefined | アイコン情報 |
| time | object | 作成・更新時刻 |
| sandboxes | string[] | 関連サンドボックスパス |

### 出力先

- メモリ（Instance.project）
- データベース（ProjectTable）

## 処理フロー

### 処理シーケンス

```
1. .git ディレクトリ検索
   └─ Filesystem.up で上方向に検索

2. Git情報取得（.git発見時）
   ├─ キャッシュID確認（.git/opencode）
   ├─ ルートコミット取得（rev-list --max-parents=0 --all）
   ├─ show-toplevel でワークツリールート取得
   └─ git-common-dir で共通Gitディレクトリ取得

3. プロジェクト情報構築
   ├─ 既存レコード確認（Database SELECT）
   ├─ 新規の場合は初期データ作成
   └─ グローバルからのマイグレーション実行

4. データベース保存
   ├─ INSERT or UPDATE（upsert）
   └─ イベント発行（project.updated）

5. コンテキスト返却
   └─ { project, sandbox } を返却
```

### フローチャート

```mermaid
flowchart TD
    A[fromDirectory開始] --> B[.git検索]
    B --> C{.git発見?}
    C -->|No| D[グローバルプロジェクト返却]
    C -->|Yes| E[キャッシュID確認]
    E --> F{キャッシュあり?}
    F -->|Yes| G[キャッシュID使用]
    F -->|No| H[rev-list実行]
    H --> I{ルートコミット取得成功?}
    I -->|No| D
    I -->|Yes| J[IDキャッシュ保存]
    J --> K[show-toplevel実行]
    K --> L[git-common-dir実行]
    L --> M[既存レコード確認]
    M --> N{既存あり?}
    N -->|Yes| O[既存データ使用]
    N -->|No| P[新規データ作成]
    P --> Q[マイグレーション実行]
    O --> R[Database upsert]
    Q --> R
    R --> S[イベント発行]
    S --> T[コンテキスト返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-49-01 | グローバルプロジェクト | Gitリポジトリ外は id="global", worktree="/" | .git未発見時 |
| BR-49-02 | IDキャッシュ | プロジェクトIDを .git/opencode にキャッシュ | ID生成時 |
| BR-49-03 | サンドボックス管理 | worktree以外のパスはsandboxesに追加 | worktree != sandbox時 |
| BR-49-04 | マイグレーション | グローバルからの既存セッションを新プロジェクトに移行 | 新規プロジェクト作成時 |
| BR-49-05 | Windowsパス正規化 | Filesystem.windowsPath でパス正規化 | パス処理時 |

### 計算ロジック

プロジェクトID生成：
```typescript
// ルートコミットのソート済み配列の最初の要素を使用
const roots = await git(["rev-list", "--max-parents=0", "--all"])
  .then(result => result.text().split("\n").filter(Boolean).map(x => x.trim()).toSorted())
const id = roots[0]
```

ワークツリールート取得：
```typescript
const top = await git(["rev-parse", "--show-toplevel"])
const worktree = await git(["rev-parse", "--git-common-dir"])
  .then(result => path.dirname(result))
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| プロジェクト保存 | ProjectTable | UPSERT | プロジェクト情報の保存/更新 |
| マイグレーション | SessionTable | UPDATE | グローバルからのセッション移行 |

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

#### ProjectTable

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPSERT | id | プロジェクトID | 主キー |
| UPSERT | worktree | ワークツリーパス | - |
| UPSERT | vcs | "git" or null | - |
| UPSERT | name | プロジェクト名 | - |
| UPSERT | sandboxes | サンドボックス配列 | JSON配列 |
| UPSERT | time_created | 作成時刻 | - |
| UPSERT | time_updated | 更新時刻 | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Git未インストール | Bun.which("git") が null | グローバルプロジェクトとして扱う |
| - | Gitコマンド失敗 | rev-list等が失敗 | グローバルプロジェクトとして扱う |

### リトライ仕様

リトライは実装されていない。エラー発生時はグローバルプロジェクトにフォールバック。

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

データベース操作は個別に実行。マイグレーションは並列処理（work関数）で実行。

## パフォーマンス要件

- プロジェクトIDはファイルキャッシュで高速化
- Instance.provide でプロジェクト情報をキャッシュ
- マイグレーションは最大10並列で実行

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

- ファイルシステムアクセスはプロセス権限に依存
- プロジェクトIDはGitハッシュ（予測困難）

## 備考

- OPENCODE_FAKE_VCS フラグでVCS種別を偽装可能（テスト用）
- OPENCODE_EXPERIMENTAL_ICON_DISCOVERY でアイコン自動検出

---

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

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

### 推奨読解順序

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

プロジェクト情報の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | project.ts | `packages/opencode/src/project/project.ts` | Info型（33-61行目）を確認 |
| 1-2 | project.sql.ts | `packages/opencode/src/project/project.sql.ts` | ProjectTableスキーマ確認 |

**読解のコツ**: Info型のworktreeは「共通ルート」、sandboxesは「git worktreeのパス一覧」。

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

プロジェクト検出のメインフローを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | project.ts | `packages/opencode/src/project/project.ts` | fromDirectory関数（90-273行目） |

**主要処理フロー**:
1. **93-204行目**: Git情報取得（iife内）
2. **94-96行目**: .git検索
3. **103-106行目**: キャッシュID確認
4. **117-143行目**: ルートコミットからID生成
5. **154-158行目**: show-toplevel
6. **170-179行目**: git-common-dir
7. **206-223行目**: 既存レコード確認とマイグレーション

#### Step 3: インスタンス管理を理解する

Instance経由のプロジェクト利用を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | instance.ts | `packages/opencode/src/project/instance.ts` | provide関数（22-44行目） |
| 3-2 | instance.ts | `packages/opencode/src/project/instance.ts` | projectゲッター（52-53行目） |

**主要処理フロー**:
- **22-44行目**: プロジェクト検出とコンテキスト設定
- **27行目**: Project.fromDirectory 呼び出し
- **33-35行目**: init関数実行（InstanceBootstrap）

#### Step 4: マイグレーションを理解する

グローバルからのセッション移行を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | project.ts | `packages/opencode/src/project/project.ts` | migrateFromGlobal関数（299-319行目） |

**主要処理フロー**:
- **300-301行目**: グローバルプロジェクト確認
- **303-306行目**: グローバルのセッション取得
- **310-315行目**: work関数で並列移行

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

```
Instance.provide({ directory })
    │
    └─ Project.fromDirectory(directory)
           │
           ├─ Filesystem.up({ targets: [".git"] })
           │
           ├─ git(["rev-list", "--max-parents=0", "--all"])
           │
           ├─ git(["rev-parse", "--show-toplevel"])
           │
           ├─ git(["rev-parse", "--git-common-dir"])
           │
           ├─ Database.use(db => db.select().from(ProjectTable))
           │
           ├─ migrateFromGlobal(id, worktree)
           │      │
           │      └─ work(10, sessions, async (row) => {...})
           │
           └─ Database.use(db => db.insert(ProjectTable).onConflictDoUpdate(...))
```

### データフロー図

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

directory
       │
       ▼
Filesystem.up() ───▶ .gitパス
       │
       ▼
git rev-list ───▶ プロジェクトID
       │
       ▼
git rev-parse ───▶ worktree, sandbox
       │
       ▼
Database SELECT ───▶ 既存プロジェクト
       │
       ▼
Database UPSERT ───▶ ProjectTable
       │
       ▼
GlobalBus.emit ───▶ project.updated イベント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| project.ts | `packages/opencode/src/project/project.ts` | ソース | プロジェクト検出・管理 |
| instance.ts | `packages/opencode/src/project/instance.ts` | ソース | インスタンスコンテキスト管理 |
| project.sql.ts | `packages/opencode/src/project/project.sql.ts` | ソース | ProjectTableスキーマ |
| filesystem.ts | `packages/opencode/src/util/filesystem.ts` | ソース | findUp等のファイル検索 |
| git.ts | `packages/opencode/src/util/git.ts` | ソース | Gitコマンドラッパー |
