diff --git a/crates/manifest/src/paths.rs b/crates/manifest/src/paths.rs index 37fa5241..46107f47 100644 --- a/crates/manifest/src/paths.rs +++ b/crates/manifest/src/paths.rs @@ -105,10 +105,12 @@ pub fn pod_runtime_dir(pod_name: &str) -> Option { Some(runtime_dir()?.join(pod_name)) } -/// `//sock` — Pod の Unix socket パス (TUI が -/// attach 時に使う)。Pod プロセスが実際に socket を作成するのは -/// `RuntimeDir::socket_path()` 経由だが、外部からの予測はこの関数で -/// 行う。 +/// `//sock` — Pod の Unix socket パス。 +/// +/// Pod プロセス内で実際に socket を作成するのは `pod` crate の +/// `RuntimeDir::socket_path()` で、Pod 名が分かっている外部 (TUI の +/// attach フロー等) からの**予測**はこの関数で行う。両者は同じパス +/// を返すことが期待される。 pub fn pod_socket_path(pod_name: &str) -> Option { Some(pod_runtime_dir(pod_name)?.join("sock")) } diff --git a/crates/pod/src/main.rs b/crates/pod/src/main.rs index d04079dc..4fea6215 100644 --- a/crates/pod/src/main.rs +++ b/crates/pod/src/main.rs @@ -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) => { diff --git a/crates/pod/src/prompt/loader.rs b/crates/pod/src/prompt/loader.rs index 7f956e13..256420c5 100644 --- a/crates/pod/src/prompt/loader.rs +++ b/crates/pod/src/prompt/loader.rs @@ -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` | `/.insomnia/prompts/` | +//! | prefix | location | +//! |--------------|---------------------------------------------------------| +//! | `$insomnia` | builtin, baked into the binary via `include_dir!` | +//! | `$user` | `/prompts/` (resolved by `manifest::paths`) | +//! | `$workspace` | `/.insomnia/prompts/` | //! //! A reference is `$/` where `` is a `/`-separated //! name without the `.md` extension (e.g. `$insomnia/common/header`). diff --git a/crates/pod/src/runtime/dir.rs b/crates/pod/src/runtime/dir.rs index 1f442fe4..7cc49a0e 100644 --- a/crates/pod/src/runtime/dir.rs +++ b/crates/pod/src/runtime/dir.rs @@ -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") } diff --git a/docs/architecture.md b/docs/architecture.md index e140bf87..d785b440 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -87,14 +87,14 @@ target = "/abs/path" permission = "write" ``` -`[model]` は `ref = "/"` でプロバイダ / モデルカタログを引く短縮形と、`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 = "/"` でプロバイダ / モデルカタログを引く短縮形と、`scheme` / `model_id` / `auth` を直書きする inline 形式の両方を受ける。カタログは `resources/{providers,models}/builtin.toml` を builtin、`/{providers,models}.toml` を user override として解決する(`` の解決ルールは `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** — `/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/...` — `/prompts/`(`manifest::paths` で解決) - `$workspace/...` — `/.insomnia/prompts/` テンプレートは minijinja で評価。`{% include "$insomnia/common/tool-usage" %}` のようにプロンプト間で参照可能(prefix なしの include は現在のファイルからの相対解決)。 diff --git a/docs/plan/llm_providers.md b/docs/plan/llm_providers.md index 45bdc9b2..91ea96d1 100644 --- a/docs/plan/llm_providers.md +++ b/docs/plan/llm_providers.md @@ -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` + `/models.toml` の user override、`` は `manifest::paths` で解決)で宣言 - UI のプロバイダ選択肢も第一級 → 二次の優先順位で並べる - **`ollama launch insomnia` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り diff --git a/docs/pod-factory.md b/docs/pod-factory.md index 44c01bd1..2a55ef3b 100644 --- a/docs/pod-factory.md +++ b/docs/pod-factory.md @@ -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 | `/manifest.toml`(解決ルールは `manifest::paths`) | プロバイダ指定、デフォルトモデル、常用ツール設定 | | 3 | プロジェクト manifest | 起動ディレクトリから上方向に探索した最初の `/.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 (`/manifest.toml`) | そのファイルの親ディレクトリ | | project manifest (`/.insomnia/manifest.toml`) | **プロジェクトルート**(`.insomnia/` の親)。`target = "."` がワークスペース全体を指すように | | overlay(inline TOML・programmatic) | プロセスの `current_dir()` | @@ -76,7 +76,7 @@ resolve 段を取りこぼしている証拠なので `ResolveError::RelativePat ### ユーザー層(最小) -`$XDG_CONFIG_HOME/insomnia/manifest.toml`: +`/manifest.toml`: ```toml [model] @@ -180,7 +180,7 @@ import-map 形式のプレフィックスで指定する: | プレフィックス | 解決先 | |---|---| | `$insomnia` | バイナリ同梱の `resources/prompts/`(`include_dir!`) | -| `$user` | `$XDG_CONFIG_HOME/insomnia/prompts/` | +| `$user` | `/prompts/`(`manifest::paths` で解決) | | `$workspace` | `/.insomnia/prompts/` | - `.md` 拡張子は省略する(例: `$insomnia/default` → `resources/prompts/default.md`) @@ -238,15 +238,15 @@ pod [--user-manifest ] [--project ] [--overlay ] | フラグ | 説明 | |---|---| -| `--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` | セッション永続化ディレクトリ(デフォルト: `/sessions/`、`manifest::paths` で解決) | Pod の作業ディレクトリは `pod` 起動時の cwd が直接使われる。別ディレクトリで 動かしたい場合は `cd && 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) diff --git a/tickets/home-dir-layout.md b/tickets/home-dir-layout.md index 5d1d9bec..7fe577da 100644 --- a/tickets/home-dir-layout.md +++ b/tickets/home-dir-layout.md @@ -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 diff --git a/tickets/home-dir-layout.review.md b/tickets/home-dir-layout.review.md new file mode 100644 index 00000000..a1f1b846 --- /dev/null +++ b/tickets/home-dir-layout.review.md @@ -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__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 に分類。