coding_agent_tools 0.3.4

Coding agent tools (CLI + MCP). First tool: ls.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
//! Tool wrappers for `coding_agent_tools` using agentic-tools-core.
//!
//! Each tool delegates to the corresponding method on [`CodingAgentTools`].

use crate::CodingAgentTools;
use crate::just;
use crate::types::AgentLocation;
use crate::types::AgentOutput;
use crate::types::AgentType;
use crate::types::Depth;
use crate::types::GlobOutput;
use crate::types::GrepOutput;
use crate::types::LsOutput;
use crate::types::OutputMode;
use crate::types::Show;
use crate::types::SortOrder;
use agentic_config::types::CliToolsConfig;
use agentic_config::types::SubagentsConfig;
use agentic_tools_core::Tool;
use agentic_tools_core::ToolContext;
use agentic_tools_core::ToolError;
use agentic_tools_core::ToolRegistry;
use futures::future::BoxFuture;
use schemars::JsonSchema;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;

// ============================================================================
// Ls Tool
// ============================================================================

/// Input for the ls tool.
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct LsInput {
    /// Directory path (absolute or relative to cwd)
    #[serde(default)]
    pub path: Option<String>,
    /// Traversal depth: 0=header, 1=children (default), 2-10=tree
    #[serde(default)]
    pub depth: Option<Depth>,
    /// Filter: 'all' (default), 'files', or 'dirs'
    #[serde(default)]
    pub show: Option<Show>,
    /// Additional glob patterns to ignore
    #[serde(default)]
    pub ignore: Option<Vec<String>>,
    /// Include hidden files (default: false)
    #[serde(default)]
    pub hidden: Option<bool>,
}

/// Tool for listing files and directories.
#[derive(Clone)]
pub struct LsTool {
    tools: Arc<CodingAgentTools>,
}

impl LsTool {
    pub fn new(tools: Arc<CodingAgentTools>) -> Self {
        Self { tools }
    }
}

impl Tool for LsTool {
    type Input = LsInput;
    type Output = LsOutput;
    const NAME: &'static str = "cli_ls";
    const DESCRIPTION: &'static str = "List files and directories. Depth: 0=header only, 1=children (default), 2-10=tree. Filter with show='files'|'dirs'|'all'. Gitignore-aware. For shallow queries, call with same params again for next page.";

    fn call(
        &self,
        input: Self::Input,
        _ctx: &ToolContext,
    ) -> BoxFuture<'static, Result<Self::Output, ToolError>> {
        let tools = Arc::clone(&self.tools);
        Box::pin(async move {
            tools
                .ls(
                    input.path,
                    input.depth,
                    input.show,
                    input.ignore,
                    input.hidden,
                )
                .await
        })
    }
}

// ============================================================================
// AskAgent Tool
// ============================================================================

/// Input for the `ask_agent` tool.
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct AskAgentInput {
    /// Agent type: 'locator' (fast discovery, haiku) or 'analyzer' (deep analysis, sonnet). Default: locator
    #[serde(default)]
    pub agent_type: Option<AgentType>,
    /// Location: 'codebase'|'thoughts'|'references'|'web'. Default: codebase
    #[serde(default)]
    pub location: Option<AgentLocation>,
    /// Task to perform; plain language question/instructions for the subagent
    pub query: String,
}

/// Tool for spawning Claude subagents.
#[derive(Clone)]
pub struct AskAgentTool {
    tools: Arc<CodingAgentTools>,
}

impl AskAgentTool {
    pub fn new(tools: Arc<CodingAgentTools>) -> Self {
        Self { tools }
    }
}

impl Tool for AskAgentTool {
    type Input = AskAgentInput;
    type Output = AgentOutput;
    const NAME: &'static str = "ask_agent";
    const DESCRIPTION: &'static str = "Spawn a Claude subagent for discovery or deep analysis. Returns a single text response; no side effects.

Agent types:
- locator (haiku): Finds WHERE things are. Fast, shallow discovery via cli_grep/cli_glob/cli_ls. Returns file paths grouped by purpose. Cannot read file contents deeply.
- analyzer (sonnet): Explains HOW things work. Reads files, traces data flow, provides technical analysis. Must cite file:line for all claims.

Locations:
- codebase: Current repository. Paths are repo-relative.
- thoughts: Active branch documents (research/plans/artifacts). Uses thoughts_list_documents for discovery.
- references: Cloned reference repos. Paths start with references/{org}/{repo}/.
- web: Internet search. Returns URLs with quotes and source attribution.

When to use:
- Use locator when you need to find files/resources but don't yet know where they are.
- Use analyzer when you need to understand implementation details or extract specific information with citations.
- Use thoughts/references locations when the answer likely exists in existing documentation or external examples.
- Use web when you need external documentation, API references, or information not in the codebase.

When NOT to use:
- If you already know the file path, use Read directly.
- If you need a simple directory listing, use cli_ls.
- For pattern matching in known locations, use cli_grep or cli_glob.
- This tool cannot write files or make changes.

Usage notes:
- Provide clear, specific queries. The subagent is stateless and receives no prior context.
- Locator returns locations only; use analyzer or Read for content.
- Multiple ask_agent calls can run in parallel for independent queries.";

    fn call(
        &self,
        input: Self::Input,
        _ctx: &ToolContext,
    ) -> BoxFuture<'static, Result<Self::Output, ToolError>> {
        let tools = Arc::clone(&self.tools);
        Box::pin(async move {
            tools
                .ask_agent(input.agent_type, input.location, input.query)
                .await
        })
    }
}

// ============================================================================
// SearchGrep Tool
// ============================================================================

/// Input for the `search_grep` tool.
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct SearchGrepInput {
    /// Regex pattern to search for
    pub pattern: String,
    /// Directory path (absolute or relative to cwd)
    #[serde(default)]
    pub path: Option<String>,
    /// Output mode: 'files' (default), 'content', or 'count'
    #[serde(default)]
    pub mode: Option<OutputMode>,
    /// Include-only glob patterns (files to consider)
    #[serde(default)]
    pub globs: Option<Vec<String>>,
    /// Additional glob patterns to ignore (exclude)
    #[serde(default)]
    pub ignore: Option<Vec<String>>,
    /// Include hidden files (default: false)
    #[serde(default)]
    pub include_hidden: Option<bool>,
    /// Case-insensitive matching (default: false)
    #[serde(default)]
    pub case_insensitive: Option<bool>,
    /// Allow '.' to match newlines; patterns may span lines (default: false)
    #[serde(default)]
    pub multiline: Option<bool>,
    /// Show line numbers in content mode (default: true)
    #[serde(default)]
    pub line_numbers: Option<bool>,
    /// Context lines before and after matches (overridden by `context_before/after` if provided)
    #[serde(default)]
    pub context: Option<u32>,
    /// Context lines before match
    #[serde(default)]
    pub context_before: Option<u32>,
    /// Context lines after match
    #[serde(default)]
    pub context_after: Option<u32>,
    /// Search binary files as text (default: false)
    #[serde(default)]
    pub include_binary: Option<bool>,
    /// Max results to return (default: 200, capped at 1000)
    #[serde(default)]
    pub head_limit: Option<usize>,
    /// Skip the first N results (default: 0)
    #[serde(default)]
    pub offset: Option<usize>,
}

/// Tool for regex-based code search.
#[derive(Clone)]
pub struct SearchGrepTool {
    tools: Arc<CodingAgentTools>,
}

impl SearchGrepTool {
    pub fn new(tools: Arc<CodingAgentTools>) -> Self {
        Self { tools }
    }
}

impl Tool for SearchGrepTool {
    type Input = SearchGrepInput;
    type Output = GrepOutput;
    const NAME: &'static str = "cli_grep";
    const DESCRIPTION: &'static str = "Regex-based search. Modes: files (default), content, count. Stateless pagination via head_limit+offset.";

    fn call(
        &self,
        input: Self::Input,
        _ctx: &ToolContext,
    ) -> BoxFuture<'static, Result<Self::Output, ToolError>> {
        let tools = Arc::clone(&self.tools);
        Box::pin(async move {
            tools
                .search_grep(
                    input.pattern,
                    input.path,
                    input.mode,
                    input.globs,
                    input.ignore,
                    input.include_hidden,
                    input.case_insensitive,
                    input.multiline,
                    input.line_numbers,
                    input.context,
                    input.context_before,
                    input.context_after,
                    input.include_binary,
                    input.head_limit,
                    input.offset,
                )
                .await
        })
    }
}

// ============================================================================
// SearchGlob Tool
// ============================================================================

/// Input for the `search_glob` tool.
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct SearchGlobInput {
    /// Glob pattern to match against (e.g., '**/*.rs')
    pub pattern: String,
    /// Directory path (absolute or relative to cwd)
    #[serde(default)]
    pub path: Option<String>,
    /// Additional glob patterns to ignore (exclude)
    #[serde(default)]
    pub ignore: Option<Vec<String>>,
    /// Include hidden files (default: false)
    #[serde(default)]
    pub include_hidden: Option<bool>,
    /// Sort order: 'name' (default) or 'mtime' (newest first)
    #[serde(default)]
    pub sort: Option<SortOrder>,
    /// Max results to return (default: 500, capped at 1000)
    #[serde(default)]
    pub head_limit: Option<usize>,
    /// Skip the first N results (default: 0)
    #[serde(default)]
    pub offset: Option<usize>,
}

/// Tool for glob-based file matching.
#[derive(Clone)]
pub struct SearchGlobTool {
    tools: Arc<CodingAgentTools>,
}

impl SearchGlobTool {
    pub fn new(tools: Arc<CodingAgentTools>) -> Self {
        Self { tools }
    }
}

impl Tool for SearchGlobTool {
    type Input = SearchGlobInput;
    type Output = GlobOutput;
    const NAME: &'static str = "cli_glob";
    const DESCRIPTION: &'static str = "Glob-based path match. Sorting by name (default) or mtime (newest first). Stateless pagination via head_limit+offset.";

    fn call(
        &self,
        input: Self::Input,
        _ctx: &ToolContext,
    ) -> BoxFuture<'static, Result<Self::Output, ToolError>> {
        let tools = Arc::clone(&self.tools);
        Box::pin(async move {
            tools
                .search_glob(
                    input.pattern,
                    input.path,
                    input.ignore,
                    input.include_hidden,
                    input.sort,
                    input.head_limit,
                    input.offset,
                )
                .await
        })
    }
}

// ============================================================================
// JustSearch Tool
// ============================================================================

/// Input for the `just_search` tool.
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct JustSearchInput {
    /// Search query (substring match on name/docs)
    #[serde(default)]
    pub query: Option<String>,
    /// Directory filter (repo-relative or absolute)
    #[serde(default)]
    pub dir: Option<String>,
}

/// Tool for searching justfile recipes.
#[derive(Clone)]
pub struct JustSearchTool {
    tools: Arc<CodingAgentTools>,
}

impl JustSearchTool {
    pub fn new(tools: Arc<CodingAgentTools>) -> Self {
        Self { tools }
    }
}

impl Tool for JustSearchTool {
    type Input = JustSearchInput;
    type Output = just::SearchOutput;
    const NAME: &'static str = "cli_just_search";
    const DESCRIPTION: &'static str = "Search justfile recipes by name or docs. Optional dir filter. Same params => next page. Page size: 10.";

    fn call(
        &self,
        input: Self::Input,
        _ctx: &ToolContext,
    ) -> BoxFuture<'static, Result<Self::Output, ToolError>> {
        let tools = Arc::clone(&self.tools);
        Box::pin(async move { tools.just_search(input.query, input.dir).await })
    }
}

// ============================================================================
// JustExecute Tool
// ============================================================================

/// Input for the `just_execute` tool.
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct JustExecuteInput {
    /// Recipe name (e.g., 'check', 'test', 'build')
    pub recipe: String,
    /// Directory containing the justfile (optional; defaults to root if recipe exists there)
    #[serde(default)]
    pub dir: Option<String>,
    /// Arguments keyed by parameter name; star params accept arrays
    #[serde(default)]
    pub args: Option<HashMap<String, serde_json::Value>>,
}

/// Tool for executing justfile recipes.
#[derive(Clone)]
pub struct JustExecuteTool {
    tools: Arc<CodingAgentTools>,
}

impl JustExecuteTool {
    pub fn new(tools: Arc<CodingAgentTools>) -> Self {
        Self { tools }
    }
}

impl Tool for JustExecuteTool {
    type Input = JustExecuteInput;
    type Output = just::ExecuteOutput;
    const NAME: &'static str = "cli_just_execute";
    const DESCRIPTION: &'static str = "Execute a just recipe. Defaults to root justfile if no dir specified. Only disambiguate if recipe not in root.";

    fn call(
        &self,
        input: Self::Input,
        _ctx: &ToolContext,
    ) -> BoxFuture<'static, Result<Self::Output, ToolError>> {
        let tools = Arc::clone(&self.tools);
        Box::pin(async move {
            tools
                .just_execute(input.recipe, input.dir, input.args)
                .await
        })
    }
}

// ============================================================================
// Registry Builder
// ============================================================================

/// Build a `ToolRegistry` containing all `coding_agent_tools`.
pub fn build_registry(subagents: SubagentsConfig, cli_tools: CliToolsConfig) -> ToolRegistry {
    let tools = Arc::new(CodingAgentTools::with_config(subagents, cli_tools));
    ToolRegistry::builder()
        .register::<LsTool, ()>(LsTool::new(Arc::clone(&tools)))
        .register::<AskAgentTool, ()>(AskAgentTool::new(Arc::clone(&tools)))
        .register::<SearchGrepTool, ()>(SearchGrepTool::new(Arc::clone(&tools)))
        .register::<SearchGlobTool, ()>(SearchGlobTool::new(Arc::clone(&tools)))
        .register::<JustSearchTool, ()>(JustSearchTool::new(Arc::clone(&tools)))
        .register::<JustExecuteTool, ()>(JustExecuteTool::new(tools))
        .finish()
}

// Error conversion removed - methods now return agentic_tools_core::ToolError directly