Skip to main content

weavatrix_rust/engine/
session.rs

1use super::{RepositoryState, Weavatrix};
2use crate::analyzer::Analyzer;
3use crate::model::{Error, Result};
4use std::collections::BTreeMap;
5use std::path::{Path, PathBuf};
6
7impl Weavatrix {
8    /// Opens and analyzes one local repository without running its code.
9    ///
10    /// # Errors
11    ///
12    /// Returns scan, parser, or graph validation failures.
13    pub fn open(root: impl AsRef<Path>) -> Result<Self> {
14        let analyzer = Analyzer::default();
15        let state = RepositoryState::build(&analyzer, root)?;
16        let known_states = BTreeMap::from([(state.root.clone(), state.clone())]);
17        Ok(Self {
18            analyzer,
19            state,
20            known_states,
21            tool_cache: BTreeMap::new(),
22        })
23    }
24
25    pub(crate) fn from_state(state: RepositoryState) -> Self {
26        let known_states = BTreeMap::from([(state.root.clone(), state.clone())]);
27        Self {
28            analyzer: Analyzer::default(),
29            state,
30            known_states,
31            tool_cache: BTreeMap::new(),
32        }
33    }
34
35    #[must_use]
36    pub const fn state(&self) -> &RepositoryState {
37        &self.state
38    }
39
40    /// Rebuilds only the derived in-memory snapshot.
41    ///
42    /// # Errors
43    ///
44    /// Returns scan, parser, or graph validation failures.
45    pub fn rebuild(&mut self) -> Result<()> {
46        self.state = RepositoryState::build(&self.analyzer, &self.state.root)?;
47        self.tool_cache.clear();
48        self.remember_active_state();
49        Ok(())
50    }
51
52    /// Checks the incremental scanner revision and rebuilds only when source
53    /// evidence changed.
54    ///
55    /// # Errors
56    ///
57    /// Returns scan, parser, or graph validation failures.
58    pub fn refresh_if_stale(&mut self) -> Result<bool> {
59        let scan = self
60            .analyzer
61            .scan(&self.state.root, Some(&self.state.scan))?;
62        if scan.revision == self.state.scan.revision {
63            self.state.scan = scan;
64            self.remember_active_state();
65            return Ok(false);
66        }
67        self.state = RepositoryState::from_scan(&self.analyzer, &self.state.root, scan)?;
68        self.tool_cache.clear();
69        self.remember_active_state();
70        Ok(true)
71    }
72
73    /// Retargets this process to another local repository.
74    ///
75    /// # Errors
76    ///
77    /// Returns scan, parser, or graph validation failures.
78    pub fn open_repository(&mut self, root: impl AsRef<Path>) -> Result<()> {
79        self.open_repository_with_build(root, true)?;
80        Ok(())
81    }
82
83    /// Retargets this process, optionally requiring a fresh graph build.
84    ///
85    /// With `build == false`, only a repository already opened by this process
86    /// can be activated. Its exact analyzed state is retained in memory, so a
87    /// no-build switch never scans or executes repository code.
88    ///
89    /// # Errors
90    ///
91    /// Returns scan/parser failures for a requested build, or a concrete
92    /// missing-cache error for a no-build request.
93    pub fn open_repository_with_build(
94        &mut self,
95        root: impl AsRef<Path>,
96        build: bool,
97    ) -> Result<bool> {
98        if build {
99            let state = RepositoryState::build(&self.analyzer, root)?;
100            self.known_states
101                .insert(self.state.root.clone(), self.state.clone());
102            self.state = state;
103            self.tool_cache.clear();
104            self.remember_active_state();
105            return Ok(true);
106        }
107
108        let requested = root
109            .as_ref()
110            .canonicalize()
111            .map_err(|source| Error::io(root.as_ref(), source))?;
112        if requested == self.state.root {
113            return Ok(false);
114        }
115        let cached = self.known_states.get(&requested).cloned().ok_or_else(|| {
116            Error::Analysis(format!(
117                "no in-process graph for {}; call open_repo with build:true first",
118                requested.display()
119            ))
120        })?;
121        self.known_states
122            .insert(self.state.root.clone(), self.state.clone());
123        self.state = cached;
124        self.tool_cache.clear();
125        Ok(false)
126    }
127
128    pub fn known_roots(&self) -> impl Iterator<Item = &Path> {
129        self.known_states.keys().map(PathBuf::as_path)
130    }
131
132    pub(crate) fn ensure_repository_state(&mut self, root: impl AsRef<Path>) -> Result<PathBuf> {
133        let requested = root
134            .as_ref()
135            .canonicalize()
136            .map_err(|source| Error::io(root.as_ref(), source))?;
137        if requested == self.state.root || self.known_states.contains_key(&requested) {
138            return Ok(requested);
139        }
140        let state = RepositoryState::build(&self.analyzer, &requested)?;
141        self.known_states.insert(requested.clone(), state);
142        Ok(requested)
143    }
144
145    pub(crate) fn known_state(&self, root: &Path) -> Option<&RepositoryState> {
146        if root == self.state.root {
147            Some(&self.state)
148        } else {
149            self.known_states.get(root)
150        }
151    }
152
153    pub(crate) fn cached_tool_result(&self, key: &str) -> Option<blazingly_json::Value> {
154        self.tool_cache.get(key).cloned()
155    }
156
157    pub(crate) fn remember_tool_result(&mut self, key: String, value: blazingly_json::Value) {
158        const MAX_TOOL_CACHE_ENTRIES: usize = 32;
159        if self.tool_cache.len() >= MAX_TOOL_CACHE_ENTRIES {
160            self.tool_cache.clear();
161        }
162        self.tool_cache.insert(key, value);
163    }
164
165    fn remember_active_state(&mut self) {
166        self.known_states
167            .insert(self.state.root.clone(), self.state.clone());
168    }
169}