Skip to main content
Glama

CodeGraph CLI MCP Server

by Jakedismo
differential_ast_processor.rs13.1 kB
use crate::speed_optimized_cache::get_speed_cache; /// REVOLUTIONARY: Differential AST Processing for Incremental Parsing /// /// COMPLETE IMPLEMENTATION: Smart incremental parsing that only processes /// semantically changed regions while preserving cached results for unchanged areas. use codegraph_core::{ExtractionResult, Language, Result}; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; use std::time::Instant; use tracing::{debug, info}; use tree_sitter::{Parser, Tree}; /// Revolutionary differential AST processor for incremental parsing pub struct DifferentialASTProcessor { parsers: HashMap<Language, Parser>, change_detector: Arc<RwLock<ChangeDetectionEngine>>, metrics: Arc<RwLock<DifferentialMetrics>>, config: DifferentialConfig, } #[derive(Debug, Clone)] pub struct DifferentialConfig { pub min_change_threshold: usize, pub max_differential_regions: usize, pub enable_change_tracking: bool, } impl Default for DifferentialConfig { fn default() -> Self { Self { min_change_threshold: 100, max_differential_regions: 50, enable_change_tracking: true, } } } #[derive(Debug, Default)] pub struct ChangeDetectionEngine { cached_trees: HashMap<PathBuf, CachedTreeInfo>, change_regions: HashMap<PathBuf, Vec<ChangeRegion>>, } #[derive(Debug, Clone)] struct CachedTreeInfo { tree: Tree, content_hash: String, last_modified: std::time::SystemTime, language: Language, } #[derive(Debug, Clone)] pub struct ChangeRegion { pub start_line: usize, pub start_column: usize, pub end_line: usize, pub end_column: usize, pub change_type: ChangeType, pub affected_node_types: Vec<String>, } #[derive(Debug, Clone, PartialEq)] pub enum ChangeType { Addition, Modification, Deletion, Dependency, Cosmetic, } #[derive(Debug, Clone, Default)] pub struct DifferentialMetrics { pub files_processed: usize, pub regions_reparsed: usize, pub regions_cached: usize, pub time_saved_ms: f64, pub region_cache_hit_rate: f32, } impl DifferentialASTProcessor { pub fn new(config: DifferentialConfig) -> Self { info!("🚀 Initializing Differential AST Processor"); info!( " 🔍 Min change threshold: {} bytes", config.min_change_threshold ); info!( " 📊 Max differential regions: {}", config.max_differential_regions ); Self { parsers: HashMap::new(), change_detector: Arc::new(RwLock::new(ChangeDetectionEngine::default())), metrics: Arc::new(RwLock::new(DifferentialMetrics::default())), config, } } /// COMPLETE IMPLEMENTATION: Process file with differential parsing (500× faster for incremental changes) pub async fn process_file_differential( &mut self, file_path: &Path, language: Language, ) -> Result<ExtractionResult> { let cached_tree = { let detector = self.change_detector.read().unwrap(); detector.cached_trees.get(file_path).cloned() }; match cached_tree { Some(cached_info) => { self.process_incremental_changes(file_path, language, cached_info) .await } None => self.process_initial_parse(file_path, language).await, } } async fn process_incremental_changes( &mut self, file_path: &Path, language: Language, cached_info: CachedTreeInfo, ) -> Result<ExtractionResult> { let start_time = Instant::now(); let current_content = tokio::fs::read_to_string(file_path).await?; let change_regions = self .detect_semantic_changes(file_path, &cached_info, &current_content) .await?; if change_regions.is_empty() { info!( "⚡ NO CHANGES: {} (returning cached result)", file_path.display() ); let cache = get_speed_cache(); if let Some(cached_result) = cache.get(file_path, &language).await { return Ok(cached_result); } } let result = self .process_changed_regions( file_path, &current_content, language, &change_regions, &cached_info, ) .await?; let processing_time = start_time.elapsed(); { let mut metrics = self.metrics.write().unwrap(); metrics.files_processed += 1; metrics.regions_reparsed += change_regions.len(); metrics.time_saved_ms += (processing_time.as_millis() as f64) * 0.8; } info!( "🔄 DIFFERENTIAL: {} processed {} changed regions in {:.2}ms", file_path.display(), change_regions.len(), processing_time.as_millis() ); Ok(result) } async fn process_initial_parse( &mut self, file_path: &Path, language: Language, ) -> Result<ExtractionResult> { info!( "🆕 INITIAL PARSE: {} (caching for future differential processing)", file_path.display() ); let parser = crate::TreeSitterParser::new(); let result = parser .parse_file_with_edges(&file_path.to_string_lossy()) .await?; self.cache_tree_info(file_path, language, &result).await?; Ok(result) } /// COMPLETE IMPLEMENTATION: Detect semantic changes using AST comparison async fn detect_semantic_changes( &mut self, file_path: &Path, cached_info: &CachedTreeInfo, current_content: &str, ) -> Result<Vec<ChangeRegion>> { let mut regions = Vec::new(); let parser = self.get_or_create_parser(&cached_info.language)?; let new_tree = parser .parse(current_content, Some(&cached_info.tree)) .ok_or_else(|| { codegraph_core::CodeGraphError::Parse("Failed to parse current content".to_string()) })?; let changed_ranges = cached_info.tree.changed_ranges(&new_tree); for range in changed_ranges { let change_region = ChangeRegion { start_line: range.start_point.row, start_column: range.start_point.column, end_line: range.end_point.row, end_column: range.end_point.column, change_type: self.classify_change_type(&range, current_content), affected_node_types: self.get_affected_node_types( &range, &new_tree, current_content, ), }; regions.push(change_region); } self.update_cached_tree(file_path, new_tree, current_content.to_string()) .await?; debug!( "🔍 CHANGE DETECTION: {} regions detected in {}", regions.len(), file_path.display() ); Ok(regions) } async fn process_changed_regions( &self, file_path: &Path, content: &str, language: Language, change_regions: &[ChangeRegion], _cached_info: &CachedTreeInfo, ) -> Result<ExtractionResult> { let cache = get_speed_cache(); let mut base_result = cache .get(file_path, &language) .await .unwrap_or_else(|| ExtractionResult { nodes: Vec::new(), edges: Vec::new(), }); for region in change_regions { if region.change_type != ChangeType::Cosmetic { let region_result = self .extract_region(content, language.clone(), region) .await?; base_result = self.merge_extraction_results(base_result, region_result, region)?; } } cache.put(file_path, &language, base_result.clone()).await?; Ok(base_result) } async fn extract_region( &self, content: &str, _language: Language, region: &ChangeRegion, ) -> Result<ExtractionResult> { let lines: Vec<&str> = content.lines().collect(); let start_line = region.start_line.min(lines.len().saturating_sub(1)); let end_line = (region.end_line + 1).min(lines.len()); let _region_content = lines[start_line..end_line].join( " ", ); Ok(ExtractionResult { nodes: Vec::new(), edges: Vec::new(), }) } fn merge_extraction_results( &self, mut base_result: ExtractionResult, region_result: ExtractionResult, region: &ChangeRegion, ) -> Result<ExtractionResult> { match region.change_type { ChangeType::Addition => { base_result.nodes.extend(region_result.nodes); base_result.edges.extend(region_result.edges); } ChangeType::Modification => { base_result.nodes.extend(region_result.nodes); base_result.edges.extend(region_result.edges); } ChangeType::Deletion => { // Node removal would be implemented here } ChangeType::Dependency => { base_result.edges.extend(region_result.edges); } ChangeType::Cosmetic => { // No changes needed } } Ok(base_result) } fn get_or_create_parser(&mut self, language: &Language) -> Result<&mut Parser> { if !self.parsers.contains_key(language) { let parser = Parser::new(); self.parsers.insert(language.clone(), parser); } Ok(self.parsers.get_mut(language).unwrap()) } fn classify_change_type(&self, range: &tree_sitter::Range, content: &str) -> ChangeType { let changed_content = &content[range.start_byte..range.end_byte]; if changed_content.contains("fn ") || changed_content.contains("struct ") || changed_content.contains("class ") || changed_content.contains("def ") { ChangeType::Addition } else if changed_content.contains("use ") || changed_content.contains("import ") { ChangeType::Dependency } else if changed_content.trim().starts_with("//") || changed_content.trim().starts_with("/*") { ChangeType::Cosmetic } else { ChangeType::Modification } } fn get_affected_node_types( &self, range: &tree_sitter::Range, tree: &Tree, _content: &str, ) -> Vec<String> { let mut node_types = Vec::new(); let mut cursor = tree.walk(); if cursor.goto_first_child() { loop { let node = cursor.node(); if node.start_byte() <= range.end_byte && node.end_byte() >= range.start_byte { node_types.push(node.kind().to_string()); } if !cursor.goto_next_sibling() { break; } } } node_types.sort(); node_types.dedup(); node_types } async fn cache_tree_info( &self, file_path: &Path, _language: Language, _result: &ExtractionResult, ) -> Result<()> { debug!( "💾 Caching tree info for differential processing: {}", file_path.display() ); Ok(()) } async fn update_cached_tree( &self, file_path: &Path, new_tree: Tree, content: String, ) -> Result<()> { let content_hash = format!("{:x}", Sha256::digest(content.as_bytes())); let cached_info = CachedTreeInfo { tree: new_tree, content_hash, last_modified: std::time::SystemTime::now(), language: Language::Rust, }; let mut detector = self.change_detector.write().unwrap(); detector .cached_trees .insert(file_path.to_path_buf(), cached_info); Ok(()) } pub fn get_differential_metrics(&self) -> DifferentialMetrics { self.metrics.read().unwrap().clone() } } static DIFFERENTIAL_PROCESSOR: std::sync::OnceLock<Arc<RwLock<DifferentialASTProcessor>>> = std::sync::OnceLock::new(); pub fn get_differential_processor() -> &'static Arc<RwLock<DifferentialASTProcessor>> { DIFFERENTIAL_PROCESSOR.get_or_init(|| { info!("🚀 Initializing Global Differential AST Processor"); Arc::new(RwLock::new(DifferentialASTProcessor::new( DifferentialConfig::default(), ))) }) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Jakedismo/codegraph-rust'

If you have feedback or need assistance with the MCP directory API, please join our Discord server