1use super::InternalTool;
17use super::ToolMetadata;
18use super::output::ComprehensiveToolOutput as ToolOutput;
19use crate::subagents::IntelligenceLevel;
20use agcodex_ast::SourceLocation;
21
22use dashmap::DashMap;
23use lru::LruCache;
24use serde::Deserialize;
25use serde::Serialize;
26use std::collections::HashMap;
27use std::num::NonZeroUsize;
28use std::path::Path;
29use std::path::PathBuf;
30use std::sync::Arc;
31use std::sync::Mutex;
32use std::time::Duration;
33use std::time::Instant;
34use thiserror::Error;
35use tokio::fs;
36use tracing::debug;
37use tracing::info;
38use tree_sitter::Language;
40use tree_sitter::Node;
41use tree_sitter::Parser;
42use tree_sitter::Query;
43use tree_sitter::QueryCursor;
44use tree_sitter::StreamingIterator;
45use tree_sitter::Tree;
46
47extern crate tree_sitter_bash;
49extern crate tree_sitter_c;
50extern crate tree_sitter_c_sharp;
51extern crate tree_sitter_clojure;
52extern crate tree_sitter_cpp;
53extern crate tree_sitter_css;
54extern crate tree_sitter_dockerfile;
55extern crate tree_sitter_elixir;
56extern crate tree_sitter_go;
57extern crate tree_sitter_haskell;
58extern crate tree_sitter_hcl;
59extern crate tree_sitter_html;
60extern crate tree_sitter_java;
61extern crate tree_sitter_javascript;
62extern crate tree_sitter_json;
63extern crate tree_sitter_kotlin_ng;
64extern crate tree_sitter_lua;
66extern crate tree_sitter_make;
67extern crate tree_sitter_markdown;
68extern crate tree_sitter_nix;
69extern crate tree_sitter_objc;
70extern crate tree_sitter_ocaml;
71extern crate tree_sitter_php;
72extern crate tree_sitter_python;
73extern crate tree_sitter_rst;
74extern crate tree_sitter_ruby;
75extern crate tree_sitter_rust;
76extern crate tree_sitter_scala;
77extern crate tree_sitter_swift;
78extern crate tree_sitter_toml;
79extern crate tree_sitter_typescript;
80extern crate tree_sitter_yaml;
81extern crate tree_sitter_zig;
82
83#[derive(Error, Debug)]
85pub enum TreeError {
86 #[error("unsupported language: {language}")]
87 UnsupportedLanguage { language: String },
88
89 #[error("failed to parse code: {reason}")]
90 ParseFailed { reason: String },
91
92 #[error("query compilation failed: {query} - {reason}")]
93 QueryCompilationFailed { query: String, reason: String },
94
95 #[error("query execution failed: {reason}")]
96 QueryExecutionFailed { reason: String },
97
98 #[error("file reading failed: {path} - {reason}")]
99 FileReadFailed { path: PathBuf, reason: String },
100
101 #[error("language detection failed for file: {path}")]
102 LanguageDetectionFailed { path: PathBuf },
103
104 #[error("AST node access failed: {reason}")]
105 NodeAccessFailed { reason: String },
106
107 #[error("diff computation failed: {reason}")]
108 DiffFailed { reason: String },
109
110 #[error("symbol extraction failed: {reason}")]
111 SymbolExtractionFailed { reason: String },
112
113 #[error("cache operation failed: {reason}")]
114 CacheFailed { reason: String },
115}
116
117pub type TreeResult<T> = std::result::Result<T, TreeError>;
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
122pub enum SupportedLanguage {
123 Rust,
125 Python,
126 JavaScript,
127 TypeScript,
128 Go,
129 Java,
130 C,
131 Cpp,
132 CSharp,
133 Bash,
134
135 Html,
137 Css,
138 Json,
139 Yaml,
140 Toml,
141
142 Ruby,
144 Php,
145 Lua,
146
147 Haskell,
149 Elixir,
150 Scala,
151 Ocaml,
152 Clojure,
153
154 Zig,
156 Swift,
157 Kotlin,
158 ObjectiveC,
159
160 Dockerfile,
162 Hcl,
163 Nix,
164 Make,
165
166 Markdown,
168 Rst,
170}
171
172impl SupportedLanguage {
173 pub const fn extensions(&self) -> &'static [&'static str] {
175 match self {
176 SupportedLanguage::Rust => &["rs"],
178 SupportedLanguage::Python => &["py", "pyw", "pyi"],
179 SupportedLanguage::JavaScript => &["js", "mjs", "cjs"],
180 SupportedLanguage::TypeScript => &["ts", "tsx", "cts", "mts"],
181 SupportedLanguage::Go => &["go"],
182 SupportedLanguage::Java => &["java"],
183 SupportedLanguage::C => &["c", "h"],
184 SupportedLanguage::Cpp => &["cpp", "cxx", "cc", "hpp", "hxx", "hh"],
185 SupportedLanguage::CSharp => &["cs"],
186 SupportedLanguage::Bash => &["sh", "bash", "zsh"],
187
188 SupportedLanguage::Html => &["html", "htm"],
190 SupportedLanguage::Css => &["css"],
191 SupportedLanguage::Json => &["json"],
192 SupportedLanguage::Yaml => &["yaml", "yml"],
193 SupportedLanguage::Toml => &["toml"],
194
195 SupportedLanguage::Ruby => &["rb"],
197 SupportedLanguage::Php => &["php"],
198 SupportedLanguage::Lua => &["lua"],
199
200 SupportedLanguage::Haskell => &["hs"],
202 SupportedLanguage::Elixir => &["ex", "exs"],
203 SupportedLanguage::Scala => &["scala", "sc"],
204 SupportedLanguage::Ocaml => &["ml", "mli"],
205 SupportedLanguage::Clojure => &["clj", "cljs", "cljc"],
206
207 SupportedLanguage::Zig => &["zig"],
209 SupportedLanguage::Swift => &["swift"],
210 SupportedLanguage::Kotlin => &["kt", "kts"],
211 SupportedLanguage::ObjectiveC => &["m", "mm"],
212
213 SupportedLanguage::Dockerfile => &["dockerfile"],
215 SupportedLanguage::Hcl => &["hcl", "tf"],
216 SupportedLanguage::Nix => &["nix"],
217 SupportedLanguage::Make => &["makefile", "mk"],
218
219 SupportedLanguage::Markdown => &["md", "markdown"],
221 SupportedLanguage::Rst => &["rst"],
223 }
224 }
225
226 pub fn grammar(&self) -> Language {
228 match self {
229 SupportedLanguage::Rust => tree_sitter_rust::LANGUAGE.into(),
231 SupportedLanguage::Python => tree_sitter_python::LANGUAGE.into(),
232 SupportedLanguage::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
233 SupportedLanguage::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
234 SupportedLanguage::Go => tree_sitter_go::LANGUAGE.into(),
235 SupportedLanguage::Java => tree_sitter_java::LANGUAGE.into(),
236 SupportedLanguage::C => tree_sitter_c::LANGUAGE.into(),
237 SupportedLanguage::Cpp => tree_sitter_cpp::LANGUAGE.into(),
238 SupportedLanguage::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
239 SupportedLanguage::Bash => tree_sitter_bash::LANGUAGE.into(),
240
241 SupportedLanguage::Html => tree_sitter_html::LANGUAGE.into(),
243 SupportedLanguage::Css => tree_sitter_css::LANGUAGE.into(),
244 SupportedLanguage::Json => tree_sitter_json::LANGUAGE.into(),
245 SupportedLanguage::Yaml => tree_sitter_yaml::LANGUAGE.into(),
246 SupportedLanguage::Toml => tree_sitter_json::LANGUAGE.into(),
249
250 SupportedLanguage::Ruby => tree_sitter_ruby::LANGUAGE.into(),
252 SupportedLanguage::Php => tree_sitter_javascript::LANGUAGE.into(),
255 SupportedLanguage::Lua => tree_sitter_lua::LANGUAGE.into(),
256
257 SupportedLanguage::Haskell => tree_sitter_haskell::LANGUAGE.into(),
259 SupportedLanguage::Elixir => tree_sitter_elixir::LANGUAGE.into(),
260 SupportedLanguage::Scala => tree_sitter_scala::LANGUAGE.into(),
261 SupportedLanguage::Ocaml => tree_sitter_rust::LANGUAGE.into(),
264 SupportedLanguage::Clojure => tree_sitter_clojure::LANGUAGE.into(),
265
266 SupportedLanguage::Zig => tree_sitter_zig::LANGUAGE.into(),
268 SupportedLanguage::Swift => tree_sitter_swift::LANGUAGE.into(),
269 SupportedLanguage::Kotlin => tree_sitter_kotlin_ng::LANGUAGE.into(),
270 SupportedLanguage::ObjectiveC => tree_sitter_objc::LANGUAGE.into(),
271
272 SupportedLanguage::Dockerfile => tree_sitter_bash::LANGUAGE.into(),
276 SupportedLanguage::Hcl => tree_sitter_hcl::LANGUAGE.into(),
277 SupportedLanguage::Nix => tree_sitter_nix::LANGUAGE.into(),
278 SupportedLanguage::Make => tree_sitter_make::LANGUAGE.into(),
279
280 SupportedLanguage::Markdown => tree_sitter_html::LANGUAGE.into(),
284 SupportedLanguage::Rst => tree_sitter_rst::LANGUAGE.into(),
286 }
287 }
288
289 pub fn from_extension(ext: &str) -> Option<Self> {
291 let ext = ext.to_lowercase();
292
293 Self::all_languages()
295 .iter()
296 .find(|&&lang| lang.extensions().contains(&ext.as_str()))
297 .copied()
298 }
299
300 pub const fn all_languages() -> &'static [Self] {
302 &[
303 SupportedLanguage::Rust,
305 SupportedLanguage::Python,
306 SupportedLanguage::JavaScript,
307 SupportedLanguage::TypeScript,
308 SupportedLanguage::Go,
309 SupportedLanguage::Java,
310 SupportedLanguage::C,
311 SupportedLanguage::Cpp,
312 SupportedLanguage::CSharp,
313 SupportedLanguage::Bash,
314 SupportedLanguage::Html,
316 SupportedLanguage::Css,
317 SupportedLanguage::Json,
318 SupportedLanguage::Yaml,
319 SupportedLanguage::Toml,
320 SupportedLanguage::Ruby,
322 SupportedLanguage::Php,
323 SupportedLanguage::Lua,
324 SupportedLanguage::Haskell,
326 SupportedLanguage::Elixir,
327 SupportedLanguage::Scala,
328 SupportedLanguage::Ocaml,
329 SupportedLanguage::Clojure,
330 SupportedLanguage::Zig,
332 SupportedLanguage::Swift,
333 SupportedLanguage::Kotlin,
334 SupportedLanguage::ObjectiveC,
335 SupportedLanguage::Dockerfile,
337 SupportedLanguage::Hcl,
338 SupportedLanguage::Nix,
339 SupportedLanguage::Make,
340 SupportedLanguage::Markdown,
342 SupportedLanguage::Rst,
344 ]
345 }
346
347 pub const fn as_str(&self) -> &'static str {
349 match self {
350 SupportedLanguage::Rust => "rust",
352 SupportedLanguage::Python => "python",
353 SupportedLanguage::JavaScript => "javascript",
354 SupportedLanguage::TypeScript => "typescript",
355 SupportedLanguage::Go => "go",
356 SupportedLanguage::Java => "java",
357 SupportedLanguage::C => "c",
358 SupportedLanguage::Cpp => "cpp",
359 SupportedLanguage::CSharp => "csharp",
360 SupportedLanguage::Bash => "bash",
361
362 SupportedLanguage::Html => "html",
364 SupportedLanguage::Css => "css",
365 SupportedLanguage::Json => "json",
366 SupportedLanguage::Yaml => "yaml",
367 SupportedLanguage::Toml => "toml",
368
369 SupportedLanguage::Ruby => "ruby",
371 SupportedLanguage::Php => "php",
372 SupportedLanguage::Lua => "lua",
373
374 SupportedLanguage::Haskell => "haskell",
376 SupportedLanguage::Elixir => "elixir",
377 SupportedLanguage::Scala => "scala",
378 SupportedLanguage::Ocaml => "ocaml",
379 SupportedLanguage::Clojure => "clojure",
380
381 SupportedLanguage::Zig => "zig",
383 SupportedLanguage::Swift => "swift",
384 SupportedLanguage::Kotlin => "kotlin",
385 SupportedLanguage::ObjectiveC => "objc",
386
387 SupportedLanguage::Dockerfile => "dockerfile",
389 SupportedLanguage::Hcl => "hcl",
390 SupportedLanguage::Nix => "nix",
391 SupportedLanguage::Make => "make",
392
393 SupportedLanguage::Markdown => "markdown",
395 SupportedLanguage::Rst => "rst",
397 }
398 }
399}
400
401#[derive(Debug)]
403pub struct ParsedAst {
404 pub tree: Tree,
405 pub language: SupportedLanguage,
406 pub source_code: String,
407 pub parse_time: Duration,
408 pub node_count: usize,
409}
410
411impl ParsedAst {
412 pub fn root_node(&self) -> Node<'_> {
414 self.tree.root_node()
415 }
416
417 pub fn has_errors(&self) -> bool {
419 self.root_node().has_error()
420 }
421
422 pub fn error_nodes(&self) -> Vec<Node<'_>> {
424 let mut errors = Vec::new();
425 self.collect_error_nodes(self.root_node(), &mut errors);
426 errors
427 }
428
429 fn collect_error_nodes<'a>(&self, node: Node<'a>, errors: &mut Vec<Node<'a>>) {
430 if node.is_error() || node.is_missing() {
431 errors.push(node);
432 }
433 for i in 0..node.child_count() {
434 if let Some(child) = node.child(i) {
435 self.collect_error_nodes(child, errors);
436 }
437 }
438 }
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct QueryMatch {
444 pub pattern_index: u32,
445 pub captures: Vec<QueryCapture>,
446 pub start_byte: u32,
447 pub end_byte: u32,
448 pub start_point: Point,
449 pub end_point: Point,
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct QueryCapture {
455 pub name: String,
456 pub text: String,
457 pub start_byte: u32,
458 pub end_byte: u32,
459 pub start_point: Point,
460 pub end_point: Point,
461}
462
463#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
465pub struct Point {
466 pub row: u32,
467 pub column: u32,
468}
469
470impl From<tree_sitter::Point> for Point {
471 fn from(point: tree_sitter::Point) -> Self {
472 Self {
473 row: point.row as u32,
474 column: point.column as u32,
475 }
476 }
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct Symbol {
482 pub name: String,
483 pub kind: SymbolKind,
484 pub start_point: Point,
485 pub end_point: Point,
486 pub start_byte: u32,
487 pub end_byte: u32,
488 pub parent: Option<String>,
489 pub visibility: Option<String>,
490 pub type_signature: Option<String>,
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize)]
495pub enum SymbolKind {
496 Function,
497 Method,
498 Class,
499 Interface,
500 Struct,
501 Enum,
502 Variable,
503 Constant,
504 Field,
505 Parameter,
506 Module,
507 Namespace,
508 Type,
509 Trait,
510 Impl,
511 Macro,
512 Other(String),
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct SemanticDiff {
518 pub added: Vec<DiffNode>,
519 pub removed: Vec<DiffNode>,
520 pub modified: Vec<ModifiedNode>,
521 pub moved: Vec<MovedNode>,
522 pub similarity_score: f64,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct DiffNode {
528 pub kind: String,
529 pub text: String,
530 pub start_point: Point,
531 pub end_point: Point,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct ModifiedNode {
537 pub old: DiffNode,
538 pub new: DiffNode,
539 pub similarity: f64,
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize)]
544pub struct MovedNode {
545 pub node: DiffNode,
546 pub old_position: Point,
547 pub new_position: Point,
548}
549
550#[derive(Debug, Clone)]
552pub enum TreeInput {
553 Parse {
555 code: String,
556 language: Option<SupportedLanguage>,
557 file_path: Option<PathBuf>,
558 },
559 ParseFile { file_path: PathBuf },
561 Query {
563 code: String,
564 language: Option<SupportedLanguage>,
565 pattern: String,
566 file_path: Option<PathBuf>,
567 },
568 QueryFile { file_path: PathBuf, pattern: String },
570 ExtractSymbols {
572 code: String,
573 language: Option<SupportedLanguage>,
574 file_path: Option<PathBuf>,
575 },
576 ExtractSymbolsFromFile { file_path: PathBuf },
578 Diff {
580 old_code: String,
581 new_code: String,
582 language: Option<SupportedLanguage>,
583 },
584 DiffFiles {
586 old_file: PathBuf,
587 new_file: PathBuf,
588 },
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
593pub enum TreeOutput {
594 Parsed {
596 language: SupportedLanguage,
597 node_count: usize,
598 has_errors: bool,
599 error_count: usize,
600 parse_time_ms: u64,
601 },
602 QueryResults {
604 matches: Vec<QueryMatch>,
605 total_matches: usize,
606 query_time_ms: u64,
607 },
608 Symbols {
610 symbols: Vec<Symbol>,
611 total_symbols: usize,
612 extraction_time_ms: u64,
613 },
614 Diff {
616 diff: SemanticDiff,
617 diff_time_ms: u64,
618 },
619}
620
621#[derive(Debug, Clone, PartialEq, Eq, Hash)]
623struct CacheKey {
624 content_hash: u64,
625 language: SupportedLanguage,
626}
627
628struct ThreadSafeParser {
630 parser: Mutex<Parser>,
631 _language: SupportedLanguage,
632}
633
634impl ThreadSafeParser {
635 fn new(language: SupportedLanguage) -> TreeResult<Self> {
636 let mut parser = Parser::new();
637 parser
638 .set_language(&language.grammar())
639 .map_err(|_| TreeError::ParseFailed {
640 reason: format!("Failed to initialize {} parser", language.as_str()),
641 })?;
642
643 Ok(Self {
644 parser: Mutex::new(parser),
645 _language: language,
646 })
647 }
648
649 fn parse(&self, code: &str) -> TreeResult<Tree> {
650 let mut parser = self.parser.lock().map_err(|e| TreeError::ParseFailed {
651 reason: format!("Parser lock failed: {}", e),
652 })?;
653
654 parser
655 .parse(code, None)
656 .ok_or_else(|| TreeError::ParseFailed {
657 reason: "Parser returned None".to_string(),
658 })
659 }
660}
661
662struct SemanticDiffEngine;
664
665impl SemanticDiffEngine {
666 fn compute_diff(old_ast: &ParsedAst, new_ast: &ParsedAst) -> TreeResult<SemanticDiff> {
668 if old_ast.language != new_ast.language {
669 return Err(TreeError::DiffFailed {
670 reason: format!(
671 "Language mismatch: {} vs {}",
672 old_ast.language.as_str(),
673 new_ast.language.as_str()
674 ),
675 });
676 }
677
678 let old_root = old_ast.root_node();
679 let new_root = new_ast.root_node();
680
681 let mut added = Vec::new();
682 let mut removed = Vec::new();
683 let mut modified = Vec::new();
684 let mut moved = Vec::new();
685
686 let old_nodes = Self::collect_nodes(old_root, &old_ast.source_code);
688 let new_nodes = Self::collect_nodes(new_root, &new_ast.source_code);
689
690 let old_map: HashMap<String, Vec<DiffNode>> = Self::group_by_kind(&old_nodes);
692 let new_map: HashMap<String, Vec<DiffNode>> = Self::group_by_kind(&new_nodes);
693
694 for (kind, new_nodes_of_kind) in &new_map {
696 if let Some(old_nodes_of_kind) = old_map.get(kind) {
697 let (mod_pairs, add_nodes) =
699 Self::match_nodes(old_nodes_of_kind, new_nodes_of_kind);
700 modified.extend(mod_pairs);
701 added.extend(add_nodes);
702 } else {
703 added.extend(new_nodes_of_kind.clone());
705 }
706 }
707
708 for (kind, old_nodes_of_kind) in &old_map {
709 if !new_map.contains_key(kind) {
710 removed.extend(old_nodes_of_kind.clone());
712 }
713 }
714
715 moved.extend(Self::detect_moved_nodes(&old_nodes, &new_nodes));
717
718 let similarity_score = Self::calculate_similarity(&old_nodes, &new_nodes);
720
721 Ok(SemanticDiff {
722 added,
723 removed,
724 modified,
725 moved,
726 similarity_score,
727 })
728 }
729
730 fn collect_nodes(node: Node, source: &str) -> Vec<DiffNode> {
731 let mut nodes = Vec::new();
732 Self::collect_nodes_recursive(node, source, &mut nodes);
733 nodes
734 }
735
736 fn collect_nodes_recursive(node: Node, source: &str, nodes: &mut Vec<DiffNode>) {
737 let text = node
738 .utf8_text(source.as_bytes())
739 .unwrap_or("<invalid utf8>");
740
741 nodes.push(DiffNode {
742 kind: node.kind().to_string(),
743 text: text.to_string(),
744 start_point: node.start_position().into(),
745 end_point: node.end_position().into(),
746 });
747
748 for i in 0..node.child_count() {
749 if let Some(child) = node.child(i) {
750 Self::collect_nodes_recursive(child, source, nodes);
751 }
752 }
753 }
754
755 fn group_by_kind(nodes: &[DiffNode]) -> HashMap<String, Vec<DiffNode>> {
756 let mut map = HashMap::new();
757 for node in nodes {
758 map.entry(node.kind.clone())
759 .or_insert_with(Vec::new)
760 .push(node.clone());
761 }
762 map
763 }
764
765 fn match_nodes(
766 old_nodes: &[DiffNode],
767 new_nodes: &[DiffNode],
768 ) -> (Vec<ModifiedNode>, Vec<DiffNode>) {
769 let mut modified = Vec::new();
770 let mut added = Vec::new();
771 let mut used_old = vec![false; old_nodes.len()];
772
773 for new_node in new_nodes {
774 let mut best_match = None;
775 let mut best_similarity = 0.0;
776
777 for (i, old_node) in old_nodes.iter().enumerate() {
778 if used_old[i] {
779 continue;
780 }
781
782 let similarity = Self::text_similarity(&old_node.text, &new_node.text);
783 if similarity > best_similarity && similarity > 0.3 {
784 best_similarity = similarity;
785 best_match = Some(i);
786 }
787 }
788
789 if let Some(idx) = best_match {
790 used_old[idx] = true;
791 if best_similarity < 0.9 {
792 modified.push(ModifiedNode {
793 old: old_nodes[idx].clone(),
794 new: new_node.clone(),
795 similarity: best_similarity,
796 });
797 }
798 } else {
799 added.push(new_node.clone());
800 }
801 }
802
803 (modified, added)
804 }
805
806 fn detect_moved_nodes(old_nodes: &[DiffNode], new_nodes: &[DiffNode]) -> Vec<MovedNode> {
807 let mut moved = Vec::new();
808
809 for old_node in old_nodes {
810 for new_node in new_nodes {
811 if old_node.kind == new_node.kind
812 && old_node.text == new_node.text
813 && (old_node.start_point.row != new_node.start_point.row
814 || old_node.start_point.column != new_node.start_point.column)
815 {
816 moved.push(MovedNode {
817 node: new_node.clone(),
818 old_position: old_node.start_point,
819 new_position: new_node.start_point,
820 });
821 }
822 }
823 }
824
825 moved
826 }
827
828 fn text_similarity(text1: &str, text2: &str) -> f64 {
829 if text1 == text2 {
830 return 1.0;
831 }
832
833 let len1 = text1.len() as f64;
834 let len2 = text2.len() as f64;
835 let max_len = len1.max(len2);
836
837 if max_len == 0.0 {
838 return 1.0;
839 }
840
841 let common_chars = text1.chars().filter(|c| text2.contains(*c)).count() as f64;
843
844 common_chars / max_len
845 }
846
847 fn calculate_similarity(old_nodes: &[DiffNode], new_nodes: &[DiffNode]) -> f64 {
848 if old_nodes.is_empty() && new_nodes.is_empty() {
849 return 1.0;
850 }
851
852 let total_nodes = (old_nodes.len() + new_nodes.len()) as f64;
853 let common_nodes = old_nodes
854 .iter()
855 .filter(|old| new_nodes.iter().any(|new| old.text == new.text))
856 .count() as f64;
857
858 (common_nodes * 2.0) / total_nodes
859 }
860}
861
862struct QueryLibrary {
864 _queries: HashMap<String, HashMap<SupportedLanguage, String>>,
865}
866
867impl QueryLibrary {
868 fn new() -> Self {
869 let mut queries = HashMap::new();
870
871 let mut function_queries = HashMap::new();
873 function_queries.insert(
874 SupportedLanguage::Rust,
875 "(function_item name: (identifier) @name) @function".to_string(),
876 );
877 function_queries.insert(
878 SupportedLanguage::Python,
879 "(function_definition name: (identifier) @name) @function".to_string(),
880 );
881 function_queries.insert(
882 SupportedLanguage::JavaScript,
883 "(function_declaration name: (identifier) @name) @function".to_string(),
884 );
885 function_queries.insert(
886 SupportedLanguage::TypeScript,
887 "(function_declaration name: (identifier) @name) @function".to_string(),
888 );
889 function_queries.insert(
890 SupportedLanguage::Go,
891 "(function_declaration name: (identifier) @name) @function".to_string(),
892 );
893 function_queries.insert(
894 SupportedLanguage::Java,
895 "(method_declaration name: (identifier) @name) @function".to_string(),
896 );
897 queries.insert("functions".to_string(), function_queries);
898
899 let mut class_queries = HashMap::new();
901 class_queries.insert(
902 SupportedLanguage::Rust,
903 "(struct_item name: (type_identifier) @name) @class".to_string(),
904 );
905 class_queries.insert(
906 SupportedLanguage::Python,
907 "(class_definition name: (identifier) @name) @class".to_string(),
908 );
909 class_queries.insert(
910 SupportedLanguage::JavaScript,
911 "(class_declaration name: (identifier) @name) @class".to_string(),
912 );
913 class_queries.insert(
914 SupportedLanguage::TypeScript,
915 "(class_declaration name: (identifier) @name) @class".to_string(),
916 );
917 class_queries.insert(
918 SupportedLanguage::Java,
919 "(class_declaration name: (identifier) @name) @class".to_string(),
920 );
921 queries.insert("classes".to_string(), class_queries);
922
923 let mut variable_queries = HashMap::new();
925 variable_queries.insert(
926 SupportedLanguage::Rust,
927 "(let_declaration pattern: (identifier) @name) @variable".to_string(),
928 );
929 variable_queries.insert(
930 SupportedLanguage::Python,
931 "(assignment left: (identifier) @name) @variable".to_string(),
932 );
933 variable_queries.insert(
934 SupportedLanguage::JavaScript,
935 "(variable_declaration (variable_declarator name: (identifier) @name)) @variable"
936 .to_string(),
937 );
938 queries.insert("variables".to_string(), variable_queries);
939
940 Self { _queries: queries }
941 }
942
943 fn _get_query(&self, pattern_name: &str, language: SupportedLanguage) -> Option<&String> {
944 self._queries.get(pattern_name)?.get(&language)
945 }
946}
947
948pub struct TreeTool {
950 parsers: Arc<DashMap<SupportedLanguage, ThreadSafeParser>>,
952 ast_cache: Arc<Mutex<LruCache<CacheKey, (Arc<ParsedAst>, Instant)>>>,
954 _diff_engine: SemanticDiffEngine,
956 _query_library: QueryLibrary,
958 intelligence_level: IntelligenceLevel,
960 max_cache_size: NonZeroUsize,
962 cache_ttl: Duration,
964}
965
966impl TreeTool {
967 pub fn new(intelligence_level: IntelligenceLevel) -> TreeResult<Self> {
969 let (cache_size, cache_ttl) = match intelligence_level {
970 IntelligenceLevel::Light => (NonZeroUsize::new(100).unwrap(), Duration::from_secs(300)), IntelligenceLevel::Medium => {
972 (NonZeroUsize::new(500).unwrap(), Duration::from_secs(900))
973 } IntelligenceLevel::Hard => {
975 (NonZeroUsize::new(2000).unwrap(), Duration::from_secs(1800))
976 } };
978
979 let mut tool = Self {
980 parsers: Arc::new(DashMap::new()),
981 ast_cache: Arc::new(Mutex::new(LruCache::new(cache_size))),
982 _diff_engine: SemanticDiffEngine,
983 _query_library: QueryLibrary::new(),
984 intelligence_level,
985 max_cache_size: cache_size,
986 cache_ttl,
987 };
988
989 tool.initialize_parsers()?;
991
992 Ok(tool)
993 }
994
995 fn initialize_parsers(&mut self) -> TreeResult<()> {
997 let languages = SupportedLanguage::all_languages();
998
999 for &language in languages {
1000 let parser = ThreadSafeParser::new(language)?;
1001 self.parsers.insert(language, parser);
1002 debug!("Initialized parser for {}", language.as_str());
1003 }
1004
1005 info!("Initialized {} language parsers", languages.len());
1006 Ok(())
1007 }
1008
1009 pub async fn parse(
1011 &self,
1012 code: String,
1013 language: Option<SupportedLanguage>,
1014 file_path: Option<PathBuf>,
1015 ) -> TreeResult<Arc<ParsedAst>> {
1016 let start_time = Instant::now();
1017
1018 let detected_language = match language {
1020 Some(lang) => lang,
1021 None => {
1022 if let Some(ref path) = file_path {
1023 self.detect_language(path)?
1024 } else {
1025 return Err(TreeError::LanguageDetectionFailed {
1026 path: file_path.unwrap_or_else(|| PathBuf::from("unknown")),
1027 });
1028 }
1029 }
1030 };
1031
1032 let content_hash = self.hash_content(&code);
1034 let cache_key = CacheKey {
1035 content_hash,
1036 language: detected_language,
1037 };
1038
1039 {
1041 let mut cache = self.ast_cache.lock().map_err(|e| TreeError::CacheFailed {
1042 reason: format!("Cache lock failed: {}", e),
1043 })?;
1044
1045 if let Some((cached_ast, cached_time)) = cache.get(&cache_key) {
1046 if cached_time.elapsed() < self.cache_ttl {
1048 debug!(
1049 "Cache hit for {} code ({} bytes)",
1050 detected_language.as_str(),
1051 code.len()
1052 );
1053 return Ok(cached_ast.clone());
1054 } else {
1055 cache.pop(&cache_key);
1057 }
1058 }
1059 }
1060
1061 let parser_ref =
1063 self.parsers
1064 .get(&detected_language)
1065 .ok_or_else(|| TreeError::UnsupportedLanguage {
1066 language: detected_language.as_str().to_string(),
1067 })?;
1068
1069 let tree = parser_ref.parse(&code)?;
1071
1072 let parse_time = start_time.elapsed();
1073 let node_count = self.count_nodes(tree.root_node());
1074
1075 let parsed_ast = Arc::new(ParsedAst {
1076 tree,
1077 language: detected_language,
1078 source_code: code,
1079 parse_time,
1080 node_count,
1081 });
1082
1083 {
1085 let mut cache = self.ast_cache.lock().map_err(|e| TreeError::CacheFailed {
1086 reason: format!("Cache lock failed: {}", e),
1087 })?;
1088 cache.put(cache_key, (parsed_ast.clone(), Instant::now()));
1089 }
1090
1091 debug!(
1092 "Parsed {} code in {:?} ({} nodes)",
1093 detected_language.as_str(),
1094 parse_time,
1095 node_count
1096 );
1097
1098 Ok(parsed_ast)
1099 }
1100
1101 pub async fn query(
1103 &self,
1104 code: String,
1105 language: Option<SupportedLanguage>,
1106 pattern: String,
1107 file_path: Option<PathBuf>,
1108 ) -> TreeResult<Vec<QueryMatch>> {
1109 let start_time = Instant::now();
1110
1111 let parsed_ast = self.parse(code, language, file_path).await?;
1113
1114 let query = self.get_or_compile_query(&pattern, parsed_ast.language)?;
1116
1117 let mut cursor = QueryCursor::new();
1119 let mut results = Vec::new();
1120
1121 let mut matches_iter = cursor.matches(
1123 &query,
1124 parsed_ast.root_node(),
1125 parsed_ast.source_code.as_bytes(),
1126 );
1127
1128 loop {
1130 matches_iter.advance();
1131 let Some(query_match) = matches_iter.get() else {
1132 break;
1133 };
1134 let start_byte = query_match
1135 .captures
1136 .iter()
1137 .map(|c| c.node.start_byte())
1138 .min()
1139 .unwrap_or(0);
1140 let end_byte = query_match
1141 .captures
1142 .iter()
1143 .map(|c| c.node.end_byte())
1144 .max()
1145 .unwrap_or(0);
1146 let start_point = query_match
1147 .captures
1148 .iter()
1149 .map(|c| c.node.start_position())
1150 .min()
1151 .unwrap_or_default();
1152 let end_point = query_match
1153 .captures
1154 .iter()
1155 .map(|c| c.node.end_position())
1156 .max()
1157 .unwrap_or_default();
1158
1159 let captures: Vec<QueryCapture> = query_match
1160 .captures
1161 .iter()
1162 .map(|capture| {
1163 let node = capture.node;
1164 let capture_name = query.capture_names()[capture.index as usize].to_string();
1165 let text = node
1166 .utf8_text(parsed_ast.source_code.as_bytes())
1167 .unwrap_or("<invalid utf8>")
1168 .to_string();
1169
1170 QueryCapture {
1171 name: capture_name,
1172 text,
1173 start_byte: node.start_byte() as u32,
1174 end_byte: node.end_byte() as u32,
1175 start_point: node.start_position().into(),
1176 end_point: node.end_position().into(),
1177 }
1178 })
1179 .collect();
1180
1181 results.push(QueryMatch {
1182 pattern_index: query_match.pattern_index as u32,
1183 captures,
1184 start_byte: start_byte as u32,
1185 end_byte: end_byte as u32,
1186 start_point: start_point.into(),
1187 end_point: end_point.into(),
1188 });
1189 }
1190
1191 let query_time = start_time.elapsed();
1192 debug!(
1193 "Query executed in {:?}, found {} matches",
1194 query_time,
1195 results.len()
1196 );
1197
1198 Ok(results)
1199 }
1200
1201 pub async fn get_symbols(
1203 &self,
1204 code: String,
1205 language: Option<SupportedLanguage>,
1206 file_path: Option<PathBuf>,
1207 ) -> TreeResult<Vec<Symbol>> {
1208 let start_time = Instant::now();
1209
1210 let parsed_ast = self.parse(code, language, file_path).await?;
1212
1213 let symbols = match parsed_ast.language {
1215 SupportedLanguage::Rust => self.extract_rust_symbols(&parsed_ast)?,
1216 SupportedLanguage::Python => self.extract_python_symbols(&parsed_ast)?,
1217 SupportedLanguage::JavaScript | SupportedLanguage::TypeScript => {
1218 self.extract_js_symbols(&parsed_ast)?
1219 }
1220 SupportedLanguage::Go => self.extract_go_symbols(&parsed_ast)?,
1221 SupportedLanguage::Java => self.extract_java_symbols(&parsed_ast)?,
1222 SupportedLanguage::C | SupportedLanguage::Cpp => self.extract_c_symbols(&parsed_ast)?,
1223 SupportedLanguage::CSharp => self.extract_c_symbols(&parsed_ast)?, SupportedLanguage::Bash => self.extract_bash_symbols(&parsed_ast)?,
1225 SupportedLanguage::Html
1227 | SupportedLanguage::Css
1228 | SupportedLanguage::Json
1229 | SupportedLanguage::Yaml
1230 | SupportedLanguage::Toml
1231 | SupportedLanguage::Ruby
1232 | SupportedLanguage::Php
1233 | SupportedLanguage::Lua
1234 | SupportedLanguage::Haskell
1235 | SupportedLanguage::Elixir
1236 | SupportedLanguage::Scala
1237 | SupportedLanguage::Ocaml
1238 | SupportedLanguage::Clojure
1239 | SupportedLanguage::Zig
1240 | SupportedLanguage::Swift
1241 | SupportedLanguage::Kotlin
1242 | SupportedLanguage::ObjectiveC
1243 | SupportedLanguage::Dockerfile
1244 | SupportedLanguage::Hcl
1245 | SupportedLanguage::Nix
1246 | SupportedLanguage::Make
1247 | SupportedLanguage::Markdown
1248 | SupportedLanguage::Rst => {
1250 vec![]
1252 }
1253 };
1254
1255 let extraction_time = start_time.elapsed();
1256 debug!(
1257 "Extracted {} symbols in {:?}",
1258 symbols.len(),
1259 extraction_time
1260 );
1261
1262 Ok(symbols)
1263 }
1264
1265 pub async fn diff(
1267 &self,
1268 old_code: String,
1269 new_code: String,
1270 language: Option<SupportedLanguage>,
1271 ) -> TreeResult<SemanticDiff> {
1272 let start_time = Instant::now();
1273
1274 let old_ast = self.parse(old_code, language, None).await?;
1276 let new_ast = self.parse(new_code, language, None).await?;
1277
1278 if old_ast.language != new_ast.language {
1280 return Err(TreeError::DiffFailed {
1281 reason: format!(
1282 "Language mismatch: {} vs {}",
1283 old_ast.language.as_str(),
1284 new_ast.language.as_str()
1285 ),
1286 });
1287 }
1288
1289 let diff = self.compute_semantic_diff(&old_ast, &new_ast)?;
1291
1292 let diff_time = start_time.elapsed();
1293 debug!("Computed diff in {:?}", diff_time);
1294
1295 Ok(diff)
1296 }
1297
1298 pub async fn parse_file(&self, file_path: PathBuf) -> TreeResult<Arc<ParsedAst>> {
1300 let code = fs::read_to_string(&file_path)
1301 .await
1302 .map_err(|e| TreeError::FileReadFailed {
1303 path: file_path.clone(),
1304 reason: e.to_string(),
1305 })?;
1306
1307 self.parse(code, None, Some(file_path)).await
1308 }
1309
1310 pub async fn query_file(
1312 &self,
1313 file_path: PathBuf,
1314 pattern: String,
1315 ) -> TreeResult<Vec<QueryMatch>> {
1316 let code = fs::read_to_string(&file_path)
1317 .await
1318 .map_err(|e| TreeError::FileReadFailed {
1319 path: file_path.clone(),
1320 reason: e.to_string(),
1321 })?;
1322
1323 self.query(code, None, pattern, Some(file_path)).await
1324 }
1325
1326 pub async fn extract_symbols_from_file(&self, file_path: PathBuf) -> TreeResult<Vec<Symbol>> {
1328 let code = fs::read_to_string(&file_path)
1329 .await
1330 .map_err(|e| TreeError::FileReadFailed {
1331 path: file_path.clone(),
1332 reason: e.to_string(),
1333 })?;
1334
1335 self.get_symbols(code, None, Some(file_path)).await
1336 }
1337
1338 pub async fn diff_files(
1340 &self,
1341 old_file: PathBuf,
1342 new_file: PathBuf,
1343 ) -> TreeResult<SemanticDiff> {
1344 let old_code =
1345 fs::read_to_string(&old_file)
1346 .await
1347 .map_err(|e| TreeError::FileReadFailed {
1348 path: old_file,
1349 reason: e.to_string(),
1350 })?;
1351
1352 let new_code =
1353 fs::read_to_string(&new_file)
1354 .await
1355 .map_err(|e| TreeError::FileReadFailed {
1356 path: new_file,
1357 reason: e.to_string(),
1358 })?;
1359
1360 self.diff(old_code, new_code, None).await
1361 }
1362
1363 fn detect_language(&self, path: &Path) -> TreeResult<SupportedLanguage> {
1365 let extension = path
1366 .extension()
1367 .and_then(|ext| ext.to_str())
1368 .ok_or_else(|| TreeError::LanguageDetectionFailed {
1369 path: path.to_path_buf(),
1370 })?;
1371
1372 SupportedLanguage::from_extension(extension).ok_or_else(|| TreeError::UnsupportedLanguage {
1373 language: extension.to_string(),
1374 })
1375 }
1376
1377 fn get_or_compile_query(
1379 &self,
1380 pattern: &str,
1381 language: SupportedLanguage,
1382 ) -> TreeResult<Query> {
1383 let _cache_key = format!("{}:{}", language.as_str(), pattern);
1384
1385 let query = Query::new(&language.grammar(), pattern).map_err(|e| {
1387 TreeError::QueryCompilationFailed {
1388 query: pattern.to_string(),
1389 reason: e.to_string(),
1390 }
1391 })?;
1392
1393 debug!("Compiled query: {}", pattern);
1394
1395 Ok(query)
1396 }
1397
1398 fn hash_content(&self, content: &str) -> u64 {
1400 use std::collections::hash_map::DefaultHasher;
1401 use std::hash::Hash;
1402 use std::hash::Hasher;
1403
1404 let mut hasher = DefaultHasher::new();
1405 content.hash(&mut hasher);
1406 hasher.finish()
1407 }
1408
1409 fn count_nodes(&self, node: Node) -> usize {
1411 let mut count = 1; for i in 0..node.child_count() {
1413 if let Some(child) = node.child(i) {
1414 count += self.count_nodes(child);
1415 }
1416 }
1417 count
1418 }
1419
1420 const fn extract_rust_symbols(&self, _ast: &ParsedAst) -> TreeResult<Vec<Symbol>> {
1422 Ok(vec![])
1425 }
1426
1427 const fn extract_python_symbols(&self, _ast: &ParsedAst) -> TreeResult<Vec<Symbol>> {
1429 Ok(vec![])
1431 }
1432
1433 const fn extract_js_symbols(&self, _ast: &ParsedAst) -> TreeResult<Vec<Symbol>> {
1435 Ok(vec![])
1437 }
1438
1439 const fn extract_go_symbols(&self, _ast: &ParsedAst) -> TreeResult<Vec<Symbol>> {
1441 Ok(vec![])
1443 }
1444
1445 const fn extract_java_symbols(&self, _ast: &ParsedAst) -> TreeResult<Vec<Symbol>> {
1447 Ok(vec![])
1449 }
1450
1451 const fn extract_c_symbols(&self, _ast: &ParsedAst) -> TreeResult<Vec<Symbol>> {
1453 Ok(vec![])
1455 }
1456
1457 const fn extract_bash_symbols(&self, _ast: &ParsedAst) -> TreeResult<Vec<Symbol>> {
1459 Ok(vec![])
1461 }
1462
1463 fn compute_semantic_diff(
1465 &self,
1466 old_ast: &ParsedAst,
1467 new_ast: &ParsedAst,
1468 ) -> TreeResult<SemanticDiff> {
1469 SemanticDiffEngine::compute_diff(old_ast, new_ast)
1470 }
1471
1472 pub fn cache_stats(&self) -> Result<HashMap<String, serde_json::Value>, TreeError> {
1474 let mut stats = HashMap::new();
1475
1476 let ast_cache_size = self
1477 .ast_cache
1478 .lock()
1479 .map_err(|e| TreeError::CacheFailed {
1480 reason: format!("Cache lock failed: {}", e),
1481 })?
1482 .len();
1483
1484 stats.insert(
1485 "ast_cache_size".to_string(),
1486 serde_json::Value::Number(ast_cache_size.into()),
1487 );
1488
1489 stats.insert(
1490 "max_cache_size".to_string(),
1491 serde_json::Value::Number(self.max_cache_size.get().into()),
1492 );
1493
1494 Ok(stats)
1495 }
1496
1497 pub fn clear_caches(&self) -> TreeResult<()> {
1499 let mut ast_cache = self.ast_cache.lock().map_err(|e| TreeError::CacheFailed {
1500 reason: format!("Cache lock failed: {}", e),
1501 })?;
1502 ast_cache.clear();
1503
1504 info!("All caches cleared");
1505 Ok(())
1506 }
1507}
1508
1509#[async_trait::async_trait]
1510impl InternalTool for TreeTool {
1511 type Input = TreeInput;
1512 type Output = ToolOutput<TreeOutput>;
1513 type Error = TreeError;
1514
1515 async fn execute(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
1516 let start_time = Instant::now();
1517
1518 let (result, operation) = match input {
1519 TreeInput::Parse {
1520 code,
1521 language,
1522 file_path,
1523 } => {
1524 let ast = self.parse(code, language, file_path).await?;
1525 let output = TreeOutput::Parsed {
1526 language: ast.language,
1527 node_count: ast.node_count,
1528 has_errors: ast.has_errors(),
1529 error_count: ast.error_nodes().len(),
1530 parse_time_ms: ast.parse_time.as_millis() as u64,
1531 };
1532 (output, "parse")
1533 }
1534 TreeInput::ParseFile { file_path } => {
1535 let ast = self.parse_file(file_path).await?;
1536 let output = TreeOutput::Parsed {
1537 language: ast.language,
1538 node_count: ast.node_count,
1539 has_errors: ast.has_errors(),
1540 error_count: ast.error_nodes().len(),
1541 parse_time_ms: ast.parse_time.as_millis() as u64,
1542 };
1543 (output, "parse_file")
1544 }
1545 TreeInput::Query {
1546 code,
1547 language,
1548 pattern,
1549 file_path,
1550 } => {
1551 let matches = self.query(code, language, pattern, file_path).await?;
1552 let query_time = start_time.elapsed();
1553 let output = TreeOutput::QueryResults {
1554 total_matches: matches.len(),
1555 matches,
1556 query_time_ms: query_time.as_millis() as u64,
1557 };
1558 (output, "query")
1559 }
1560 TreeInput::QueryFile { file_path, pattern } => {
1561 let matches = self.query_file(file_path, pattern).await?;
1562 let query_time = start_time.elapsed();
1563 let output = TreeOutput::QueryResults {
1564 total_matches: matches.len(),
1565 matches,
1566 query_time_ms: query_time.as_millis() as u64,
1567 };
1568 (output, "query_file")
1569 }
1570 TreeInput::ExtractSymbols {
1571 code,
1572 language,
1573 file_path,
1574 } => {
1575 let symbols = self.get_symbols(code, language, file_path).await?;
1576 let extraction_time = start_time.elapsed();
1577 let output = TreeOutput::Symbols {
1578 total_symbols: symbols.len(),
1579 symbols,
1580 extraction_time_ms: extraction_time.as_millis() as u64,
1581 };
1582 (output, "extract_symbols")
1583 }
1584 TreeInput::ExtractSymbolsFromFile { file_path } => {
1585 let symbols = self.extract_symbols_from_file(file_path).await?;
1586 let extraction_time = start_time.elapsed();
1587 let output = TreeOutput::Symbols {
1588 total_symbols: symbols.len(),
1589 symbols,
1590 extraction_time_ms: extraction_time.as_millis() as u64,
1591 };
1592 (output, "extract_symbols_from_file")
1593 }
1594 TreeInput::Diff {
1595 old_code,
1596 new_code,
1597 language,
1598 } => {
1599 let diff = self.diff(old_code, new_code, language).await?;
1600 let diff_time = start_time.elapsed();
1601 let output = TreeOutput::Diff {
1602 diff,
1603 diff_time_ms: diff_time.as_millis() as u64,
1604 };
1605 (output, "diff")
1606 }
1607 TreeInput::DiffFiles { old_file, new_file } => {
1608 let diff = self.diff_files(old_file, new_file).await?;
1609 let diff_time = start_time.elapsed();
1610 let output = TreeOutput::Diff {
1611 diff,
1612 diff_time_ms: diff_time.as_millis() as u64,
1613 };
1614 (output, "diff_files")
1615 }
1616 };
1617
1618 let execution_time = start_time.elapsed();
1619 let cache_stats = self.cache_stats().unwrap_or_default();
1620
1621 let location = SourceLocation {
1623 file_path: "<tree-tool>".to_string(),
1624 start_line: 0,
1625 start_column: 0,
1626 end_line: 0,
1627 end_column: 0,
1628 byte_range: (0, 0),
1629 };
1630
1631 let mut output = ToolOutput::new(
1632 result,
1633 "tree",
1634 format!("Tree operation: {}", operation),
1635 location,
1636 );
1637
1638 output.metadata.parameters.insert(
1640 "execution_time_ms".to_string(),
1641 (execution_time.as_millis() as u64).to_string(),
1642 );
1643 output
1644 .metadata
1645 .parameters
1646 .insert("cache_stats".to_string(), format!("{:?}", cache_stats));
1647 output.metadata.parameters.insert(
1648 "intelligence_level".to_string(),
1649 match self.intelligence_level {
1650 IntelligenceLevel::Light => "light".to_string(),
1651 IntelligenceLevel::Medium => "medium".to_string(),
1652 IntelligenceLevel::Hard => "hard".to_string(),
1653 },
1654 );
1655
1656 Ok(output)
1657 }
1658
1659 fn metadata(&self) -> ToolMetadata {
1660 ToolMetadata {
1661 name: "TreeTool".to_string(),
1662 description: "Multi-language AST parsing and querying with tree-sitter".to_string(),
1663 version: "1.0.0".to_string(),
1664 author: "AGCodex".to_string(),
1665 }
1666 }
1667}
1668
1669#[cfg(test)]
1670mod tests {
1671 use super::*;
1672
1673 #[tokio::test]
1674 async fn test_rust_parsing() {
1675 let tool = TreeTool::new(IntelligenceLevel::Medium).expect("Failed to create TreeTool");
1676 let rust_code = r#"
1677 fn hello_world() -> String {
1678 "Hello, world!".to_string()
1679 }
1680
1681 struct Person {
1682 name: String,
1683 age: u32,
1684 }
1685
1686 impl Person {
1687 fn new(name: String, age: u32) -> Self {
1688 Self { name, age }
1689 }
1690 }
1691 "#;
1692
1693 let result = tool
1694 .parse(rust_code.to_string(), Some(SupportedLanguage::Rust), None)
1695 .await;
1696 assert!(
1697 result.is_ok(),
1698 "Failed to parse Rust code: {:?}",
1699 result.err()
1700 );
1701
1702 let ast = result.unwrap();
1703 assert_eq!(ast.language, SupportedLanguage::Rust);
1704 assert!(!ast.has_errors(), "AST has parse errors");
1705 assert!(
1706 ast.node_count > 10,
1707 "Expected more than 10 nodes, got {}",
1708 ast.node_count
1709 );
1710
1711 assert_eq!(ast.source_code, rust_code);
1713
1714 let invalid_code = "fn broken { invalid syntax }";
1716 let invalid_result = tool
1717 .parse(
1718 invalid_code.to_string(),
1719 Some(SupportedLanguage::Rust),
1720 None,
1721 )
1722 .await;
1723 assert!(
1724 invalid_result.is_ok(),
1725 "Should parse even with syntax errors"
1726 );
1727 let invalid_ast = invalid_result.unwrap();
1728 assert!(invalid_ast.has_errors(), "Should detect syntax errors");
1729 }
1730
1731 #[tokio::test]
1732 async fn test_language_detection() {
1733 let tool = TreeTool::new(IntelligenceLevel::Light).expect("Failed to create TreeTool");
1734
1735 assert_eq!(
1737 tool.detect_language(Path::new("test.rs")).unwrap(),
1738 SupportedLanguage::Rust
1739 );
1740 assert_eq!(
1741 tool.detect_language(Path::new("test.py")).unwrap(),
1742 SupportedLanguage::Python
1743 );
1744 assert_eq!(
1745 tool.detect_language(Path::new("test.js")).unwrap(),
1746 SupportedLanguage::JavaScript
1747 );
1748 assert_eq!(
1749 tool.detect_language(Path::new("test.ts")).unwrap(),
1750 SupportedLanguage::TypeScript
1751 );
1752 assert_eq!(
1753 tool.detect_language(Path::new("test.go")).unwrap(),
1754 SupportedLanguage::Go
1755 );
1756 assert_eq!(
1757 tool.detect_language(Path::new("test.java")).unwrap(),
1758 SupportedLanguage::Java
1759 );
1760
1761 assert_eq!(
1763 SupportedLanguage::from_extension("RS"),
1764 Some(SupportedLanguage::Rust)
1765 );
1766 assert_eq!(
1767 SupportedLanguage::from_extension("PY"),
1768 Some(SupportedLanguage::Python)
1769 );
1770
1771 assert!(tool.detect_language(Path::new("test.unknown")).is_err());
1773
1774 assert!(tool.detect_language(Path::new("Makefile")).is_err());
1776
1777 assert!(tool.detect_language(Path::new("test.tex")).is_err());
1779 }
1780
1781 #[tokio::test]
1782 async fn test_query_functionality() {
1783 let tool = TreeTool::new(IntelligenceLevel::Medium).expect("Failed to create TreeTool");
1784 let rust_code = r#"
1785 fn main() {
1786 println!("Hello, world!");
1787 }
1788
1789 fn helper() {
1790 println!("Helper function");
1791 }
1792
1793 pub fn public_func(x: i32) -> i32 {
1794 x * 2
1795 }
1796 "#;
1797
1798 let query_pattern = "(function_item name: (identifier) @func_name)";
1800 let result = tool
1801 .query(
1802 rust_code.to_string(),
1803 Some(SupportedLanguage::Rust),
1804 query_pattern.to_string(),
1805 None,
1806 )
1807 .await;
1808
1809 assert!(result.is_ok(), "Query failed: {:?}", result.err());
1810 let matches = result.unwrap();
1811 assert_eq!(
1812 matches.len(),
1813 3,
1814 "Should find 3 functions (main, helper, public_func)"
1815 );
1816
1817 for match_result in &matches {
1819 assert!(
1820 !match_result.captures.is_empty(),
1821 "Each match should have captures"
1822 );
1823 for capture in &match_result.captures {
1824 assert_eq!(
1825 capture.name, "func_name",
1826 "Capture name should be 'func_name'"
1827 );
1828 assert!(!capture.text.is_empty(), "Capture text should not be empty");
1829 }
1830 }
1831
1832 let func_names: Vec<String> = matches
1834 .iter()
1835 .flat_map(|m| m.captures.iter())
1836 .map(|c| c.text.clone())
1837 .collect();
1838
1839 assert!(
1840 func_names.contains(&"main".to_string()),
1841 "Should find 'main' function"
1842 );
1843 assert!(
1844 func_names.contains(&"helper".to_string()),
1845 "Should find 'helper' function"
1846 );
1847 assert!(
1848 func_names.contains(&"public_func".to_string()),
1849 "Should find 'public_func' function"
1850 );
1851
1852 let invalid_query = "(invalid syntax @broken";
1854 let invalid_result = tool
1855 .query(
1856 rust_code.to_string(),
1857 Some(SupportedLanguage::Rust),
1858 invalid_query.to_string(),
1859 None,
1860 )
1861 .await;
1862 assert!(invalid_result.is_err(), "Invalid query should fail");
1863 }
1864
1865 #[tokio::test]
1866 async fn test_cache_functionality() {
1867 let tool = TreeTool::new(IntelligenceLevel::Light).expect("Failed to create TreeTool");
1868 let code = "fn test() { println!(\"Testing cache\"); }".to_string();
1869
1870 let result1 = tool
1872 .parse(code.clone(), Some(SupportedLanguage::Rust), None)
1873 .await
1874 .expect("First parse failed");
1875
1876 let result2 = tool
1877 .parse(code.clone(), Some(SupportedLanguage::Rust), None)
1878 .await
1879 .expect("Second parse failed");
1880
1881 assert!(
1883 Arc::ptr_eq(&result1, &result2),
1884 "Results should be the same cached instance"
1885 );
1886
1887 let stats = tool.cache_stats().expect("Failed to get cache stats");
1888 let cache_size = stats
1889 .get("ast_cache_size")
1890 .expect("Missing ast_cache_size")
1891 .as_u64()
1892 .expect("ast_cache_size should be a number") as usize;
1893 assert_eq!(cache_size, 1, "Should have exactly 1 cached entry");
1894
1895 let different_code = "fn another() { let x = 42; }".to_string();
1897 let _result3 = tool
1898 .parse(different_code, Some(SupportedLanguage::Rust), None)
1899 .await
1900 .expect("Third parse failed");
1901
1902 let updated_stats = tool
1903 .cache_stats()
1904 .expect("Failed to get updated cache stats");
1905 let updated_cache_size = updated_stats
1906 .get("ast_cache_size")
1907 .expect("Missing ast_cache_size")
1908 .as_u64()
1909 .expect("ast_cache_size should be a number") as usize;
1910 assert_eq!(
1911 updated_cache_size, 2,
1912 "Should have 2 cached entries after parsing different code"
1913 );
1914
1915 tool.clear_caches().expect("Failed to clear caches");
1917 let cleared_stats = tool
1918 .cache_stats()
1919 .expect("Failed to get cleared cache stats");
1920 let cleared_cache_size = cleared_stats
1921 .get("ast_cache_size")
1922 .expect("Missing ast_cache_size")
1923 .as_u64()
1924 .expect("ast_cache_size should be a number") as usize;
1925 assert_eq!(
1926 cleared_cache_size, 0,
1927 "Cache should be empty after clearing"
1928 );
1929 }
1930
1931 #[tokio::test]
1932 async fn test_diff_functionality() {
1933 let tool = TreeTool::new(IntelligenceLevel::Medium).expect("Failed to create TreeTool");
1934
1935 let old_code = r#"
1936 fn calculate(x: i32) -> i32 {
1937 x * 2
1938 }
1939 "#
1940 .to_string();
1941
1942 let new_code = r#"
1943 fn calculate(x: i32, y: i32) -> i32 {
1944 x * y
1945 }
1946
1947 fn helper() -> String {
1948 "Helper".to_string()
1949 }
1950 "#
1951 .to_string();
1952
1953 let diff_result = tool
1954 .diff(old_code, new_code, Some(SupportedLanguage::Rust))
1955 .await;
1956
1957 assert!(
1958 diff_result.is_ok(),
1959 "Diff computation failed: {:?}",
1960 diff_result.err()
1961 );
1962 let diff = diff_result.unwrap();
1963
1964 assert!(
1966 !diff.added.is_empty() || !diff.modified.is_empty(),
1967 "Should detect additions or modifications"
1968 );
1969 assert!(
1970 diff.similarity_score >= 0.0 && diff.similarity_score <= 1.0,
1971 "Similarity score should be between 0 and 1"
1972 );
1973 }
1974
1975 #[tokio::test]
1976 async fn test_python_parsing() {
1977 let tool = TreeTool::new(IntelligenceLevel::Medium).expect("Failed to create TreeTool");
1978 let python_code = r#"
1979def greet(name):
1980 return f"Hello, {name}!"
1981
1982class Person:
1983 def __init__(self, name, age):
1984 self.name = name
1985 self.age = age
1986
1987 def introduce(self):
1988 return f"I'm {self.name}, {self.age} years old"
1989 "#;
1990
1991 let result = tool
1992 .parse(
1993 python_code.to_string(),
1994 Some(SupportedLanguage::Python),
1995 None,
1996 )
1997 .await;
1998
1999 assert!(
2000 result.is_ok(),
2001 "Failed to parse Python code: {:?}",
2002 result.err()
2003 );
2004 let ast = result.unwrap();
2005 assert_eq!(ast.language, SupportedLanguage::Python);
2006 assert!(!ast.has_errors(), "Python AST has parse errors");
2007 assert!(
2008 ast.node_count > 10,
2009 "Expected substantial node count for Python code"
2010 );
2011 }
2012
2013 #[tokio::test]
2014 async fn test_javascript_parsing() {
2015 let tool = TreeTool::new(IntelligenceLevel::Medium).expect("Failed to create TreeTool");
2016 let js_code = r#"
2017function greet(name) {
2018 return `Hello, ${name}!`;
2019}
2020
2021const arrow = (x, y) => x + y;
2022
2023class Calculator {
2024 constructor() {
2025 this.result = 0;
2026 }
2027
2028 add(value) {
2029 this.result += value;
2030 return this;
2031 }
2032}
2033 "#;
2034
2035 let result = tool
2036 .parse(
2037 js_code.to_string(),
2038 Some(SupportedLanguage::JavaScript),
2039 None,
2040 )
2041 .await;
2042
2043 assert!(
2044 result.is_ok(),
2045 "Failed to parse JavaScript code: {:?}",
2046 result.err()
2047 );
2048 let ast = result.unwrap();
2049 assert_eq!(ast.language, SupportedLanguage::JavaScript);
2050 assert!(!ast.has_errors(), "JavaScript AST has parse errors");
2051 assert!(
2052 ast.node_count > 10,
2053 "Expected substantial node count for JavaScript code"
2054 );
2055 }
2056}