use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tokio::fs;
use tracing::{debug, info};
use crate::core::CoderLibError;
use crate::tools::{Tool, ToolError, ToolResponse, Permission};
use crate::integration::HostIntegration;
pub struct ProjectStructureTool {
project_cache: std::sync::RwLock<HashMap<PathBuf, ProjectAnalysis>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectAnalysis {
pub root: PathBuf,
pub project_type: ProjectType,
pub build_system: BuildSystem,
pub dependencies: DependencyInfo,
pub structure: ProjectStructure,
pub workflow: DevelopmentWorkflow,
pub metrics: QualityMetrics,
pub analyzed_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum ProjectType {
Rust,
Node,
Python,
Go,
Java,
Cpp,
CSharp,
Mixed(Vec<ProjectType>),
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildSystem {
pub primary_tool: String,
pub config_files: Vec<PathBuf>,
pub targets: Vec<BuildTarget>,
pub build_dependencies: Vec<String>,
pub platforms: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildTarget {
pub name: String,
pub description: Option<String>,
pub target_type: TargetType,
pub command: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TargetType {
Build,
Test,
Run,
Clean,
Install,
Deploy,
Lint,
Format,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencyInfo {
pub production: Vec<Dependency>,
pub development: Vec<Dependency>,
pub optional: Vec<Dependency>,
pub tree_depth: usize,
pub total_count: usize,
pub outdated: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dependency {
pub name: String,
pub version: String,
pub latest_version: Option<String>,
pub dep_type: DependencyType,
pub license: Option<String>,
pub repository: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DependencyType {
Production,
Development,
Optional,
Peer,
Build,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectStructure {
pub source_dirs: Vec<PathBuf>,
pub test_dirs: Vec<PathBuf>,
pub doc_dirs: Vec<PathBuf>,
pub config_dirs: Vec<PathBuf>,
pub asset_dirs: Vec<PathBuf>,
pub important_files: Vec<PathBuf>,
pub file_types: HashMap<String, usize>,
pub total_loc: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DevelopmentWorkflow {
pub vcs: Option<String>,
pub ci_cd: Vec<CiCdConfig>,
pub quality_tools: Vec<QualityTool>,
pub dev_scripts: Vec<DevScript>,
pub environments: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CiCdConfig {
pub platform: String,
pub config_file: PathBuf,
pub workflows: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityTool {
pub name: String,
pub config_file: Option<PathBuf>,
pub tool_type: QualityToolType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum QualityToolType {
Linter,
Formatter,
TypeChecker,
SecurityScanner,
TestCoverage,
Documentation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DevScript {
pub name: String,
pub command: String,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityMetrics {
pub complexity_score: f64,
pub test_coverage: Option<f64>,
pub doc_coverage: Option<f64>,
pub dependency_health: f64,
pub security_score: Option<f64>,
pub maintainability: f64,
}
impl Default for ProjectStructureTool {
fn default() -> Self {
Self::new()
}
}
impl ProjectStructureTool {
pub fn new() -> Self {
Self {
project_cache: std::sync::RwLock::new(HashMap::new()),
}
}
pub async fn analyze_project(&self, project_root: &Path) -> Result<ProjectAnalysis, CoderLibError> {
info!("Analyzing project structure at: {}", project_root.display());
if let Ok(cache) = self.project_cache.read() {
if let Some(cached) = cache.get(project_root) {
let age = chrono::Utc::now() - cached.analyzed_at;
if age.num_hours() < 1 {
debug!("Using cached project analysis");
return Ok(cached.clone());
}
}
}
let analysis = self.perform_analysis(project_root).await?;
if let Ok(mut cache) = self.project_cache.write() {
cache.insert(project_root.to_path_buf(), analysis.clone());
}
Ok(analysis)
}
async fn perform_analysis(&self, project_root: &Path) -> Result<ProjectAnalysis, CoderLibError> {
let project_type = self.detect_project_type(project_root).await?;
let build_system = self.analyze_build_system(project_root, &project_type).await?;
let dependencies = self.analyze_dependencies(project_root, &project_type).await?;
let structure = self.analyze_structure(project_root).await?;
let workflow = self.analyze_workflow(project_root).await?;
let metrics = self.calculate_metrics(project_root, &structure, &dependencies).await?;
Ok(ProjectAnalysis {
root: project_root.to_path_buf(),
project_type,
build_system,
dependencies,
structure,
workflow,
metrics,
analyzed_at: chrono::Utc::now(),
})
}
async fn detect_project_type(&self, project_root: &Path) -> Result<ProjectType, CoderLibError> {
let mut detected_types = Vec::new();
let indicators = vec![
("Cargo.toml", ProjectType::Rust),
("package.json", ProjectType::Node),
("setup.py", ProjectType::Python),
("pyproject.toml", ProjectType::Python),
("go.mod", ProjectType::Go),
("pom.xml", ProjectType::Java),
("build.gradle", ProjectType::Java),
("CMakeLists.txt", ProjectType::Cpp),
("Makefile", ProjectType::Cpp),
("*.csproj", ProjectType::CSharp),
("*.sln", ProjectType::CSharp),
];
for (indicator, project_type) in indicators {
if indicator.contains('*') {
let pattern = indicator.replace('*', "");
if let Ok(mut entries) = fs::read_dir(project_root).await {
while let Some(entry) = entries.next_entry().await.unwrap_or(None) {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(&pattern) {
detected_types.push(project_type.clone());
break;
}
}
}
}
} else {
let file_path = project_root.join(indicator);
if file_path.exists() {
detected_types.push(project_type);
}
}
}
detected_types.sort();
detected_types.dedup();
match detected_types.len() {
0 => Ok(ProjectType::Unknown),
1 => Ok(detected_types.into_iter().next().unwrap()),
_ => Ok(ProjectType::Mixed(detected_types)),
}
}
fn analyze_build_system<'a>(&'a self, project_root: &'a Path, project_type: &'a ProjectType) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<BuildSystem, CoderLibError>> + Send + 'a>> {
Box::pin(async move {
match project_type {
ProjectType::Rust => self.analyze_cargo_build(project_root).await,
ProjectType::Node => self.analyze_npm_build(project_root).await,
ProjectType::Python => self.analyze_python_build(project_root).await,
ProjectType::Go => self.analyze_go_build(project_root).await,
ProjectType::Java => self.analyze_java_build(project_root).await,
ProjectType::Cpp => self.analyze_cpp_build(project_root).await,
ProjectType::CSharp => self.analyze_csharp_build(project_root).await,
ProjectType::Mixed(types) => {
if let Some(first_type) = types.first() {
self.analyze_build_system(project_root, first_type).await
} else {
Ok(self.default_build_system())
}
}
ProjectType::Unknown => Ok(self.default_build_system()),
}
})
}
async fn analyze_cargo_build(&self, project_root: &Path) -> Result<BuildSystem, CoderLibError> {
let cargo_toml = project_root.join("Cargo.toml");
let mut targets = vec![
BuildTarget {
name: "build".to_string(),
description: Some("Build the project".to_string()),
target_type: TargetType::Build,
command: "cargo build".to_string(),
},
BuildTarget {
name: "test".to_string(),
description: Some("Run tests".to_string()),
target_type: TargetType::Test,
command: "cargo test".to_string(),
},
BuildTarget {
name: "run".to_string(),
description: Some("Run the project".to_string()),
target_type: TargetType::Run,
command: "cargo run".to_string(),
},
BuildTarget {
name: "clean".to_string(),
description: Some("Clean build artifacts".to_string()),
target_type: TargetType::Clean,
command: "cargo clean".to_string(),
},
BuildTarget {
name: "check".to_string(),
description: Some("Check code without building".to_string()),
target_type: TargetType::Custom("check".to_string()),
command: "cargo check".to_string(),
},
BuildTarget {
name: "fmt".to_string(),
description: Some("Format code".to_string()),
target_type: TargetType::Format,
command: "cargo fmt".to_string(),
},
BuildTarget {
name: "clippy".to_string(),
description: Some("Run Clippy linter".to_string()),
target_type: TargetType::Lint,
command: "cargo clippy".to_string(),
},
];
if let Ok(content) = fs::read_to_string(&cargo_toml).await {
if content.contains("[[bin]]") {
targets.push(BuildTarget {
name: "build-bins".to_string(),
description: Some("Build all binaries".to_string()),
target_type: TargetType::Build,
command: "cargo build --bins".to_string(),
});
}
if content.contains("[lib]") {
targets.push(BuildTarget {
name: "build-lib".to_string(),
description: Some("Build library".to_string()),
target_type: TargetType::Build,
command: "cargo build --lib".to_string(),
});
}
}
Ok(BuildSystem {
primary_tool: "cargo".to_string(),
config_files: vec![cargo_toml],
targets,
build_dependencies: vec!["rustc".to_string(), "cargo".to_string()],
platforms: vec!["linux".to_string(), "windows".to_string(), "macos".to_string()],
})
}
async fn analyze_npm_build(&self, project_root: &Path) -> Result<BuildSystem, CoderLibError> {
let package_json = project_root.join("package.json");
let mut targets = Vec::new();
if let Ok(content) = fs::read_to_string(&package_json).await {
if let Ok(package) = serde_json::from_str::<serde_json::Value>(&content) {
if let Some(scripts) = package.get("scripts").and_then(|s| s.as_object()) {
for (name, _command) in scripts {
let target_type = match name.as_str() {
"build" => TargetType::Build,
"test" => TargetType::Test,
"start" | "dev" => TargetType::Run,
"clean" => TargetType::Clean,
"lint" => TargetType::Lint,
"format" | "fmt" => TargetType::Format,
_ => TargetType::Custom(name.clone()),
};
targets.push(BuildTarget {
name: name.clone(),
description: None,
target_type,
command: format!("npm run {}", name),
});
}
}
}
}
Ok(BuildSystem {
primary_tool: "npm".to_string(),
config_files: vec![package_json],
targets,
build_dependencies: vec!["node".to_string(), "npm".to_string()],
platforms: vec!["linux".to_string(), "windows".to_string(), "macos".to_string()],
})
}
async fn analyze_python_build(&self, project_root: &Path) -> Result<BuildSystem, CoderLibError> {
let mut config_files = Vec::new();
let mut primary_tool = "python".to_string();
if project_root.join("pyproject.toml").exists() {
config_files.push(project_root.join("pyproject.toml"));
primary_tool = "poetry".to_string();
}
if project_root.join("setup.py").exists() {
config_files.push(project_root.join("setup.py"));
}
if project_root.join("requirements.txt").exists() {
config_files.push(project_root.join("requirements.txt"));
}
let targets = vec![
BuildTarget {
name: "install".to_string(),
description: Some("Install dependencies".to_string()),
target_type: TargetType::Install,
command: "pip install -r requirements.txt".to_string(),
},
BuildTarget {
name: "test".to_string(),
description: Some("Run tests".to_string()),
target_type: TargetType::Test,
command: "python -m pytest".to_string(),
},
BuildTarget {
name: "lint".to_string(),
description: Some("Run linter".to_string()),
target_type: TargetType::Lint,
command: "flake8".to_string(),
},
BuildTarget {
name: "format".to_string(),
description: Some("Format code".to_string()),
target_type: TargetType::Format,
command: "black .".to_string(),
},
];
Ok(BuildSystem {
primary_tool,
config_files,
targets,
build_dependencies: vec!["python".to_string(), "pip".to_string()],
platforms: vec!["linux".to_string(), "windows".to_string(), "macos".to_string()],
})
}
async fn analyze_go_build(&self, project_root: &Path) -> Result<BuildSystem, CoderLibError> {
let go_mod = project_root.join("go.mod");
let targets = vec![
BuildTarget {
name: "build".to_string(),
description: Some("Build the project".to_string()),
target_type: TargetType::Build,
command: "go build".to_string(),
},
BuildTarget {
name: "test".to_string(),
description: Some("Run tests".to_string()),
target_type: TargetType::Test,
command: "go test ./...".to_string(),
},
BuildTarget {
name: "run".to_string(),
description: Some("Run the project".to_string()),
target_type: TargetType::Run,
command: "go run .".to_string(),
},
BuildTarget {
name: "clean".to_string(),
description: Some("Clean build cache".to_string()),
target_type: TargetType::Clean,
command: "go clean".to_string(),
},
BuildTarget {
name: "mod-tidy".to_string(),
description: Some("Tidy module dependencies".to_string()),
target_type: TargetType::Custom("mod".to_string()),
command: "go mod tidy".to_string(),
},
];
Ok(BuildSystem {
primary_tool: "go".to_string(),
config_files: vec![go_mod],
targets,
build_dependencies: vec!["go".to_string()],
platforms: vec!["linux".to_string(), "windows".to_string(), "macos".to_string()],
})
}
async fn analyze_java_build(&self, project_root: &Path) -> Result<BuildSystem, CoderLibError> {
let mut config_files = Vec::new();
let mut primary_tool = "javac".to_string();
let mut targets = Vec::new();
if project_root.join("pom.xml").exists() {
config_files.push(project_root.join("pom.xml"));
primary_tool = "maven".to_string();
targets = vec![
BuildTarget {
name: "compile".to_string(),
description: Some("Compile the project".to_string()),
target_type: TargetType::Build,
command: "mvn compile".to_string(),
},
BuildTarget {
name: "test".to_string(),
description: Some("Run tests".to_string()),
target_type: TargetType::Test,
command: "mvn test".to_string(),
},
BuildTarget {
name: "package".to_string(),
description: Some("Package the project".to_string()),
target_type: TargetType::Build,
command: "mvn package".to_string(),
},
BuildTarget {
name: "clean".to_string(),
description: Some("Clean build artifacts".to_string()),
target_type: TargetType::Clean,
command: "mvn clean".to_string(),
},
];
} else if project_root.join("build.gradle").exists() {
config_files.push(project_root.join("build.gradle"));
primary_tool = "gradle".to_string();
targets = vec![
BuildTarget {
name: "build".to_string(),
description: Some("Build the project".to_string()),
target_type: TargetType::Build,
command: "./gradlew build".to_string(),
},
BuildTarget {
name: "test".to_string(),
description: Some("Run tests".to_string()),
target_type: TargetType::Test,
command: "./gradlew test".to_string(),
},
BuildTarget {
name: "clean".to_string(),
description: Some("Clean build artifacts".to_string()),
target_type: TargetType::Clean,
command: "./gradlew clean".to_string(),
},
];
}
Ok(BuildSystem {
primary_tool,
config_files,
targets,
build_dependencies: vec!["java".to_string(), "javac".to_string()],
platforms: vec!["linux".to_string(), "windows".to_string(), "macos".to_string()],
})
}
async fn analyze_cpp_build(&self, project_root: &Path) -> Result<BuildSystem, CoderLibError> {
let mut config_files = Vec::new();
let mut primary_tool = "make".to_string();
if project_root.join("CMakeLists.txt").exists() {
config_files.push(project_root.join("CMakeLists.txt"));
primary_tool = "cmake".to_string();
}
if project_root.join("Makefile").exists() {
config_files.push(project_root.join("Makefile"));
}
let targets = vec![
BuildTarget {
name: "build".to_string(),
description: Some("Build the project".to_string()),
target_type: TargetType::Build,
command: if primary_tool == "cmake" { "cmake --build ." } else { "make" }.to_string(),
},
BuildTarget {
name: "clean".to_string(),
description: Some("Clean build artifacts".to_string()),
target_type: TargetType::Clean,
command: if primary_tool == "cmake" { "cmake --build . --target clean" } else { "make clean" }.to_string(),
},
BuildTarget {
name: "install".to_string(),
description: Some("Install the project".to_string()),
target_type: TargetType::Install,
command: if primary_tool == "cmake" { "cmake --install ." } else { "make install" }.to_string(),
},
];
Ok(BuildSystem {
primary_tool,
config_files,
targets,
build_dependencies: vec!["gcc".to_string(), "g++".to_string(), "make".to_string()],
platforms: vec!["linux".to_string(), "windows".to_string(), "macos".to_string()],
})
}
async fn analyze_csharp_build(&self, project_root: &Path) -> Result<BuildSystem, CoderLibError> {
let mut config_files = Vec::new();
if let Ok(mut entries) = fs::read_dir(project_root).await {
while let Some(entry) = entries.next_entry().await.unwrap_or(None) {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".csproj") || name.ends_with(".sln") {
config_files.push(entry.path());
}
}
}
}
let targets = vec![
BuildTarget {
name: "build".to_string(),
description: Some("Build the project".to_string()),
target_type: TargetType::Build,
command: "dotnet build".to_string(),
},
BuildTarget {
name: "test".to_string(),
description: Some("Run tests".to_string()),
target_type: TargetType::Test,
command: "dotnet test".to_string(),
},
BuildTarget {
name: "run".to_string(),
description: Some("Run the project".to_string()),
target_type: TargetType::Run,
command: "dotnet run".to_string(),
},
BuildTarget {
name: "clean".to_string(),
description: Some("Clean build artifacts".to_string()),
target_type: TargetType::Clean,
command: "dotnet clean".to_string(),
},
BuildTarget {
name: "restore".to_string(),
description: Some("Restore dependencies".to_string()),
target_type: TargetType::Install,
command: "dotnet restore".to_string(),
},
];
Ok(BuildSystem {
primary_tool: "dotnet".to_string(),
config_files,
targets,
build_dependencies: vec!["dotnet".to_string()],
platforms: vec!["linux".to_string(), "windows".to_string(), "macos".to_string()],
})
}
async fn analyze_dependencies(&self, project_root: &Path, project_type: &ProjectType) -> Result<DependencyInfo, CoderLibError> {
match project_type {
ProjectType::Rust => self.analyze_cargo_dependencies(project_root).await,
ProjectType::Node => self.analyze_npm_dependencies(project_root).await,
ProjectType::Python => self.analyze_python_dependencies(project_root).await,
_ => Ok(DependencyInfo {
production: Vec::new(),
development: Vec::new(),
optional: Vec::new(),
tree_depth: 0,
total_count: 0,
outdated: Vec::new(),
}),
}
}
async fn analyze_cargo_dependencies(&self, project_root: &Path) -> Result<DependencyInfo, CoderLibError> {
let cargo_toml = project_root.join("Cargo.toml");
let mut production = Vec::new();
let mut development = Vec::new();
if let Ok(content) = fs::read_to_string(&cargo_toml).await {
if let Ok(toml) = content.parse::<toml::Value>() {
if let Some(deps) = toml.get("dependencies").and_then(|d| d.as_table()) {
for (name, version) in deps {
let version_str = match version {
toml::Value::String(v) => v.clone(),
toml::Value::Table(t) => {
t.get("version").and_then(|v| v.as_str()).unwrap_or("*").to_string()
}
_ => "*".to_string(),
};
production.push(Dependency {
name: name.clone(),
version: version_str,
latest_version: None,
dep_type: DependencyType::Production,
license: None,
repository: None,
});
}
}
if let Some(dev_deps) = toml.get("dev-dependencies").and_then(|d| d.as_table()) {
for (name, version) in dev_deps {
let version_str = match version {
toml::Value::String(v) => v.clone(),
toml::Value::Table(t) => {
t.get("version").and_then(|v| v.as_str()).unwrap_or("*").to_string()
}
_ => "*".to_string(),
};
development.push(Dependency {
name: name.clone(),
version: version_str,
latest_version: None,
dep_type: DependencyType::Development,
license: None,
repository: None,
});
}
}
}
}
let total_count = production.len() + development.len();
Ok(DependencyInfo {
production,
development,
optional: Vec::new(),
tree_depth: 1, total_count,
outdated: Vec::new(),
})
}
async fn analyze_npm_dependencies(&self, project_root: &Path) -> Result<DependencyInfo, CoderLibError> {
let package_json = project_root.join("package.json");
let mut production = Vec::new();
let mut development = Vec::new();
if let Ok(content) = fs::read_to_string(&package_json).await {
if let Ok(package) = serde_json::from_str::<serde_json::Value>(&content) {
if let Some(deps) = package.get("dependencies").and_then(|d| d.as_object()) {
for (name, version) in deps {
production.push(Dependency {
name: name.clone(),
version: version.as_str().unwrap_or("*").to_string(),
latest_version: None,
dep_type: DependencyType::Production,
license: None,
repository: None,
});
}
}
if let Some(dev_deps) = package.get("devDependencies").and_then(|d| d.as_object()) {
for (name, version) in dev_deps {
development.push(Dependency {
name: name.clone(),
version: version.as_str().unwrap_or("*").to_string(),
latest_version: None,
dep_type: DependencyType::Development,
license: None,
repository: None,
});
}
}
}
}
let total_count = production.len() + development.len();
Ok(DependencyInfo {
production,
development,
optional: Vec::new(),
tree_depth: 1,
total_count,
outdated: Vec::new(),
})
}
async fn analyze_python_dependencies(&self, project_root: &Path) -> Result<DependencyInfo, CoderLibError> {
let mut production = Vec::new();
let requirements_txt = project_root.join("requirements.txt");
if let Ok(content) = fs::read_to_string(&requirements_txt).await {
for line in content.lines() {
let line = line.trim();
if !line.is_empty() && !line.starts_with('#') {
let parts: Vec<&str> = line.split("==").collect();
let name = parts[0].trim().to_string();
let version = parts.get(1).unwrap_or(&"*").trim().to_string();
production.push(Dependency {
name,
version,
latest_version: None,
dep_type: DependencyType::Production,
license: None,
repository: None,
});
}
}
}
let total_count = production.len();
Ok(DependencyInfo {
production,
development: Vec::new(),
optional: Vec::new(),
tree_depth: 1,
total_count,
outdated: Vec::new(),
})
}
async fn analyze_structure(&self, project_root: &Path) -> Result<ProjectStructure, CoderLibError> {
let mut source_dirs = Vec::new();
let mut test_dirs = Vec::new();
let mut doc_dirs = Vec::new();
let mut config_dirs = Vec::new();
let mut asset_dirs = Vec::new();
let mut important_files = Vec::new();
let mut file_types = HashMap::new();
let mut total_loc = 0;
let source_patterns = vec!["src", "lib", "source", "app"];
let test_patterns = vec!["test", "tests", "__tests__", "spec"];
let doc_patterns = vec!["doc", "docs", "documentation"];
let config_patterns = vec!["config", "conf", ".config"];
let asset_patterns = vec!["assets", "static", "public", "resources"];
self.scan_directory(
project_root,
&mut source_dirs,
&mut test_dirs,
&mut doc_dirs,
&mut config_dirs,
&mut asset_dirs,
&mut important_files,
&mut file_types,
&mut total_loc,
&source_patterns,
&test_patterns,
&doc_patterns,
&config_patterns,
&asset_patterns,
0,
).await?;
Ok(ProjectStructure {
source_dirs,
test_dirs,
doc_dirs,
config_dirs,
asset_dirs,
important_files,
file_types,
total_loc,
})
}
fn scan_directory<'a>(
&'a self,
dir: &'a Path,
source_dirs: &'a mut Vec<PathBuf>,
test_dirs: &'a mut Vec<PathBuf>,
doc_dirs: &'a mut Vec<PathBuf>,
config_dirs: &'a mut Vec<PathBuf>,
asset_dirs: &'a mut Vec<PathBuf>,
important_files: &'a mut Vec<PathBuf>,
file_types: &'a mut HashMap<String, usize>,
total_loc: &'a mut usize,
source_patterns: &'a [&str],
test_patterns: &'a [&str],
doc_patterns: &'a [&str],
config_patterns: &'a [&str],
asset_patterns: &'a [&str],
depth: usize,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), CoderLibError>> + Send + 'a>> {
Box::pin(async move {
if depth > 3 {
return Ok(());
}
if let Ok(mut entries) = fs::read_dir(dir).await {
while let Some(entry) = entries.next_entry().await.unwrap_or(None) {
let path = entry.path();
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
if path.is_dir() {
if name.starts_with('.') || name == "node_modules" || name == "target" {
continue;
}
if source_patterns.iter().any(|&p| name.contains(p)) {
source_dirs.push(path.clone());
} else if test_patterns.iter().any(|&p| name.contains(p)) {
test_dirs.push(path.clone());
} else if doc_patterns.iter().any(|&p| name.contains(p)) {
doc_dirs.push(path.clone());
} else if config_patterns.iter().any(|&p| name.contains(p)) {
config_dirs.push(path.clone());
} else if asset_patterns.iter().any(|&p| name.contains(p)) {
asset_dirs.push(path.clone());
}
self.scan_directory(
&path,
source_dirs,
test_dirs,
doc_dirs,
config_dirs,
asset_dirs,
important_files,
file_types,
total_loc,
source_patterns,
test_patterns,
doc_patterns,
config_patterns,
asset_patterns,
depth + 1,
).await?;
} else if path.is_file() {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
*file_types.entry(ext.to_string()).or_insert(0) += 1;
}
let important_patterns = vec![
"README", "LICENSE", "CHANGELOG", "CONTRIBUTING",
"Cargo.toml", "package.json", "setup.py", "go.mod",
"pom.xml", "build.gradle", "CMakeLists.txt", "Makefile",
];
if important_patterns.iter().any(|&p| name.contains(p)) {
important_files.push(path.clone());
}
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
let code_extensions = vec!["rs", "js", "ts", "py", "go", "java", "c", "cpp", "h", "hpp"];
if code_extensions.contains(&ext) {
if let Ok(content) = fs::read_to_string(&path).await {
*total_loc += content.lines().count();
}
}
}
}
}
}
Ok(())
})
}
async fn analyze_workflow(&self, project_root: &Path) -> Result<DevelopmentWorkflow, CoderLibError> {
let mut ci_cd = Vec::new();
let mut quality_tools = Vec::new();
let dev_scripts = Vec::new();
let mut environments = Vec::new();
let vcs = if project_root.join(".git").exists() {
Some("git".to_string())
} else {
None
};
let ci_configs = vec![
(".github/workflows", "GitHub Actions"),
(".gitlab-ci.yml", "GitLab CI"),
("azure-pipelines.yml", "Azure Pipelines"),
("Jenkinsfile", "Jenkins"),
(".travis.yml", "Travis CI"),
(".circleci/config.yml", "CircleCI"),
];
for (path, platform) in ci_configs {
let config_path = project_root.join(path);
if config_path.exists() {
ci_cd.push(CiCdConfig {
platform: platform.to_string(),
config_file: config_path,
workflows: Vec::new(), });
}
}
let quality_configs = vec![
(".eslintrc", "ESLint", QualityToolType::Linter),
("prettier.config.js", "Prettier", QualityToolType::Formatter),
("clippy.toml", "Clippy", QualityToolType::Linter),
("rustfmt.toml", "rustfmt", QualityToolType::Formatter),
("pyproject.toml", "Black", QualityToolType::Formatter),
(".flake8", "Flake8", QualityToolType::Linter),
("tslint.json", "TSLint", QualityToolType::Linter),
];
for (config_file, tool_name, tool_type) in quality_configs {
let config_path = project_root.join(config_file);
if config_path.exists() {
quality_tools.push(QualityTool {
name: tool_name.to_string(),
config_file: Some(config_path),
tool_type,
});
}
}
let env_files = vec![
".env", ".env.local", ".env.development", ".env.production",
"docker-compose.yml", "Dockerfile",
];
for env_file in env_files {
if project_root.join(env_file).exists() {
environments.push(env_file.to_string());
}
}
Ok(DevelopmentWorkflow {
vcs,
ci_cd,
quality_tools,
dev_scripts,
environments,
})
}
async fn calculate_metrics(
&self,
_project_root: &Path,
structure: &ProjectStructure,
dependencies: &DependencyInfo,
) -> Result<QualityMetrics, CoderLibError> {
let complexity_score = {
let file_count = structure.file_types.values().sum::<usize>() as f64;
let loc = structure.total_loc as f64;
let base_score = 100.0 - (file_count.log10() * 10.0 + loc.log10() * 5.0);
base_score.max(0.0).min(100.0)
};
let dependency_health = {
let total_deps = dependencies.total_count as f64;
let outdated_count = dependencies.outdated.len() as f64;
if total_deps == 0.0 {
100.0
} else {
let health_ratio = (total_deps - outdated_count) / total_deps;
health_ratio * 100.0
}
};
let maintainability = {
let has_tests = !structure.test_dirs.is_empty();
let has_docs = !structure.doc_dirs.is_empty();
let has_readme = structure.important_files.iter()
.any(|f| f.file_name().unwrap_or_default().to_str().unwrap_or("").contains("README"));
let mut score: f64 = 50.0; if has_tests { score += 20.0; }
if has_docs { score += 15.0; }
if has_readme { score += 15.0; }
score.min(100.0)
};
Ok(QualityMetrics {
complexity_score,
test_coverage: None, doc_coverage: None, dependency_health,
security_score: None, maintainability,
})
}
fn default_build_system(&self) -> BuildSystem {
BuildSystem {
primary_tool: "unknown".to_string(),
config_files: Vec::new(),
targets: Vec::new(),
build_dependencies: Vec::new(),
platforms: Vec::new(),
}
}
}
#[async_trait]
impl Tool for ProjectStructureTool {
async fn execute(
&self,
parameters: serde_json::Value,
_host: &dyn HostIntegration,
) -> Result<ToolResponse, ToolError> {
let project_path = parameters.get("path")
.and_then(|p| p.as_str())
.ok_or_else(|| ToolError::InvalidParameters("Missing 'path' argument".to_string()))?;
let project_root = Path::new(project_path);
if !project_root.exists() {
return Err(ToolError::ExecutionFailed(format!("Project path does not exist: {}", project_path)));
}
match self.analyze_project(project_root).await {
Ok(analysis) => {
let result = serde_json::to_value(&analysis)
.map_err(|e| ToolError::ExecutionFailed(format!("Failed to serialize analysis: {}", e)))?;
Ok(ToolResponse {
content: serde_json::to_string_pretty(&result).unwrap_or_else(|_| "Analysis completed".to_string()),
success: true,
metadata: result,
affected_files: Vec::new(),
})
}
Err(e) => Err(ToolError::ExecutionFailed(format!("Analysis failed: {}", e))),
}
}
fn requires_permission(&self) -> Permission {
Permission::None }
fn description(&self) -> &str {
"Analyze project structure, build systems, dependencies, and development workflows"
}
fn name(&self) -> &str {
"project_structure"
}
fn parameter_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the project root directory"
}
},
"required": ["path"]
})
}
fn clone_box(&self) -> Box<dyn Tool> {
Box::new(Self::new())
}
}