home-dir-layout修正

This commit is contained in:
Keisuke Hirata 2026-04-27 22:10:36 +09:00
parent f8fe6f83aa
commit 45ede7a6fc
9 changed files with 94 additions and 26 deletions

View File

@ -105,10 +105,12 @@ pub fn pod_runtime_dir(pod_name: &str) -> Option<PathBuf> {
Some(runtime_dir()?.join(pod_name))
}
/// `<runtime_dir>/<pod_name>/sock` — Pod の Unix socket パス (TUI が
/// attach 時に使う)。Pod プロセスが実際に socket を作成するのは
/// `RuntimeDir::socket_path()` 経由だが、外部からの予測はこの関数で
/// 行う。
/// `<runtime_dir>/<pod_name>/sock` — Pod の Unix socket パス。
///
/// Pod プロセス内で実際に socket を作成するのは `pod` crate の
/// `RuntimeDir::socket_path()` で、Pod 名が分かっている外部 (TUI の
/// attach フロー等) からの**予測**はこの関数で行う。両者は同じパス
/// を返すことが期待される。
pub fn pod_socket_path(pod_name: &str) -> Option<PathBuf> {
Some(pod_runtime_dir(pod_name)?.join("sock"))
}

View File

@ -95,10 +95,24 @@ async fn main() -> ExitCode {
}
};
// Initialize persistent store
let store_dir = cli.store.clone().unwrap_or_else(|| {
paths::sessions_dir().unwrap_or_else(|| PathBuf::from(".insomnia/sessions"))
});
// Initialize persistent store. `paths::sessions_dir()` only
// returns None when none of INSOMNIA_HOME / INSOMNIA_DATA_DIR /
// HOME is set — surface that as a hard error to match the
// runtime-dir resolution below, rather than silently writing to a
// relative path under cwd.
let store_dir = match cli.store.clone() {
Some(p) => p,
None => match paths::sessions_dir() {
Some(d) => d,
None => {
eprintln!(
"error: could not resolve sessions directory \
(set --store, INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)"
);
return ExitCode::FAILURE;
}
},
};
let store = match FsStore::new(&store_dir).await {
Ok(s) => s,
Err(e) => {

View File

@ -2,11 +2,11 @@
//!
//! Three prefixes address three physical libraries:
//!
//! | prefix | location |
//! |--------------|----------------------------------------------------|
//! | `$insomnia` | builtin, baked into the binary via `include_dir!` |
//! | `$user` | `$XDG_CONFIG_HOME/insomnia/prompts/` (or similar) |
//! | `$workspace` | `<project>/.insomnia/prompts/` |
//! | prefix | location |
//! |--------------|---------------------------------------------------------|
//! | `$insomnia` | builtin, baked into the binary via `include_dir!` |
//! | `$user` | `<config_dir>/prompts/` (resolved by `manifest::paths`) |
//! | `$workspace` | `<project>/.insomnia/prompts/` |
//!
//! A reference is `$<prefix>/<path>` where `<path>` is a `/`-separated
//! name without the `.md` extension (e.g. `$insomnia/common/header`).

View File

@ -95,7 +95,9 @@ impl RuntimeDir {
&self.path
}
/// Path where the Unix socket should be created.
/// Path where the Unix socket should be created. External callers
/// that only know the pod name (e.g. the TUI's attach flow)
/// predict the same path via [`manifest::paths::pod_socket_path`].
pub fn socket_path(&self) -> PathBuf {
self.path.join("sock")
}

View File

@ -87,14 +87,14 @@ target = "/abs/path"
permission = "write"
```
`[model]``ref = "<provider>/<model_id>"` でプロバイダ / モデルカタログを引く短縮形と、`scheme` / `model_id` / `auth` を直書きする inline 形式の両方を受ける。カタログは `resources/{providers,models}/builtin.toml` を builtin、`$XDG_CONFIG_HOME/insomnia/{providers,models}.toml` を user override として解決する。詳細は `docs/pod-factory.md``crates/provider/README.md`
`[model]``ref = "<provider>/<model_id>"` でプロバイダ / モデルカタログを引く短縮形と、`scheme` / `model_id` / `auth` を直書きする inline 形式の両方を受ける。カタログは `resources/{providers,models}/builtin.toml` を builtin、`<config_dir>/{providers,models}.toml` を user override として解決する`<config_dir>` の解決ルールは `manifest::paths` 参照)。詳細は `docs/pod-factory.md``crates/provider/README.md`
### PodFactory: カスケード設定
マニフェストを手書きせず、4 層のカスケードで `PodManifest` を組み立てる:
1. **ビルトインデフォルト**`manifest::defaults` の定数値
2. **ユーザー manifest**`$XDG_CONFIG_HOME/insomnia/manifest.toml`
2. **ユーザー manifest**`<config_dir>/manifest.toml``manifest::paths` で解決)
3. **プロジェクト manifest**`.insomnia/manifest.toml`cwd から上方向に探索)
4. **プログラマティック overlay** — CLI / GUI / spawn 時のインライン指定
@ -105,7 +105,7 @@ permission = "write"
`worker.instruction` はファイル参照。3 層の prefix addressing でプロンプト資産を解決:
- `$insomnia/...` — バイナリ同梱(`resources/prompts/`、`include_dir!` で埋め込み)
- `$user/...``$XDG_CONFIG_HOME/insomnia/prompts/`
- `$user/...``<config_dir>/prompts/``manifest::paths` で解決)
- `$workspace/...``<project>/.insomnia/prompts/`
テンプレートは minijinja で評価。`{% include "$insomnia/common/tool-usage" %}` のようにプロンプト間で参照可能prefix なしの include は現在のファイルからの相対解決)。

View File

@ -44,7 +44,7 @@ Ollama は独自 scheme を作らず `scheme/anthropic` を base_url 差し替
## 実装原則
- 認証ストアを読むアダプタ(`~/.codex/auth.json` 等)は **llm-worker 直下に置かず上位層に配置**。llm-worker は低レベル基盤に留める方針(`feedback_llm_worker_scope.md`)と整合
- モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠はモデルカタログ(`resources/models/builtin.toml` + `$XDG_CONFIG_HOME/insomnia/models.toml` の user override)で宣言
- モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠はモデルカタログ(`resources/models/builtin.toml` + `<config_dir>/models.toml` の user override、`<config_dir>` は `manifest::paths` で解決)で宣言
- UI のプロバイダ選択肢も第一級 → 二次の優先順位で並べる
- **`ollama launch insomnia` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り

View File

@ -13,7 +13,7 @@ overlay をマージして、検証済みの `PodManifest` と `PromptLoader`
| 優先度 | 層 | 位置 | 典型的な内容 |
|---|---|---|---|
| 1 | ビルトインのデフォルト | `manifest::defaults` モジュールの `pub const` 群を `PodManifestConfig::builtin_defaults()` が cascade 層として注入 | `tool_output.default_max_bytes = 16KB` など |
| 2 | ユーザー manifest | `$XDG_CONFIG_HOME/insomnia/manifest.toml`(未設定時は `~/.config/insomnia/manifest.toml` | プロバイダ指定、デフォルトモデル、常用ツール設定 |
| 2 | ユーザー manifest | `<config_dir>/manifest.toml`(解決ルールは `manifest::paths` | プロバイダ指定、デフォルトモデル、常用ツール設定 |
| 3 | プロジェクト manifest | 起動ディレクトリから上方向に探索した最初の `<root>/.insomnia/manifest.toml` | scope、compaction、プロジェクト固有の instruction |
| 4 | プログラマティック overlay | CLI / GUI / 別 Pod からの spawn 等 | `pod.name`、`pod.pwd` のような Pod 固有値 |
@ -48,7 +48,7 @@ manifest 中のパス(`provider.api_key_file` / `scope.*.target` /
| 層 | ベース |
|---|---|
| user manifest (`~/.config/insomnia/manifest.toml`) | そのファイルの親ディレクトリ |
| user manifest (`<config_dir>/manifest.toml`) | そのファイルの親ディレクトリ |
| project manifest (`<project>/.insomnia/manifest.toml`) | **プロジェクトルート**`.insomnia/` の親)。`target = "."` がワークスペース全体を指すように |
| overlayinline TOML・programmatic | プロセスの `current_dir()` |
@ -76,7 +76,7 @@ resolve 段を取りこぼしている証拠なので `ResolveError::RelativePat
### ユーザー層(最小)
`$XDG_CONFIG_HOME/insomnia/manifest.toml`:
`<config_dir>/manifest.toml`:
```toml
[model]
@ -180,7 +180,7 @@ import-map 形式のプレフィックスで指定する:
| プレフィックス | 解決先 |
|---|---|
| `$insomnia` | バイナリ同梱の `resources/prompts/``include_dir!` |
| `$user` | `$XDG_CONFIG_HOME/insomnia/prompts/` |
| `$user` | `<config_dir>/prompts/``manifest::paths` で解決) |
| `$workspace` | `<project>/.insomnia/prompts/` |
- `.md` 拡張子は省略する(例: `$insomnia/default``resources/prompts/default.md`
@ -238,15 +238,15 @@ pod [--user-manifest <path>] [--project <path>] [--overlay <toml>]
| フラグ | 説明 |
|---|---|
| `--user-manifest` | ユーザー manifest のパス。省略時は XDG から自動解決 |
| `--user-manifest` | ユーザー manifest のパス。省略時は `manifest::paths::user_manifest_path()`自動解決 |
| `--project` | プロジェクト manifest 探索の起点。省略時は cwd から上方向に `.insomnia/` を探索 |
| `--overlay` | 最上層の overlay を inline TOML 文字列で渡す(例: `--overlay 'worker.instruction = "$user/foo"'` |
| `-s, --store` | セッション永続化ディレクトリ(デフォルト: `~/.insomnia/sessions/` |
| `-s, --store` | セッション永続化ディレクトリ(デフォルト: `<data_dir>/sessions/`、`manifest::paths` で解決 |
Pod の作業ディレクトリは `pod` 起動時の cwd が直接使われる。別ディレクトリで
動かしたい場合は `cd <path> && pod ...` のように外側で `cd` してから起動する。
引数無しで起動すると、cwd + XDG の自動解決だけで動く最小構成になる
引数無しで起動すると、cwd + `manifest::paths` の自動解決だけで動く最小構成になる
overlay 無し、プロジェクトに `.insomnia/manifest.toml` があればそれを使う)。
---
@ -257,7 +257,7 @@ Pod の作業ディレクトリは `pod` 起動時の cwd が直接使われる
use pod::{Pod, PodFactory};
let (manifest, loader) = PodFactory::new()
.with_user_manifest_auto()? // XDG から自動読み込み、不在 OK
.with_user_manifest_auto()? // manifest::paths から自動読み込み、不在 OK
.with_project_manifest_auto()? // cwd から上方向に .insomnia/ を探索、不在 OK
.with_overlay_toml(overlay)? // programmatic な最上層 overlay
.resolve()?; // -> (PodManifest, PromptLoader)

View File

@ -105,3 +105,9 @@ $XDG_CONFIG_HOME/insomnia/ # 人が編集する設定 (fallback ~/.config/ins
## 後続チケット
- `tickets/tui-user-model-setup.md`: 本チケットで確定したレイアウトに従って user manifest を書き込む wizard を実装する
## Review
- 状態: Approve
- レビュー詳細: [./home-dir-layout.review.md](./home-dir-layout.review.md)
- 日付: 2026-04-27

View File

@ -0,0 +1,44 @@
# Review: ホームディレクトリ配下のディレクトリ整理
レビュー対象: HEAD (`915061f home-dirの整理`)。
## 前提・要件の確認
- **paths.rs に config/data/runtime の責務と解決ロジックが集約され、module コメントで配置が明記**: 満たされている。`crates/manifest/src/paths.rs:1-25` に三つのベースディレクトリの責務、解決順マトリクス、`INSOMNIA_HOME` 設定時の `$X/config` `$X` `$X/run` 各サブツリーへの集約が表で示されている。`config_dir` / `data_dir` / `runtime_dir` と well-known file getter (`user_manifest_path`, `user_prompts_dir`, `user_pack_file`, `user_catalog_override`, `sessions_dir`, `scope_lock_path`, `pod_runtime_dir`, `pod_socket_path`) が揃っている。
- **既存 11 箇所の置換 / `default_runtime_dir` と `default_base` の重複解消**: 満たされている。
- `pod/src/main.rs:99-101` `paths::sessions_dir()`、`pod/src/main.rs:137-146` `paths::runtime_dir()` で旧 `default_store_dir` / `default_runtime_dir` を完全削除 (`git diff HEAD~1 HEAD -- crates/pod/src/main.rs` で確認済み)。
- `pod/src/runtime/dir.rs:122-130` `default_base``paths::runtime_dir()` の薄いラッパに変わり、`main.rs` との重複が解消されている。
- `pod/src/runtime/scope_lock.rs:69-77` `default_lock_path``paths::scope_lock_path()` のラッパ。
- `pod/src/factory.rs:111-118``paths::user_manifest_path` / `user_prompts_dir` / `user_pack_file` を直接利用。
- `tui/src/main.rs:32-38` `resolve_socket``paths::pod_socket_path()` 経由。
- `provider/src/catalog.rs:160,193``paths::user_catalog_override` を利用。
- `manifest/src/cascade.rs` から `user_manifest_path` 削除、`manifest/src/lib.rs:9` で `pub use paths::user_manifest_path` の互換 re-export。
- **`INSOMNIA_HOME` / `INSOMNIA_CONFIG_DIR` / `INSOMNIA_DATA_DIR` / `INSOMNIA_RUNTIME_DIR` の優先順位**: `paths.rs:30-68` の各関数と `paths.rs:199-292` のテストで `INSOMNIA_<KIND>_DIR > INSOMNIA_HOME > XDG_* > 既定` の順位が網羅的にカバーされている (`config_dir_explicit_wins_over_insomnia_home`, `config_dir_insomnia_home_outranks_xdg`, `runtime_dir_insomnia_home_is_run_subdir`, etc.)。
- **`INSOMNIA_SCOPE_LOCK` の廃止**: 完了。production / test 両方の grep で 0 ヒット (`tickets/home-dir-layout.md` 内の歴史的記述のみ残る)。`scope_lock.rs:545-1126` のテストは `RuntimeDirSandbox` (`INSOMNIA_RUNTIME_DIR` + `INSOMNIA_HOME` / `XDG_RUNTIME_DIR` の退避) に置換済み。
- **`unsafe std::env::set_var` を使うテスト箇所が最小限**: 妥当な水準まで縮小。`paths.rs` 12 件・`scope_lock.rs` 3 ブロック (set/remove)・`catalog.rs` 2 件・統合テスト 3 本 (各 EnvGuard + 個別 set_var) と、いずれも env override の RAII guard か serial gate を経由しており、共有ヘルパに収斂されている。元の `scope_lock.rs:980` のような scope_lock 直書きは消えた。env を弄らないと `INSOMNIA_RUNTIME_DIR` 等の優先順位を検証できないため「最小限」の解釈として妥当。
- **pod 起動 / TUI spawn flow が動作**: `cargo build --workspace` / `cargo test --workspace` 通過の旨をユーザー側で確認済みと申告 (本レビューでは再実行せず)。コードパスは旧実装と論理同型のラッパに置換されているため、機能後退の余地は小さい。
## アーキテクチャ・スコープ
- 集約先の選定 (新 crate を作らず `manifest::paths`) は memory `feedback_llm_worker_scope` / `feedback_crate_naming` の方針に整合。`manifest` crate は既に provider / pod / tui から依存されており、追加の依存グラフ歪みが発生していない。
- API 形状は **「ベース 3 つ + well-known ファイルの getter」** の薄い層に留めており、I/O やディレクトリ作成・存在検査を `paths` 側で握り込んでいない。実際の作成・検査は呼び出し側 (`scope_lock.rs:101-122` `LockFileGuard::open`, `RuntimeDir::create_default`, etc.) に残っているので、テストでの差し替えやサンドボックス運用が壊れない。
- `INSOMNIA_HOME` のレイアウトマッピング (`config = $X/config`, `data = $X` 直, `runtime = $X/run`) は、案 C (XDG_CONFIG_HOME 尊重 + `~/.insomnia/` を data/runtime) と整合。テスト用途では「単一 tempdir に三系統が同居する」直感も保たれている。
- 範囲外 (Windows / `XDG_DATA_HOME` / `XDG_STATE_HOME`) には手を出していない。YAGNI を守れている。
- `manifest/src/lib.rs:9``pub use paths::user_manifest_path` は、cascade.rs から関数を消した互換維持で、ext crate の API 壊しを避けている。狙いは合理的。
## 指摘事項
### Non-blocking / Follow-up
- ドキュメント未追従 (本チケットの完了条件外だが、後続のために notes): `docs/architecture.md:90,97,108,122`、`docs/pod-factory.md:16,79,183`、`docs/plan/llm_providers.md:47`、`crates/pod/src/prompt/loader.rs:8` が `$XDG_CONFIG_HOME/insomnia/...` / `$XDG_RUNTIME_DIR/insomnia/scope.lock` を直接書いており、`INSOMNIA_HOME` / `INSOMNIA_CONFIG_DIR` 系の override に触れていない。誤りではない (その経路は今も既定として有効) が、後続の `tickets/tui-user-model-setup.md` で wizard が「どこに書くか」を表示する際、ドキュメント側の表記も `manifest::paths` を起点にする方が一貫する。本チケットでは敢えて手を入れていないが、次にこの周辺を触る時にまとめて差し替えるのが自然。
- `paths::data_dir` は env が完全に何も無い場合でも `HOME` だけは要求する設計だが、`INSOMNIA_DATA_DIR` 単独設定で `HOME` 不在のケースはちゃんと拾う (`paths.rs:46-52`)。一方 `data_dir` には `XDG_DATA_HOME` のフォールスルーが無く、ticket の方針 (`$XDG_DATA_HOME` 非対応) どおりだが、将来同変数を採用する際の差分位置は `data_dir` の 2 行目になる旨をコメントしておくと意図が伝わりやすい。
- `paths::pod_socket_path` の doc コメント (`paths.rs:108-113`) は「Pod プロセスは `RuntimeDir::socket_path()` 経由で作成する」と外部からの予測 API である旨を明示しており良い。一方 `RuntimeDir::socket_path()` 側 (`crates/pod/src/runtime/dir.rs:99-101`) には逆参照が無いため、相互コメントを足すと将来の混乱予防になる (Nit に近い)。
### Nits
- `paths.rs:121-126``env_path` ヘルパは `std::env::var(name).ok().filter(...)` で、`Err(NotPresent)` と `Err(NotUnicode)` を共に未設定として扱う。後者は実用上ほぼ起きないが、tracing で warn を出す価値はある — ただし本チケットの責務外なので強制ではない。
- `pod/src/main.rs:100``unwrap_or_else(|| PathBuf::from(".insomnia/sessions"))` フォールバック (env 全滅時の相対パス退避) は、後続の `FsStore::new` で cwd 起点の相対書き出しになるため挙動が読みにくい。`runtime_dir` 側 (`main.rs:137-146`) のように early-error にするか、コメントで意図 (どうせ HOME も無い極端な構成: テスト harness のごく一部) を一行入れると良い。実害は無いので Nit。
## 判断
**Approve** — チケットの 6 つの完了条件すべてに対して具体的な根拠が確認でき、コードベースの歪みも観測されない。`paths` モジュールは責務分離・優先順位・テストカバレッジの三点で過剰でも不足でもなく、後続の `tui-user-model-setup` 等が乗ってくる土台として妥当。ドキュメント追従は本チケットの責務外として Follow-up に分類。