Skip to main content

cgx_engine/
registry.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5
6/// Metadata for an indexed repository stored in the global registry.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct RepoEntry {
9    /// Stable SHA-256–derived ID for the repo path.
10    pub id: String,
11    pub name: String,
12    pub path: PathBuf,
13    /// Path to the DuckDB database file (`~/.cgx/repos/<id>.db`).
14    pub db_path: PathBuf,
15    pub indexed_at: String,
16    pub node_count: u64,
17    pub edge_count: u64,
18    /// Fraction of nodes per language, e.g. `{"typescript": 0.72, "rust": 0.28}`.
19    #[serde(default)]
20    pub language_breakdown: HashMap<String, f64>,
21}
22
23/// Global registry of all repositories indexed by cgx, persisted at `~/.cgx/registry.json`.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct Registry {
26    #[serde(default = "default_version")]
27    pub version: u32,
28    #[serde(default)]
29    pub repos: Vec<RepoEntry>,
30}
31
32fn default_version() -> u32 {
33    1
34}
35
36impl Registry {
37    fn path() -> PathBuf {
38        dirs::home_dir()
39            .unwrap_or_else(|| PathBuf::from("."))
40            .join(".cgx")
41            .join("registry.json")
42    }
43
44    /// Load the registry from `~/.cgx/registry.json`, creating it if absent.
45    pub fn load() -> anyhow::Result<Self> {
46        let path = Self::path();
47        if path.exists() {
48            let content = std::fs::read_to_string(&path)?;
49            let registry: Registry = serde_json::from_str(&content)?;
50            Ok(registry)
51        } else {
52            if let Some(dir) = path.parent() {
53                std::fs::create_dir_all(dir)?;
54            }
55            Ok(Registry {
56                version: 1,
57                repos: Vec::new(),
58            })
59        }
60    }
61
62    /// Persist the registry to `~/.cgx/registry.json`.
63    pub fn save(&self) -> anyhow::Result<()> {
64        let path = Self::path();
65        if let Some(dir) = path.parent() {
66            std::fs::create_dir_all(dir)?;
67        }
68        let content = serde_json::to_string_pretty(self)?;
69        std::fs::write(&path, content)?;
70        Ok(())
71    }
72
73    /// Add or replace a repo entry (matched by `id`).
74    pub fn register(&mut self, entry: RepoEntry) {
75        self.repos.retain(|r| r.id != entry.id);
76        self.repos.push(entry);
77    }
78
79    /// Look up a repo by its canonical on-disk path.
80    pub fn find_by_path(&self, path: &Path) -> Option<&RepoEntry> {
81        let canonical = path.canonicalize().ok()?;
82        self.repos
83            .iter()
84            .find(|r| r.path.canonicalize().ok().as_ref() == Some(&canonical))
85    }
86
87    /// Look up a repo by its stable SHA-derived `id`.
88    pub fn find_by_id(&self, id: &str) -> Option<&RepoEntry> {
89        self.repos.iter().find(|r| r.id == id)
90    }
91}