Skip to main content

lean_ctx/core/gotcha_tracker/
persist.rs

1use super::model::{Gotcha, GotchaStore};
2use std::path::PathBuf;
3
4impl GotchaStore {
5    pub fn load(project_root: &str) -> Self {
6        let hash = crate::core::project_hash::hash_project_root(project_root);
7        let path = gotcha_path(&hash);
8        if let Ok(content) = std::fs::read_to_string(&path) {
9            if let Ok(mut store) = serde_json::from_str::<GotchaStore>(&content) {
10                store.apply_decay();
11                store.pending_errors = Vec::new();
12                return store;
13            }
14        }
15        Self::new(&hash)
16    }
17
18    pub fn save(&self, project_root: &str) -> Result<(), String> {
19        let hash = crate::core::project_hash::hash_project_root(project_root);
20        let path = gotcha_path(&hash);
21        if let Some(parent) = path.parent() {
22            std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
23        }
24        let tmp = path.with_extension("tmp");
25        let json = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?;
26        std::fs::write(&tmp, &json).map_err(|e| e.to_string())?;
27        std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
28        Ok(())
29    }
30}
31
32fn gotcha_path(project_hash: &str) -> PathBuf {
33    crate::core::data_dir::lean_ctx_data_dir()
34        .unwrap_or_else(|_| PathBuf::from("."))
35        .join("knowledge")
36        .join(project_hash)
37        .join("gotchas.json")
38}
39
40// ---------------------------------------------------------------------------
41// Universal gotchas (cross-project)
42// ---------------------------------------------------------------------------
43
44pub fn load_universal_gotchas() -> Vec<Gotcha> {
45    let policy = crate::core::memory_boundary::BoundaryPolicy::default();
46    if !policy.universal_gotchas_enabled {
47        return Vec::new();
48    }
49    let Ok(dir) = crate::core::data_dir::lean_ctx_data_dir() else {
50        return Vec::new();
51    };
52    let path = dir.join("universal-gotchas.json");
53    if let Ok(content) = std::fs::read_to_string(&path) {
54        serde_json::from_str(&content).unwrap_or_default()
55    } else {
56        Vec::new()
57    }
58}
59
60pub fn save_universal_gotchas(gotchas: &[Gotcha]) -> Result<(), String> {
61    let dir = crate::core::data_dir::lean_ctx_data_dir()?;
62    let path = dir.join("universal-gotchas.json");
63    let tmp = path.with_extension("tmp");
64    let json = serde_json::to_string_pretty(gotchas).map_err(|e| e.to_string())?;
65    std::fs::write(&tmp, &json).map_err(|e| e.to_string())?;
66    std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
67    Ok(())
68}