syncable_cli/agent/tools/
analyze.rs

1//! Analyze tool - wraps the analyze command using Rig's Tool trait
2
3use super::compression::{CompressionConfig, compress_analysis_output};
4use rig::completion::ToolDefinition;
5use rig::tool::Tool;
6use serde::{Deserialize, Serialize};
7use serde_json::json;
8use std::path::PathBuf;
9
10/// Arguments for the analyze tool
11#[derive(Debug, Deserialize)]
12pub struct AnalyzeArgs {
13    /// Optional subdirectory path to analyze
14    pub path: Option<String>,
15}
16
17/// Error type for analyze tool
18#[derive(Debug, thiserror::Error)]
19#[error("Analysis error: {0}")]
20pub struct AnalyzeError(String);
21
22/// Tool to analyze a project
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct AnalyzeTool {
25    project_path: PathBuf,
26}
27
28impl AnalyzeTool {
29    pub fn new(project_path: PathBuf) -> Self {
30        Self { project_path }
31    }
32}
33
34impl Tool for AnalyzeTool {
35    const NAME: &'static str = "analyze_project";
36
37    type Error = AnalyzeError;
38    type Args = AnalyzeArgs;
39    type Output = String;
40
41    async fn definition(&self, _prompt: String) -> ToolDefinition {
42        ToolDefinition {
43            name: Self::NAME.to_string(),
44            description: "Analyze the project to detect programming languages, frameworks, dependencies, build tools, and architecture patterns. Returns a comprehensive overview of the project's technology stack.".to_string(),
45            parameters: json!({
46                "type": "object",
47                "properties": {
48                    "path": {
49                        "type": "string",
50                        "description": "Optional subdirectory path to analyze (relative to project root). If not provided, analyzes the entire project."
51                    }
52                }
53            }),
54        }
55    }
56
57    async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
58        let path = if let Some(subpath) = args.path {
59            self.project_path.join(subpath)
60        } else {
61            self.project_path.clone()
62        };
63
64        // Use monorepo analyzer to detect ALL projects in monorepos
65        // This returns MonorepoAnalysis with full project list instead of flat ProjectAnalysis
66        match crate::analyzer::analyze_monorepo(&path) {
67            Ok(analysis) => {
68                let json_value = serde_json::to_value(&analysis)
69                    .map_err(|e| AnalyzeError(format!("Failed to serialize: {}", e)))?;
70
71                // Use smart compression with RAG retrieval pattern
72                // This preserves all data while keeping context size manageable
73                let config = CompressionConfig::default();
74                Ok(compress_analysis_output(&json_value, &config))
75            }
76            Err(e) => Err(AnalyzeError(format!("Analysis failed: {}", e))),
77        }
78    }
79}