1use std::path::PathBuf;
23
24pub fn innate_home() -> PathBuf {
26 dirs_next::home_dir()
27 .unwrap_or_else(|| PathBuf::from("."))
28 .join(".innate")
29}
30
31pub fn data_dir() -> PathBuf {
33 innate_home().join("data")
34}
35
36pub fn logs_dir() -> PathBuf {
38 innate_home().join("logs")
39}
40
41pub fn sessions_dir() -> PathBuf {
43 innate_home().join("sessions")
44}
45
46pub fn settings_path() -> PathBuf {
47 innate_home().join("settings.json")
48}
49
50pub fn default_db_path() -> PathBuf {
51 data_dir().join("personal.db")
52}
53
54pub fn daemon_pid_path() -> PathBuf {
55 data_dir().join("daemon.pid")
56}
57
58pub fn daemon_state_path() -> PathBuf {
59 data_dir().join("daemon_state.sqlite")
60}
61
62pub fn daemon_log_path() -> PathBuf {
63 logs_dir().join("daemon.log")
64}
65
66pub fn mcp_log_path() -> PathBuf {
67 logs_dir().join("mcp.log")
68}
69
70pub fn llm_trace_path() -> PathBuf {
73 logs_dir().join("llm_trace.log")
74}
75
76pub fn session_log_path() -> PathBuf {
77 sessions_dir().join("session.log")
78}
79
80pub fn backup_state_path() -> PathBuf {
82 data_dir().join("backup_state.json")
83}
84
85pub fn tmp_dir() -> PathBuf {
87 data_dir().join("tmp")
88}
89
90pub fn ensure_layout() {
94 ensure_layout_at(&innate_home());
95}
96
97pub fn ensure_layout_at(home: &std::path::Path) {
101 let data = home.join("data");
102 let logs = home.join("logs");
103 for dir in [&data, &logs, &home.join("sessions")] {
104 let _ = std::fs::create_dir_all(dir);
105 }
106
107 let moves: [(PathBuf, PathBuf); 8] = [
108 (home.join("personal.db"), data.join("personal.db")),
109 (home.join("personal.db-shm"), data.join("personal.db-shm")),
110 (home.join("personal.db-wal"), data.join("personal.db-wal")),
111 (
112 home.join("daemon_state.sqlite"),
113 data.join("daemon_state.sqlite"),
114 ),
115 (home.join("daemon.pid"), data.join("daemon.pid")),
116 (
117 home.join("backup_state.json"),
118 data.join("backup_state.json"),
119 ),
120 (home.join("daemon.log"), logs.join("daemon.log")),
121 (home.join("mcp.log"), logs.join("mcp.log")),
122 ];
123 for (legacy, target) in moves {
124 if legacy.exists() && !target.exists() {
125 let _ = std::fs::rename(&legacy, &target);
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn ensure_layout_migrates_legacy_flat_files() {
136 let tmp = tempfile::tempdir().unwrap();
137 let home = tmp.path();
138 for f in [
140 "personal.db",
141 "personal.db-wal",
142 "daemon_state.sqlite",
143 "daemon.pid",
144 "daemon.log",
145 "mcp.log",
146 ] {
147 std::fs::write(home.join(f), b"x").unwrap();
148 }
149
150 ensure_layout_at(home);
151
152 assert!(home.join("data").is_dir());
154 assert!(home.join("logs").is_dir());
155 assert!(home.join("sessions").is_dir());
156 assert!(home.join("data/personal.db").exists());
158 assert!(home.join("data/personal.db-wal").exists());
159 assert!(home.join("data/daemon_state.sqlite").exists());
160 assert!(home.join("data/daemon.pid").exists());
161 assert!(home.join("logs/daemon.log").exists());
162 assert!(home.join("logs/mcp.log").exists());
163 assert!(!home.join("personal.db").exists());
164 assert!(!home.join("mcp.log").exists());
165
166 ensure_layout_at(home);
168 assert!(home.join("data/personal.db").exists());
169 }
170}