opendev_tools_impl/file_search/
ast_grep_tool.rs1use std::collections::HashMap;
4
5use opendev_tools_core::{BaseTool, ToolContext, ToolResult};
6
7use super::types::AstGrepArgs;
8use crate::dir_hints::list_available_dirs;
9use crate::path_utils::{resolve_dir_path, validate_path_access};
10
11#[derive(Debug)]
13pub struct AstGrepTool;
14
15#[async_trait::async_trait]
16impl BaseTool for AstGrepTool {
17 fn name(&self) -> &str {
18 "ast_grep"
19 }
20
21 fn description(&self) -> &str {
22 "Search code structurally using AST patterns via ast-grep. \
23 Use $VAR wildcards for structural matching (e.g., \"$A && $A()\"). \
24 $$$VAR matches multiple nodes (e.g., \"fn $NAME() { $$$BODY }\")."
25 }
26
27 fn parameter_schema(&self) -> serde_json::Value {
28 serde_json::json!({
29 "type": "object",
30 "properties": {
31 "pattern": {
32 "type": "string",
33 "description": "AST pattern with $VAR wildcards for structural matching"
34 },
35 "path": {
36 "type": "string",
37 "description": "File or directory to search in (defaults to working directory)"
38 },
39 "lang": {
40 "type": "string",
41 "description": "Language hint (e.g., 'rust', 'javascript', 'python'). Auto-detected from file extension if not specified."
42 },
43 "head_limit": {
44 "type": "number",
45 "description": "Limit output to first N matches"
46 }
47 },
48 "required": ["pattern"]
49 })
50 }
51
52 async fn execute(
53 &self,
54 args: HashMap<String, serde_json::Value>,
55 ctx: &ToolContext,
56 ) -> ToolResult {
57 let ast_args = match AstGrepArgs::from_map(&args) {
58 Ok(a) => a,
59 Err(e) => return ToolResult::fail(e),
60 };
61
62 let search_path = ast_args
63 .path
64 .as_deref()
65 .map(|p| resolve_dir_path(p, &ctx.working_dir))
66 .unwrap_or_else(|| ctx.working_dir.clone());
67
68 if let Err(msg) = validate_path_access(&search_path, &ctx.working_dir) {
69 return ToolResult::fail(msg);
70 }
71
72 if !search_path.exists() {
73 let available = list_available_dirs(&ctx.working_dir);
74 return ToolResult::fail(format!(
75 "Path not found: {}\n\nAvailable directories in working dir ({}):\n{}",
76 search_path.display(),
77 ctx.working_dir.display(),
78 available
79 ));
80 }
81
82 self.run_ast_grep(&ast_args, &search_path).await
83 }
84}