use crate::config::constants::tools;
use crate::config::types::{AgentConfig, AnalysisDepth, OutputFormat};
use crate::tools::ToolRegistry;
use crate::utils::colors::style;
use anyhow::Result;
use serde_json::json;
pub async fn handle_analyze_command(config: AgentConfig, depth: String, format: String) -> Result<()> {
println!("{}", style("Analyzing workspace...").cyan().bold());
let depth = match depth.to_lowercase().as_str() {
"basic" => AnalysisDepth::Basic,
"standard" => AnalysisDepth::Standard,
"deep" => AnalysisDepth::Deep,
_ => {
println!("{}", style("Invalid depth. Using 'standard'.").red());
AnalysisDepth::Standard
}
};
let _output_format = match format.to_lowercase().as_str() {
"text" => OutputFormat::Text,
"json" => OutputFormat::Json,
"html" => OutputFormat::Html,
_ => {
println!("{}", style("Invalid format. Using 'text'.").red());
OutputFormat::Text
}
};
let registry = ToolRegistry::new(config.workspace.clone()).await;
registry.mark_tool_preapproved(tools::LIST_FILES).await;
println!("{}", style("1. Getting workspace structure...").dim());
let root_files = registry
.execute_tool(
tools::LIST_FILES,
json!({"mode": "list", "path": ".", "per_page": 50, "max_items": 200, "include_hidden": false}),
)
.await;
let entry_names = match root_files {
Ok(result) => {
println!("{}", style("Root directory structure obtained").green());
let names = extract_entry_names(&result);
println!(" Found {} files/directories in root", names.len());
names
}
Err(e) => {
println!("{} {}", style("Failed to list root directory:").red(), e);
Vec::new()
}
};
println!("{}", style("2. Identifying project type...").dim());
let important_files = vec![
"README.md",
"Cargo.toml",
"package.json",
"go.mod",
"requirements.txt",
"Makefile",
];
for file in important_files {
if entry_names.iter().any(|name| name == file) {
println!(" {} {}", style("Detected").green(), file);
}
}
println!("{}", style("3. Reading project configuration...").dim());
let config_files = vec!["AGENTS.md", "README.md", "Cargo.toml", "package.json"];
for config_file in config_files {
let config_path = config.workspace.join(config_file);
let Ok(metadata) = tokio::fs::metadata(&config_path).await else {
continue;
};
println!(" {} {} ({} bytes)", style("Read").green(), config_file, metadata.len());
}
println!("{}", style("4. Analyzing source code structure...").dim());
let src_dirs = vec!["src", "lib", "pkg", "internal", "cmd"];
for dir in src_dirs {
if entry_names.iter().any(|name| name == dir) {
println!(" {} source directory: {}", style("Found").green(), dir);
}
}
if matches!(depth, AnalysisDepth::Deep) {
println!("{}", style("Deep analysis: use grep/search tools for detailed code inspection.").dim());
}
println!("{}", style("Workspace analysis complete!").green().bold());
println!("{}", style("You can now ask me specific questions about the codebase.").dim());
Ok(())
}
fn extract_entry_names(result: &serde_json::Value) -> Vec<String> {
result
.get("items")
.and_then(|items| items.as_array())
.map(|entries| {
entries
.iter()
.filter_map(|entry| entry.get("name").and_then(|name| name.as_str()))
.map(|name| name.to_string())
.collect()
})
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::extract_entry_names;
use serde_json::json;
#[test]
fn extracts_names_from_items_payload() {
let result = json!({
"success": true,
"items": [
{"name": "src", "path": "src", "type": "directory"},
{"name": "README.md", "path": "README.md", "type": "file"}
]
});
assert_eq!(extract_entry_names(&result), vec!["src", "README.md"]);
}
#[test]
fn returns_empty_for_empty_items() {
let result = json!({"success": true, "items": []});
assert!(extract_entry_names(&result).is_empty());
}
#[test]
fn returns_empty_when_items_field_missing() {
let result = json!({"success": true, "count": 0});
assert!(extract_entry_names(&result).is_empty());
}
}