vtcode_core/commands/
analyze.rs

1//! Analyze command implementation - workspace analysis
2
3use crate::config::constants::tools;
4use crate::config::types::{AgentConfig, AnalysisDepth, OutputFormat};
5use crate::tools::ToolRegistry;
6use crate::tools::tree_sitter::{CodeAnalyzer, TreeSitterAnalyzer};
7use anyhow::Result;
8use console::style;
9use serde_json::json;
10
11/// Handle the analyze command - comprehensive workspace analysis
12pub async fn handle_analyze_command(
13    config: AgentConfig,
14    depth: String,
15    format: String,
16) -> Result<()> {
17    println!("{}", style("Analyzing workspace...").cyan().bold());
18
19    let depth = match depth.to_lowercase().as_str() {
20        "basic" => AnalysisDepth::Basic,
21        "standard" => AnalysisDepth::Standard,
22        "deep" => AnalysisDepth::Deep,
23        _ => {
24            println!("{}", style("Invalid depth. Using 'standard'.").yellow());
25            AnalysisDepth::Standard
26        }
27    };
28
29    let _output_format = match format.to_lowercase().as_str() {
30        "text" => OutputFormat::Text,
31        "json" => OutputFormat::Json,
32        "html" => OutputFormat::Html,
33        _ => {
34            println!("{}", style("Invalid format. Using 'text'.").yellow());
35            OutputFormat::Text
36        }
37    };
38
39    let mut registry = ToolRegistry::new(config.workspace.clone());
40
41    // Step 1: Get high-level directory structure
42    println!("{}", style("1. Getting workspace structure...").dim());
43    let root_files = registry
44        .execute_tool(tools::LIST_FILES, json!({"path": ".", "max_items": 50}))
45        .await;
46
47    match root_files {
48        Ok(result) => {
49            println!("{}", style("Root directory structure obtained").green());
50            if let Some(files_array) = result.get("files") {
51                println!(
52                    "   Found {} files/directories in root",
53                    files_array.as_array().unwrap_or(&vec![]).len()
54                );
55            }
56        }
57        Err(e) => println!("{} {}", style("Failed to list root directory:").red(), e),
58    }
59
60    // Step 2: Look for important project files
61    println!("{}", style("2. Identifying project type...").dim());
62    let important_files = vec![
63        "README.md",
64        "Cargo.toml",
65        "package.json",
66        "go.mod",
67        "requirements.txt",
68        "Makefile",
69    ];
70
71    for file in important_files {
72        let check_file = registry
73            .execute_tool(
74                tools::LIST_FILES,
75                json!({"path": ".", "include_hidden": false}),
76            )
77            .await;
78        if let Ok(result) = check_file {
79            if let Some(files) = result.get("files") {
80                if let Some(files_array) = files.as_array() {
81                    for file_obj in files_array {
82                        if let Some(path) = file_obj.get("path") {
83                            if path.as_str().unwrap_or("") == file {
84                                println!("   {} Detected: {}", style("Detected").green(), file);
85                                break;
86                            }
87                        }
88                    }
89                }
90            }
91        }
92    }
93
94    // Step 3: Read key configuration files
95    println!("{}", style("3. Reading project configuration...").dim());
96    let config_files = vec!["AGENTS.md", "README.md", "Cargo.toml", "package.json"];
97
98    for config_file in config_files {
99        let read_result = registry
100            .execute_tool(
101                tools::READ_FILE,
102                json!({"path": config_file, "max_bytes": 2000}),
103            )
104            .await;
105        match read_result {
106            Ok(result) => {
107                println!(
108                    "   {} Read {} ({} bytes)",
109                    style("Read").green(),
110                    config_file,
111                    result
112                        .get("metadata")
113                        .and_then(|m| m.get("size"))
114                        .unwrap_or(&serde_json::json!(null))
115                );
116            }
117            Err(_) => {} // File doesn't exist, that's ok
118        }
119    }
120
121    // Step 4: Analyze source code structure
122    println!("{}", style("4. Analyzing source code structure...").dim());
123
124    // Check for common source directories
125    let src_dirs = vec!["src", "lib", "pkg", "internal", "cmd"];
126    for dir in src_dirs {
127        let check_dir = registry
128            .execute_tool(
129                tools::LIST_FILES,
130                json!({"path": ".", "include_hidden": false}),
131            )
132            .await;
133        if let Ok(result) = check_dir {
134            if let Some(files) = result.get("files") {
135                if let Some(files_array) = files.as_array() {
136                    for file_obj in files_array {
137                        if let Some(path) = file_obj.get("path") {
138                            if path.as_str().unwrap_or("") == dir {
139                                println!(
140                                    "   {} Found source directory: {}",
141                                    style("Found").green(),
142                                    dir
143                                );
144                                break;
145                            }
146                        }
147                    }
148                }
149            }
150        }
151    }
152
153    // Step 6: Research-preview code analysis with tree-sitter (for deep analysis)
154    if matches!(depth, AnalysisDepth::Deep) {
155        println!(
156            "{}",
157            style("6. Research-preview code analysis with tree-sitter...").yellow()
158        );
159        match perform_tree_sitter_analysis(&config).await {
160            Ok(_) => println!(
161                "   {} Tree-sitter analysis complete",
162                style("Complete").green()
163            ),
164            Err(e) => println!(
165                "   {} Tree-sitter analysis failed: {}",
166                style("Failed").red(),
167                e
168            ),
169        }
170    }
171
172    println!("{}", style("Workspace analysis complete!").green().bold());
173    println!(
174        "{}",
175        style("You can now ask me specific questions about the codebase.").dim()
176    );
177
178    if matches!(depth, AnalysisDepth::Deep) {
179        println!(
180            "{}",
181            style("Research-preview analysis available with tree-sitter integration.").dim()
182        );
183    }
184
185    Ok(())
186}
187
188/// Perform Research-preview code analysis using tree-sitter
189async fn perform_tree_sitter_analysis(config: &AgentConfig) -> Result<()> {
190    use crate::tools::tree_sitter::analyzer::LanguageSupport;
191
192    let mut analyzer = TreeSitterAnalyzer::new()?;
193    let code_analyzer = CodeAnalyzer::new(&LanguageSupport::Rust); // Default to Rust
194
195    // Find code files to analyze
196    let mut registry = ToolRegistry::new(config.workspace.clone());
197    let list_result = registry
198        .execute_tool(tools::LIST_FILES, json!({"path": ".", "recursive": true}))
199        .await?;
200
201    if let Some(files) = list_result.get("files") {
202        if let Some(files_array) = files.as_array() {
203            let mut analyzed_files = 0;
204            let mut total_lines = 0;
205            let mut total_functions = 0;
206
207            for file_obj in files_array {
208                if let Some(path) = file_obj.get("path").and_then(|p| p.as_str()) {
209                    if path.ends_with(".rs") {
210                        // Analyze Rust files
211                        match analyzer.parse_file(std::path::Path::new(path)) {
212                            Ok(syntax_tree) => {
213                                let analysis = code_analyzer.analyze(&syntax_tree, path);
214                                analyzed_files += 1;
215                                total_lines += analysis.metrics.lines_of_code;
216                                total_functions += analysis.metrics.functions_count;
217
218                                if config.verbose {
219                                    println!(
220                                        "     Analyzed {}: {} lines, {} functions",
221                                        path,
222                                        analysis.metrics.lines_of_code,
223                                        analysis.metrics.functions_count
224                                    );
225
226                                    if !analysis.issues.is_empty() {
227                                        println!("       {} issues found", analysis.issues.len());
228                                    }
229                                }
230                            }
231                            Err(e) => {
232                                if config.verbose {
233                                    println!("     Failed to analyze {}: {}", path, e);
234                                }
235                            }
236                        }
237                    }
238                }
239            }
240
241            if analyzed_files > 0 {
242                println!(
243                    "     Analyzed {} files: {} total lines, {} functions",
244                    analyzed_files, total_lines, total_functions
245                );
246
247                // Calculate quality metrics for the project
248                let avg_lines_per_function = if total_functions > 0 {
249                    total_lines as f64 / total_functions as f64
250                } else {
251                    0.0
252                };
253
254                println!(
255                    "     Average lines per function: {:.1}",
256                    avg_lines_per_function
257                );
258
259                if avg_lines_per_function > 50.0 {
260                    println!("       Consider breaking down large functions");
261                }
262            }
263        }
264    }
265
266    Ok(())
267}