1use serde::{Deserialize, Serialize};
7
8use crate::graph::CodeGraph;
9use crate::types::CodeUnitType;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct CodeConcept {
16 pub name: String,
18 pub description: String,
20 pub implementations: Vec<ConceptImplementation>,
22 pub related: Vec<String>,
24 pub confidence: f64,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ConceptImplementation {
31 pub node_id: u64,
33 pub name: String,
35 pub file_path: String,
37 pub strength: f64,
39 pub aspect: String,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ConceptQuery {
46 pub description: String,
48 pub constraints: Vec<ConceptConstraint>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub enum ConceptConstraint {
55 InModule(String),
57 OfType(String),
59 HasPattern(String),
61}
62
63struct ConceptDef {
66 name: &'static str,
67 description: &'static str,
68 keywords: &'static [&'static str],
69 related: &'static [&'static str],
70}
71
72const CONCEPTS: &[ConceptDef] = &[
73 ConceptDef {
74 name: "Authentication",
75 description: "User identity verification and access control",
76 keywords: &[
77 "auth",
78 "login",
79 "logout",
80 "token",
81 "jwt",
82 "oauth",
83 "password",
84 "session",
85 "credential",
86 ],
87 related: &["Authorization", "User Management", "Security"],
88 },
89 ConceptDef {
90 name: "Payment",
91 description: "Financial transaction processing",
92 keywords: &[
93 "payment", "charge", "stripe", "paypal", "billing", "checkout", "invoice", "refund",
94 ],
95 related: &["User Management", "Configuration"],
96 },
97 ConceptDef {
98 name: "User Management",
99 description: "User accounts, profiles, and registration",
100 keywords: &[
101 "user",
102 "account",
103 "profile",
104 "registration",
105 "signup",
106 "onboard",
107 ],
108 related: &["Authentication", "Database"],
109 },
110 ConceptDef {
111 name: "Database",
112 description: "Data persistence and querying",
113 keywords: &[
114 "database",
115 "db",
116 "query",
117 "sql",
118 "migration",
119 "schema",
120 "repository",
121 "orm",
122 "model",
123 ],
124 related: &["Caching", "Configuration"],
125 },
126 ConceptDef {
127 name: "API",
128 description: "External interface endpoints",
129 keywords: &[
130 "api",
131 "endpoint",
132 "route",
133 "handler",
134 "controller",
135 "rest",
136 "graphql",
137 "grpc",
138 ],
139 related: &["Authentication", "Error Handling"],
140 },
141 ConceptDef {
142 name: "Logging",
143 description: "Application logging and telemetry",
144 keywords: &[
145 "log",
146 "logger",
147 "trace",
148 "debug",
149 "warn",
150 "error",
151 "metric",
152 "telemetry",
153 "monitor",
154 ],
155 related: &["Error Handling", "Configuration"],
156 },
157 ConceptDef {
158 name: "Configuration",
159 description: "Application configuration and settings",
160 keywords: &[
161 "config",
162 "setting",
163 "env",
164 "environment",
165 "option",
166 "feature_flag",
167 ],
168 related: &["Logging"],
169 },
170 ConceptDef {
171 name: "Testing",
172 description: "Test infrastructure and utilities",
173 keywords: &[
174 "test",
175 "mock",
176 "stub",
177 "fixture",
178 "assert",
179 "spec",
180 "bench",
181 "integration_test",
182 ],
183 related: &["Error Handling"],
184 },
185 ConceptDef {
186 name: "Error Handling",
187 description: "Error management and recovery",
188 keywords: &[
189 "error",
190 "exception",
191 "retry",
192 "fallback",
193 "panic",
194 "throw",
195 "recover",
196 "result",
197 ],
198 related: &["Logging", "API"],
199 },
200 ConceptDef {
201 name: "Caching",
202 description: "Data caching for performance",
203 keywords: &[
204 "cache",
205 "memoize",
206 "lru",
207 "ttl",
208 "redis",
209 "memcached",
210 "invalidate",
211 ],
212 related: &["Database", "Configuration"],
213 },
214 ConceptDef {
215 name: "Rate Limiting",
216 description: "Request throttling and rate control",
217 keywords: &["rate_limit", "throttle", "backoff", "quota", "bucket"],
218 related: &["Authentication", "API"],
219 },
220 ConceptDef {
221 name: "Security",
222 description: "Security measures and vulnerability protection",
223 keywords: &[
224 "security", "encrypt", "decrypt", "hash", "sanitize", "xss", "csrf", "cors",
225 ],
226 related: &["Authentication", "API"],
227 },
228];
229
230pub struct ConceptNavigator<'g> {
234 graph: &'g CodeGraph,
235}
236
237impl<'g> ConceptNavigator<'g> {
238 pub fn new(graph: &'g CodeGraph) -> Self {
239 Self { graph }
240 }
241
242 pub fn find_concept(&self, query: ConceptQuery) -> Vec<CodeConcept> {
244 let query_lower = query.description.to_lowercase();
245 let mut results = Vec::new();
246
247 for def in CONCEPTS {
248 let name_match = def.name.to_lowercase().contains(&query_lower)
249 || query_lower.contains(&def.name.to_lowercase());
250 let keyword_match = def.keywords.iter().any(|k| query_lower.contains(k));
251
252 if name_match || keyword_match {
253 let concept = self.build_concept(def, &query.constraints);
254 if !concept.implementations.is_empty() {
255 results.push(concept);
256 }
257 }
258 }
259
260 results.sort_by(|a, b| {
262 b.confidence
263 .partial_cmp(&a.confidence)
264 .unwrap_or(std::cmp::Ordering::Equal)
265 });
266 results
267 }
268
269 pub fn map_all_concepts(&self) -> Vec<CodeConcept> {
271 let mut results = Vec::new();
272 for def in CONCEPTS {
273 let concept = self.build_concept(def, &[]);
274 if !concept.implementations.is_empty() {
275 results.push(concept);
276 }
277 }
278 results.sort_by(|a, b| {
279 b.confidence
280 .partial_cmp(&a.confidence)
281 .unwrap_or(std::cmp::Ordering::Equal)
282 });
283 results
284 }
285
286 pub fn explain_concept(&self, name: &str) -> Option<CodeConcept> {
288 let name_lower = name.to_lowercase();
289 for def in CONCEPTS {
290 if def.name.to_lowercase() == name_lower {
291 let concept = self.build_concept(def, &[]);
292 return Some(concept);
293 }
294 }
295 None
296 }
297
298 fn build_concept(&self, def: &ConceptDef, constraints: &[ConceptConstraint]) -> CodeConcept {
301 let mut implementations = Vec::new();
302
303 for unit in self.graph.units() {
304 let name_lower = unit.name.to_lowercase();
305 let qname_lower = unit.qualified_name.to_lowercase();
306
307 let mut score = 0.0f64;
309 for keyword in def.keywords {
310 if name_lower.contains(keyword) {
311 score += 0.4;
312 }
313 if qname_lower.contains(keyword) {
314 score += 0.2;
315 }
316 if let Some(ref doc) = unit.doc_summary {
317 if doc.to_lowercase().contains(keyword) {
318 score += 0.15;
319 }
320 }
321 }
322
323 if unit.unit_type == CodeUnitType::Function || unit.unit_type == CodeUnitType::Type {
325 score += 0.05;
326 }
327
328 if score < 0.3 {
329 continue;
330 }
331
332 let passes_constraints = constraints.iter().all(|c| match c {
334 ConceptConstraint::InModule(m) => qname_lower.contains(&m.to_lowercase()),
335 ConceptConstraint::OfType(t) => {
336 unit.unit_type.label().to_lowercase() == t.to_lowercase()
337 }
338 ConceptConstraint::HasPattern(_) => true, });
340
341 if !passes_constraints {
342 continue;
343 }
344
345 let aspect = if unit.unit_type == CodeUnitType::Function {
346 "implementation".to_string()
347 } else if unit.unit_type == CodeUnitType::Type {
348 "definition".to_string()
349 } else if unit.unit_type == CodeUnitType::Test {
350 "test".to_string()
351 } else {
352 "usage".to_string()
353 };
354
355 implementations.push(ConceptImplementation {
356 node_id: unit.id,
357 name: unit.name.clone(),
358 file_path: unit.file_path.display().to_string(),
359 strength: score.min(1.0),
360 aspect,
361 });
362 }
363
364 implementations.sort_by(|a, b| {
366 b.strength
367 .partial_cmp(&a.strength)
368 .unwrap_or(std::cmp::Ordering::Equal)
369 });
370
371 let confidence = if implementations.is_empty() {
372 0.0
373 } else {
374 let top_strength = implementations[0].strength;
375 (top_strength * 0.6 + (implementations.len() as f64 * 0.1).min(0.4)).min(1.0)
376 };
377
378 CodeConcept {
379 name: def.name.to_string(),
380 description: def.description.to_string(),
381 implementations,
382 related: def.related.iter().map(|s| s.to_string()).collect(),
383 confidence,
384 }
385 }
386}
387
388#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::types::{CodeUnit, CodeUnitType, Language, Span};
394 use std::path::PathBuf;
395
396 fn test_graph() -> CodeGraph {
397 let mut graph = CodeGraph::with_default_dimension();
398 graph.add_unit(CodeUnit::new(
399 CodeUnitType::Function,
400 Language::Python,
401 "authenticate_user".to_string(),
402 "auth.authenticate_user".to_string(),
403 PathBuf::from("src/auth.py"),
404 Span::new(1, 0, 30, 0),
405 ));
406 graph.add_unit(CodeUnit::new(
407 CodeUnitType::Function,
408 Language::Python,
409 "process_payment".to_string(),
410 "payments.process_payment".to_string(),
411 PathBuf::from("src/payments.py"),
412 Span::new(1, 0, 20, 0),
413 ));
414 graph.add_unit(CodeUnit::new(
415 CodeUnitType::Function,
416 Language::Python,
417 "cache_result".to_string(),
418 "utils.cache_result".to_string(),
419 PathBuf::from("src/utils.py"),
420 Span::new(1, 0, 15, 0),
421 ));
422 graph
423 }
424
425 #[test]
426 fn find_authentication_concept() {
427 let graph = test_graph();
428 let nav = ConceptNavigator::new(&graph);
429 let results = nav.find_concept(ConceptQuery {
430 description: "authentication".to_string(),
431 constraints: Vec::new(),
432 });
433 assert!(!results.is_empty());
434 assert_eq!(results[0].name, "Authentication");
435 assert!(!results[0].implementations.is_empty());
436 }
437
438 #[test]
439 fn map_all_finds_concepts() {
440 let graph = test_graph();
441 let nav = ConceptNavigator::new(&graph);
442 let all = nav.map_all_concepts();
443 assert!(all.len() >= 2);
445 }
446
447 #[test]
448 fn explain_concept_works() {
449 let graph = test_graph();
450 let nav = ConceptNavigator::new(&graph);
451 let explained = nav.explain_concept("Payment");
452 assert!(explained.is_some());
453 let c = explained.unwrap();
454 assert!(!c.implementations.is_empty());
455 }
456}