1#![allow(dead_code)]
24
25use std::io;
26use std::path::Path;
27
28use crate::volume;
29
30const BOOTLOADER_CONTENT: &str = "\
38# CLAUDE.md
39
40> Bootloader — loads the distro's agent briefing.
41
42@.omne/dist/AGENTS.md
43";
44
45const DOCS_INDEX_BASELINE: &str = "\
49# Volume docs
50
51Index for this volume's knowledge base. Populate `raw/` with verbatim
52source material, `inter/` with intermediate synthesis, and `wiki/`
53with curated long-form notes.
54";
55
56const GITIGNORE_TEMPLATE: &str = include_str!("../templates/gitignore-template");
60
61pub fn create_volume_dirs(root: &Path) -> io::Result<()> {
67 std::fs::create_dir_all(volume::cfg_dir(root))?;
68 let docs = volume::docs_dir(root);
69 std::fs::create_dir_all(docs.join("raw"))?;
70 std::fs::create_dir_all(docs.join("inter"))?;
71 std::fs::create_dir_all(docs.join("wiki"))?;
72 std::fs::create_dir_all(volume::var_dir(root))?;
73 std::fs::create_dir_all(volume::wt_dir(root))?;
74 Ok(())
75}
76
77pub fn write_docs_baseline(root: &Path) -> io::Result<()> {
81 let index = volume::docs_baseline(root);
82 if !index.exists() {
83 std::fs::write(&index, DOCS_INDEX_BASELINE)?;
84 }
85 for subdir in ["raw", "inter", "wiki"] {
86 let keep = volume::docs_dir(root).join(subdir).join(".gitkeep");
87 if !keep.exists() {
88 std::fs::write(&keep, "")?;
89 }
90 }
91 Ok(())
92}
93
94pub fn write_bootloader(root: &Path) -> io::Result<()> {
96 std::fs::write(root.join("CLAUDE.md"), BOOTLOADER_CONTENT)
97}
98
99pub fn write_omne_readme(root: &Path, content: &str) -> io::Result<()> {
107 std::fs::write(root.join(".omne").join("omne.md"), content)
108}
109
110pub fn write_gitignore(root: &Path) -> io::Result<()> {
118 let path = root.join(".gitignore");
119 if !path.exists() {
120 return std::fs::write(&path, GITIGNORE_TEMPLATE);
121 }
122
123 let existing = std::fs::read_to_string(&path)?;
124 let required: Vec<&str> = GITIGNORE_TEMPLATE
125 .lines()
126 .map(str::trim)
127 .filter(|l| !l.is_empty() && !l.starts_with('#'))
128 .collect();
129 let missing: Vec<&str> = required
130 .into_iter()
131 .filter(|needle| !existing.lines().any(|l| l.trim() == *needle))
132 .collect();
133 if missing.is_empty() {
134 return Ok(());
135 }
136
137 let mut out = existing;
138 if !out.ends_with('\n') {
139 out.push('\n');
140 }
141 if !out.contains("# omne") {
142 out.push_str("\n# omne\n");
143 }
144 for line in missing {
145 out.push_str(line);
146 out.push('\n');
147 }
148 std::fs::write(&path, out)
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use tempfile::TempDir;
155
156 #[test]
159 fn creates_lib_and_runtime_dirs() {
160 let tmp = TempDir::new().unwrap();
161 create_volume_dirs(tmp.path()).unwrap();
162 let omne = tmp.path().join(".omne");
163 assert!(omne.join("lib").join("cfg").is_dir());
164 assert!(omne.join("lib").join("docs").join("raw").is_dir());
165 assert!(omne.join("lib").join("docs").join("inter").is_dir());
166 assert!(omne.join("lib").join("docs").join("wiki").is_dir());
167 assert!(omne.join("var").is_dir());
168 assert!(omne.join("wt").is_dir());
169 }
170
171 #[test]
172 fn does_not_create_legacy_cfg_or_log_at_root() {
173 let tmp = TempDir::new().unwrap();
174 create_volume_dirs(tmp.path()).unwrap();
175 assert!(!tmp.path().join(".omne/cfg").exists());
176 assert!(!tmp.path().join(".omne/log").exists());
177 }
178
179 #[test]
180 fn idempotent() {
181 let tmp = TempDir::new().unwrap();
182 create_volume_dirs(tmp.path()).unwrap();
183 create_volume_dirs(tmp.path()).unwrap();
184 assert!(tmp.path().join(".omne/var").is_dir());
185 }
186
187 #[test]
190 fn stamps_index_and_gitkeeps() {
191 let tmp = TempDir::new().unwrap();
192 create_volume_dirs(tmp.path()).unwrap();
193 write_docs_baseline(tmp.path()).unwrap();
194 let docs = tmp.path().join(".omne/lib/docs");
195 assert!(docs.join("index.md").is_file());
196 assert!(docs.join("raw/.gitkeep").is_file());
197 assert!(docs.join("inter/.gitkeep").is_file());
198 assert!(docs.join("wiki/.gitkeep").is_file());
199 }
200
201 #[test]
202 fn docs_baseline_preserves_existing_index() {
203 let tmp = TempDir::new().unwrap();
204 create_volume_dirs(tmp.path()).unwrap();
205 let index = tmp.path().join(".omne/lib/docs/index.md");
206 std::fs::write(&index, "user authored\n").unwrap();
207 write_docs_baseline(tmp.path()).unwrap();
208 assert_eq!(std::fs::read_to_string(&index).unwrap(), "user authored\n");
209 }
210
211 #[test]
214 fn bootloader_imports_dist_agents() {
215 let tmp = TempDir::new().unwrap();
216 write_bootloader(tmp.path()).unwrap();
217 let content = std::fs::read_to_string(tmp.path().join("CLAUDE.md")).unwrap();
218 assert!(
219 content.contains("@.omne/dist/AGENTS.md"),
220 "bootloader must import dist/AGENTS.md: {content}"
221 );
222 assert!(
223 !content.contains("MANIFEST.md"),
224 "bootloader must not retain legacy MANIFEST.md hop: {content}"
225 );
226 }
227
228 #[test]
231 fn writes_omne_readme_file() {
232 let tmp = TempDir::new().unwrap();
233 std::fs::create_dir_all(tmp.path().join(".omne")).unwrap();
234 write_omne_readme(tmp.path(), "test content").unwrap();
235 let path = tmp.path().join(".omne").join("omne.md");
236 assert!(path.is_file());
237 assert_eq!(std::fs::read_to_string(path).unwrap(), "test content");
238 }
239
240 #[test]
243 fn writes_fresh_gitignore_when_absent() {
244 let tmp = TempDir::new().unwrap();
245 write_gitignore(tmp.path()).unwrap();
246 let content = std::fs::read_to_string(tmp.path().join(".gitignore")).unwrap();
247 assert!(content.contains(".omne/wt/"));
248 assert!(content.contains(".omne/var/runs/*/nodes/"));
249 assert!(content.contains(".omne/var/.ulid-last"));
250 }
251
252 #[test]
253 fn appends_missing_entries_to_existing_gitignore() {
254 let tmp = TempDir::new().unwrap();
255 let path = tmp.path().join(".gitignore");
256 std::fs::write(&path, "target/\nnode_modules/\n").unwrap();
257 write_gitignore(tmp.path()).unwrap();
258 let content = std::fs::read_to_string(&path).unwrap();
259 assert!(content.contains("target/"), "existing entries preserved");
260 assert!(content.contains(".omne/wt/"), "new entries appended");
261 assert!(content.contains("# omne"), "section marker added");
262 }
263
264 #[test]
265 fn gitignore_is_idempotent() {
266 let tmp = TempDir::new().unwrap();
267 write_gitignore(tmp.path()).unwrap();
268 let first = std::fs::read_to_string(tmp.path().join(".gitignore")).unwrap();
269 write_gitignore(tmp.path()).unwrap();
270 let second = std::fs::read_to_string(tmp.path().join(".gitignore")).unwrap();
271 assert_eq!(first, second, "repeat invocations must not mutate");
272 }
273}