vtcode_core/commands/
analyze.rs1use 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
11pub 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 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 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 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(_) => {} }
119 }
120
121 println!("{}", style("4. Analyzing source code structure...").dim());
123
124 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 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
188async 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); 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 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 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}