cadi_core/ghost/
analyzer.rs

1use crate::graph::{GraphStore, EdgeType};
2use std::collections::HashSet;
3
4/// Analyzes atoms to determine what dependencies should be included
5pub struct DependencyAnalyzer<'a> {
6    graph: &'a GraphStore,
7}
8
9#[derive(Debug)]
10pub struct DependencyInfo {
11    pub atom_id: String,
12    pub dependencies: Vec<DependencyEdge>,
13    pub token_estimate: usize,
14}
15
16#[derive(Debug)]
17pub struct DependencyEdge {
18    pub target: String,
19    pub edge_type: EdgeType,
20    pub priority: DependencyPriority,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24pub enum DependencyPriority {
25    Critical = 0,    // Type definitions, direct imports
26    High = 1,        // Method signatures, interface implementations
27    Medium = 2,      // Indirect dependencies
28    Low = 3,         // Optional or weak references
29}
30
31impl<'a> DependencyAnalyzer<'a> {
32    pub fn new(graph: &'a GraphStore) -> Self {
33        Self { graph }
34    }
35
36    /// Analyze dependencies for a set of atoms
37    pub fn analyze_dependencies(
38        &self,
39        atom_ids: &[String],
40    ) -> Result<Vec<DependencyInfo>, Box<dyn std::error::Error + Send + Sync>> {
41        let mut results = Vec::new();
42
43        for atom_id in atom_ids {
44            let deps = self.graph.get_dependencies(atom_id)?;
45            let token_estimate = self.graph.get_token_estimate(atom_id)?;
46
47            let dependency_edges = deps
48                .into_iter()
49                .map(|(edge_type, target)| DependencyEdge {
50                    target,
51                    edge_type,
52                    priority: self.calculate_priority(&edge_type),
53                })
54                .collect();
55
56            results.push(DependencyInfo {
57                atom_id: atom_id.clone(),
58                dependencies: dependency_edges,
59                token_estimate,
60            });
61        }
62
63        Ok(results)
64    }
65
66    /// Find all atoms that would be included in an expansion
67    pub fn simulate_expansion(
68        &self,
69        atom_ids: &[String],
70        policy: &super::policy::ExpansionPolicy,
71    ) -> Result<ExpansionSimulation, Box<dyn std::error::Error + Send + Sync>> {
72        let mut included = HashSet::new();
73        let mut total_tokens = 0;
74        let mut depth_reached = 0;
75
76        // Start with requested atoms
77        let mut frontier: Vec<(String, usize)> = atom_ids.iter()
78            .map(|id| (id.clone(), 0))
79            .collect();
80
81        for (atom_id, _depth) in &frontier {
82            included.insert(atom_id.clone());
83            total_tokens += self.graph.get_token_estimate(atom_id)?;
84        }
85
86        // BFS expansion
87        while let Some((atom_id, depth)) = frontier.pop() {
88            if depth >= policy.max_depth {
89                depth_reached = depth_reached.max(depth);
90                continue;
91            }
92
93            if included.len() >= policy.max_atoms || total_tokens >= policy.max_tokens {
94                break;
95            }
96
97            let deps = self.graph.get_dependencies(&atom_id)?;
98            for (edge_type, dep_id) in deps {
99                if policy.follow_edges.contains(&edge_type) && !included.contains(&dep_id) {
100                    let dep_tokens = self.graph.get_token_estimate(&dep_id)?;
101                    if total_tokens + dep_tokens <= policy.max_tokens {
102                        included.insert(dep_id.clone());
103                        total_tokens += dep_tokens;
104                        frontier.push((dep_id, depth + 1));
105                    }
106                }
107            }
108        }
109
110        // Check if we were truncated
111        let truncated = included.len() >= policy.max_atoms || total_tokens >= policy.max_tokens;
112
113        Ok(ExpansionSimulation {
114            included_atoms: included.into_iter().collect(),
115            total_tokens,
116            max_depth_reached: depth_reached,
117            truncated,
118        })
119    }
120
121    fn calculate_priority(&self, edge_type: &EdgeType) -> DependencyPriority {
122        match edge_type {
123            EdgeType::Imports => DependencyPriority::Critical,
124            EdgeType::TypeRef => DependencyPriority::High,
125            EdgeType::Calls => DependencyPriority::Medium,
126            EdgeType::Implements => DependencyPriority::High,
127            EdgeType::Extends => DependencyPriority::High,
128            EdgeType::GenericRef => DependencyPriority::High,
129            EdgeType::ComposedOf => DependencyPriority::Low,
130            EdgeType::Exports => DependencyPriority::Low,
131            EdgeType::MacroUse => DependencyPriority::Medium,
132            EdgeType::Tests => DependencyPriority::Low,
133            EdgeType::DocRef => DependencyPriority::Low,
134        }
135    }
136}
137
138#[derive(Debug)]
139pub struct ExpansionSimulation {
140    pub included_atoms: Vec<String>,
141    pub total_tokens: usize,
142    pub max_depth_reached: usize,
143    pub truncated: bool,
144}