agentic_codebase/graph/
code_graph.rs1use std::collections::{HashMap, HashSet};
7
8use crate::types::{
9 AcbError, AcbResult, CodeUnit, CodeUnitType, Edge, EdgeType, Language, MAX_EDGES_PER_UNIT,
10};
11
12#[derive(Debug, Clone)]
17pub struct CodeGraph {
18 units: Vec<CodeUnit>,
20
21 edges: Vec<Edge>,
23
24 edges_by_source: HashMap<u64, Vec<usize>>,
26
27 edges_by_target: HashMap<u64, Vec<usize>>,
29
30 dimension: usize,
32
33 languages: HashSet<Language>,
35}
36
37impl CodeGraph {
38 pub fn new(dimension: usize) -> Self {
40 Self {
41 units: Vec::new(),
42 edges: Vec::new(),
43 edges_by_source: HashMap::new(),
44 edges_by_target: HashMap::new(),
45 dimension,
46 languages: HashSet::new(),
47 }
48 }
49
50 pub fn with_default_dimension() -> Self {
52 Self::new(crate::types::DEFAULT_DIMENSION)
53 }
54
55 pub fn add_unit(&mut self, mut unit: CodeUnit) -> u64 {
59 let id = self.units.len() as u64;
60 unit.id = id;
61 self.languages.insert(unit.language);
62 self.units.push(unit);
63 id
64 }
65
66 pub fn add_edge(&mut self, edge: Edge) -> AcbResult<()> {
74 if edge.source_id == edge.target_id {
76 return Err(AcbError::SelfEdge(edge.source_id));
77 }
78
79 if edge.source_id >= self.units.len() as u64 {
81 return Err(AcbError::UnitNotFound(edge.source_id));
82 }
83
84 if edge.target_id >= self.units.len() as u64 {
86 return Err(AcbError::InvalidEdgeTarget(edge.target_id));
87 }
88
89 let source_edge_count = self
91 .edges_by_source
92 .get(&edge.source_id)
93 .map(|v| v.len() as u32)
94 .unwrap_or(0);
95 if source_edge_count >= MAX_EDGES_PER_UNIT {
96 return Err(AcbError::TooManyEdges(source_edge_count));
97 }
98
99 let idx = self.edges.len();
100 self.edges_by_source
101 .entry(edge.source_id)
102 .or_default()
103 .push(idx);
104 self.edges_by_target
105 .entry(edge.target_id)
106 .or_default()
107 .push(idx);
108 self.edges.push(edge);
109
110 Ok(())
111 }
112
113 pub fn unit_count(&self) -> usize {
115 self.units.len()
116 }
117
118 pub fn edge_count(&self) -> usize {
120 self.edges.len()
121 }
122
123 pub fn dimension(&self) -> usize {
125 self.dimension
126 }
127
128 pub fn languages(&self) -> &HashSet<Language> {
130 &self.languages
131 }
132
133 pub fn get_unit(&self, id: u64) -> Option<&CodeUnit> {
135 self.units.get(id as usize)
136 }
137
138 pub fn get_unit_mut(&mut self, id: u64) -> Option<&mut CodeUnit> {
140 self.units.get_mut(id as usize)
141 }
142
143 pub fn units(&self) -> &[CodeUnit] {
145 &self.units
146 }
147
148 pub fn edges(&self) -> &[Edge] {
150 &self.edges
151 }
152
153 pub fn edges_from(&self, source_id: u64) -> Vec<&Edge> {
155 self.edges_by_source
156 .get(&source_id)
157 .map(|indices| indices.iter().map(|&i| &self.edges[i]).collect())
158 .unwrap_or_default()
159 }
160
161 pub fn edges_to(&self, target_id: u64) -> Vec<&Edge> {
163 self.edges_by_target
164 .get(&target_id)
165 .map(|indices| indices.iter().map(|&i| &self.edges[i]).collect())
166 .unwrap_or_default()
167 }
168
169 pub fn edges_from_of_type(&self, source_id: u64, edge_type: EdgeType) -> Vec<&Edge> {
171 self.edges_from(source_id)
172 .into_iter()
173 .filter(|e| e.edge_type == edge_type)
174 .collect()
175 }
176
177 pub fn edges_to_of_type(&self, target_id: u64, edge_type: EdgeType) -> Vec<&Edge> {
179 self.edges_to(target_id)
180 .into_iter()
181 .filter(|e| e.edge_type == edge_type)
182 .collect()
183 }
184
185 pub fn find_units_by_name(&self, prefix: &str) -> Vec<&CodeUnit> {
187 let prefix_lower = prefix.to_lowercase();
188 self.units
189 .iter()
190 .filter(|u| u.name.to_lowercase().starts_with(&prefix_lower))
191 .collect()
192 }
193
194 pub fn find_units_by_exact_name(&self, name: &str) -> Vec<&CodeUnit> {
196 self.units.iter().filter(|u| u.name == name).collect()
197 }
198
199 pub fn find_units_by_type(&self, unit_type: CodeUnitType) -> Vec<&CodeUnit> {
201 self.units
202 .iter()
203 .filter(|u| u.unit_type == unit_type)
204 .collect()
205 }
206
207 pub fn find_units_by_language(&self, language: Language) -> Vec<&CodeUnit> {
209 self.units
210 .iter()
211 .filter(|u| u.language == language)
212 .collect()
213 }
214
215 pub fn find_units_by_path(&self, path: &std::path::Path) -> Vec<&CodeUnit> {
217 self.units.iter().filter(|u| u.file_path == path).collect()
218 }
219
220 pub fn has_edge(&self, source_id: u64, target_id: u64, edge_type: EdgeType) -> bool {
222 self.edges_from(source_id)
223 .iter()
224 .any(|e| e.target_id == target_id && e.edge_type == edge_type)
225 }
226
227 pub fn stats(&self) -> GraphStats {
229 let mut type_counts: HashMap<CodeUnitType, usize> = HashMap::new();
230 let mut edge_type_counts: HashMap<EdgeType, usize> = HashMap::new();
231 let mut lang_counts: HashMap<Language, usize> = HashMap::new();
232
233 for unit in &self.units {
234 *type_counts.entry(unit.unit_type).or_default() += 1;
235 *lang_counts.entry(unit.language).or_default() += 1;
236 }
237 for edge in &self.edges {
238 *edge_type_counts.entry(edge.edge_type).or_default() += 1;
239 }
240
241 GraphStats {
242 unit_count: self.units.len(),
243 edge_count: self.edges.len(),
244 dimension: self.dimension,
245 type_counts,
246 edge_type_counts,
247 language_counts: lang_counts,
248 }
249 }
250}
251
252impl Default for CodeGraph {
253 fn default() -> Self {
254 Self::with_default_dimension()
255 }
256}
257
258#[derive(Debug, Clone)]
260pub struct GraphStats {
261 pub unit_count: usize,
263 pub edge_count: usize,
265 pub dimension: usize,
267 pub type_counts: HashMap<CodeUnitType, usize>,
269 pub edge_type_counts: HashMap<EdgeType, usize>,
271 pub language_counts: HashMap<Language, usize>,
273}