dirge-agent 0.13.0

Minimalistic coding agent written in Rust, optimized for memory footprint and performance
use std::path::PathBuf;
use std::sync::{Arc, RwLock};

use rig::completion::ToolDefinition;
use rig::tool::Tool;
use serde::Deserialize;

use crate::agent::tools::{AskSender, PermCheck, ToolError, check_perm};
use crate::semantic::SymbolIndex;

pub struct FindDefinitionTool {
    pub permission: Option<PermCheck>,
    pub ask_tx: Option<AskSender>,
    index: Arc<RwLock<SymbolIndex>>,
}

impl FindDefinitionTool {
    pub fn new(
        index: Arc<RwLock<SymbolIndex>>,
        permission: Option<PermCheck>,
        ask_tx: Option<AskSender>,
    ) -> Self {
        Self {
            permission,
            ask_tx,
            index,
        }
    }
}

#[derive(Deserialize)]
pub struct Args {
    name: String,
}

impl Tool for FindDefinitionTool {
    const NAME: &'static str = "find_definition";

    type Error = ToolError;
    type Args = Args;
    type Output = String;

    async fn definition(&self, _prompt: String) -> ToolDefinition {
        ToolDefinition {
            name: "find_definition".to_string(),
            description: "Find where a SYMBOL (function, class, type, etc.) is DEFINED across the project. Uses tree-sitter for precise structural matching. NOT for finding files by name — use `find_files` for that. NOT for content search — use `grep`.".to_string(),
            parameters: serde_json::json!({
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Name of the symbol to find"
                    }
                },
                "required": ["name"]
            }),
        }
    }

    async fn call(&self, args: Args) -> Result<String, ToolError> {
        check_perm(
            &self.permission,
            &self.ask_tx,
            "find_definition",
            &args.name,
        )
        .await?;

        let results = {
            let idx = self
                .index
                .read()
                .map_err(|e| ToolError::Msg(format!("Index read-lock error: {e}")))?;
            idx.ensure_all(
                &std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
                None,
            )
            .map_err(ToolError::Msg)?;
            idx.find_definition(&args.name).map_err(ToolError::Msg)?
        };

        if results.is_empty() {
            return Ok(format!("No definition found for '{}'", args.name));
        }

        let mut output = format!(
            "Found {} definition(s) for '{}':\n",
            results.len(),
            args.name
        );
        for (path, sym) in &results {
            output.push_str(&format!(
                "  {}:{} [{}] {}\n",
                path.display(),
                sym.range.start_line,
                sym.kind,
                sym.signature
            ));
        }

        Ok(output)
    }
}