//! `cd` tool: verify and resolve a workspace-relative directory path. use super::super::Tool; use super::super::ToolCtx; use anyhow::Result; use serde_json::{json, Value}; /// Tool that resolves a workspace-relative path and reports whether it exists and is a dir. pub struct Cd; impl Tool for Cd { fn name(&self) -> &'static str { "cd" } fn description(&self) -> &'static str { "Check if a directory exists within the workspace and print its resolved path. Use this to verify a directory path before running other commands there." } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "path": { "type": "string", "description": "Directory path (relative to workspace root)" } }, "required": ["path"] }) } /// Resolve `path` against the workspace roots and report its status. /// /// Flow: extract `path` → `resolve_path` (sandboxed to `ctx.workspaces`) → /// check `exists()` and `is_dir()` → canonicalize → return canonical path. /// /// Why: the agent has no persistent cwd between tool calls; "cd" here is purely a /// verification + canonicalization helper rather than a state change. /// /// Return: canonical path on success; explicit "does not exist" / "not a directory" /// message (still `Ok`) so the model can react without treating it as an error. fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let rel = crate::tool::arg_str(args, "path")?; let path = super::super::resolve_path(&ctx.workspaces, &rel)?; if !path.exists() { return Ok(super::path_not_found(&rel, &path)); } if !path.is_dir() { return Ok(super::path_not_a_directory(&rel, &path)); } let canon = path.canonicalize().unwrap_or(path); Ok(format!("{}", canon.display())) } }