batuta/
tools.rs

1#![allow(dead_code)]
2
3use anyhow::{Context, Result};
4use std::process::Command;
5
6#[cfg(feature = "native")]
7use tracing::{debug, info};
8
9// Stub macros for WASM build
10#[cfg(not(feature = "native"))]
11macro_rules! info {
12    ($($arg:tt)*) => {{}};
13}
14
15#[cfg(not(feature = "native"))]
16macro_rules! debug {
17    ($($arg:tt)*) => {{}};
18}
19
20/// Detected tool information
21#[derive(Debug, Clone)]
22pub struct ToolInfo {
23    pub name: String,
24    pub version: Option<String>,
25    pub path: String,
26    pub available: bool,
27}
28
29/// Check which transpiler and utility tools are available
30#[derive(Debug, Clone)]
31pub struct ToolRegistry {
32    pub decy: Option<ToolInfo>,
33    pub depyler: Option<ToolInfo>,
34    pub bashrs: Option<ToolInfo>,
35    pub ruchy: Option<ToolInfo>,
36    pub trueno: Option<ToolInfo>,
37    pub aprender: Option<ToolInfo>,
38    pub realizar: Option<ToolInfo>,
39    pub renacer: Option<ToolInfo>,
40    pub pmat: Option<ToolInfo>,
41}
42
43impl ToolRegistry {
44    /// Detect all available tools
45    pub fn detect() -> Self {
46        info!("Detecting installed Pragmatic AI Labs tools...");
47
48        Self {
49            decy: detect_tool("decy"),
50            depyler: detect_tool("depyler"),
51            bashrs: detect_tool("bashrs"),
52            ruchy: detect_tool("ruchy"),
53            trueno: detect_tool("trueno"),
54            aprender: detect_tool("aprender"),
55            realizar: detect_tool("realizar"),
56            renacer: detect_tool("renacer"),
57            pmat: detect_tool("pmat"),
58        }
59    }
60
61    /// Check if any transpiler is available
62    pub fn has_transpiler(&self) -> bool {
63        self.decy.is_some() || self.depyler.is_some() || self.bashrs.is_some()
64    }
65
66    /// Get transpiler for a specific language
67    pub fn get_transpiler_for_language(&self, lang: &crate::types::Language) -> Option<&ToolInfo> {
68        use crate::types::Language;
69
70        match lang {
71            Language::C | Language::Cpp => self.decy.as_ref(),
72            Language::Python => self.depyler.as_ref(),
73            Language::Shell => self.bashrs.as_ref(),
74            _ => None,
75        }
76    }
77
78    /// Get list of available tools
79    pub fn available_tools(&self) -> Vec<String> {
80        let mut tools = Vec::new();
81
82        if let Some(tool) = &self.decy {
83            if tool.available {
84                tools.push("Decy (C/C++ → Rust)".to_string());
85            }
86        }
87        if let Some(tool) = &self.depyler {
88            if tool.available {
89                tools.push("Depyler (Python → Rust)".to_string());
90            }
91        }
92        if let Some(tool) = &self.bashrs {
93            if tool.available {
94                tools.push("Bashrs (Shell → Rust)".to_string());
95            }
96        }
97        if let Some(tool) = &self.ruchy {
98            if tool.available {
99                tools.push("Ruchy (Rust scripting)".to_string());
100            }
101        }
102        if let Some(tool) = &self.pmat {
103            if tool.available {
104                tools.push("PMAT (Quality analysis)".to_string());
105            }
106        }
107        if let Some(tool) = &self.trueno {
108            if tool.available {
109                tools.push("Trueno (Multi-target compute)".to_string());
110            }
111        }
112        if let Some(tool) = &self.aprender {
113            if tool.available {
114                tools.push("Aprender (ML library)".to_string());
115            }
116        }
117        if let Some(tool) = &self.realizar {
118            if tool.available {
119                tools.push("Realizar (Inference runtime)".to_string());
120            }
121        }
122        if let Some(tool) = &self.renacer {
123            if tool.available {
124                tools.push("Renacer (Syscall tracing)".to_string());
125            }
126        }
127
128        tools
129    }
130
131    /// Get installation instructions for missing tools
132    pub fn get_installation_instructions(&self, needed_tools: &[&str]) -> Vec<String> {
133        let mut instructions = Vec::new();
134
135        for tool in needed_tools {
136            let instruction = match *tool {
137                "decy" if self.decy.is_none() => Some("Install Decy: cargo install decy"),
138                "depyler" if self.depyler.is_none() => {
139                    Some("Install Depyler: cargo install depyler")
140                }
141                "bashrs" if self.bashrs.is_none() => Some("Install Bashrs: cargo install bashrs"),
142                "ruchy" if self.ruchy.is_none() => Some("Install Ruchy: cargo install ruchy"),
143                "pmat" if self.pmat.is_none() => Some("Install PMAT: cargo install pmat"),
144                "trueno" if self.trueno.is_none() => {
145                    Some("Install Trueno: Add 'trueno' to Cargo.toml dependencies")
146                }
147                "aprender" if self.aprender.is_none() => {
148                    Some("Install Aprender: Add 'aprender' to Cargo.toml dependencies")
149                }
150                "realizar" if self.realizar.is_none() => {
151                    Some("Install Realizar: Add 'realizar' to Cargo.toml dependencies")
152                }
153                "renacer" if self.renacer.is_none() => {
154                    Some("Install Renacer: cargo install renacer")
155                }
156                _ => None,
157            };
158
159            if let Some(inst) = instruction {
160                instructions.push(inst.to_string());
161            }
162        }
163
164        instructions
165    }
166}
167
168/// Detect a single tool
169fn detect_tool(name: &str) -> Option<ToolInfo> {
170    debug!("Checking for tool: {}", name);
171
172    // Try to find the tool using `which`
173    let path = match which::which(name) {
174        Ok(p) => p.to_string_lossy().to_string(),
175        Err(_) => {
176            debug!("Tool '{}' not found in PATH", name);
177            return None;
178        }
179    };
180
181    // Try to get version
182    let version = get_tool_version(name);
183
184    debug!(
185        "Found tool '{}' at '{}' (version: {:?})",
186        name, path, version
187    );
188
189    Some(ToolInfo {
190        name: name.to_string(),
191        version,
192        path,
193        available: true,
194    })
195}
196
197/// Get tool version by running --version
198fn get_tool_version(name: &str) -> Option<String> {
199    let output = Command::new(name).arg("--version").output().ok()?;
200
201    if !output.status.success() {
202        return None;
203    }
204
205    let stdout = String::from_utf8_lossy(&output.stdout);
206    let version_line = stdout.lines().next()?;
207
208    // Extract version number from output
209    // Common formats:
210    // "tool 1.2.3"
211    // "tool version 1.2.3"
212    // "1.2.3"
213    let parts: Vec<&str> = version_line.split_whitespace().collect();
214    let version = parts.last()?.to_string();
215
216    Some(version)
217}
218
219/// Run a tool command and capture output
220pub fn run_tool(
221    tool_name: &str,
222    args: &[&str],
223    working_dir: Option<&std::path::Path>,
224) -> Result<String> {
225    debug!("Running tool: {} {:?}", tool_name, args);
226
227    let mut cmd = Command::new(tool_name);
228    cmd.args(args);
229
230    if let Some(dir) = working_dir {
231        cmd.current_dir(dir);
232    }
233
234    let output = cmd
235        .output()
236        .with_context(|| format!("Failed to run tool: {}", tool_name))?;
237
238    if !output.status.success() {
239        let stderr = String::from_utf8_lossy(&output.stderr);
240        anyhow::bail!(
241            "Tool '{}' failed with exit code {:?}: {}",
242            tool_name,
243            output.status.code(),
244            stderr
245        );
246    }
247
248    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
249    Ok(stdout)
250}
251
252/// Transpile Python code using Depyler
253pub fn transpile_python(
254    input_path: &std::path::Path,
255    output_path: &std::path::Path,
256) -> Result<String> {
257    info!(
258        "Transpiling Python with Depyler: {:?} → {:?}",
259        input_path, output_path
260    );
261
262    let input_str = input_path.to_string_lossy();
263    let output_str = output_path.to_string_lossy();
264
265    let args = vec![
266        "transpile",
267        "--input",
268        &input_str,
269        "--output",
270        &output_str,
271        "--format",
272        "project", // Generate full Rust project structure
273    ];
274
275    run_tool("depyler", &args, None)
276}
277
278/// Transpile Shell script using Bashrs
279pub fn transpile_shell(
280    input_path: &std::path::Path,
281    output_path: &std::path::Path,
282) -> Result<String> {
283    info!(
284        "Transpiling Shell with Bashrs: {:?} → {:?}",
285        input_path, output_path
286    );
287
288    let input_str = input_path.to_string_lossy();
289    let output_str = output_path.to_string_lossy();
290
291    let args = vec![
292        "build",
293        &input_str,
294        "-o",
295        &output_str,
296        "--target",
297        "posix", // Most compatible shell target
298        "--verify",
299        "strict", // Strict verification
300    ];
301
302    run_tool("bashrs", &args, None)
303}
304
305/// Transpile C/C++ code using Decy (if available)
306pub fn transpile_c_cpp(
307    input_path: &std::path::Path,
308    output_path: &std::path::Path,
309) -> Result<String> {
310    info!(
311        "Transpiling C/C++ with Decy: {:?} → {:?}",
312        input_path, output_path
313    );
314
315    let input_str = input_path.to_string_lossy();
316    let output_str = output_path.to_string_lossy();
317
318    // Note: Decy might not be installed, handle gracefully
319    let args = vec!["transpile", "--input", &input_str, "--output", &output_str];
320
321    run_tool("decy", &args, None)
322}
323
324/// Run quality analysis using PMAT
325pub fn analyze_quality(path: &std::path::Path) -> Result<String> {
326    info!("Running PMAT quality analysis: {:?}", path);
327
328    let path_str = path.to_string_lossy();
329
330    let args = vec!["analyze", "complexity", &path_str, "--format", "json"];
331
332    run_tool("pmat", &args, None)
333}
334
335/// Run Ruchy scripting (if needed)
336pub fn run_ruchy_script(script_path: &std::path::Path) -> Result<String> {
337    info!("Running Ruchy script: {:?}", script_path);
338
339    let script_str = script_path.to_string_lossy();
340
341    let args = vec!["run", &script_str];
342
343    run_tool("ruchy", &args, None)
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use std::path::PathBuf;
350
351    // ============================================================================
352    // TOOLINFO TESTS
353    // ============================================================================
354
355    #[test]
356    fn test_tool_info_creation() {
357        let tool = ToolInfo {
358            name: "decy".to_string(),
359            version: Some("1.0.0".to_string()),
360            path: "/usr/local/bin/decy".to_string(),
361            available: true,
362        };
363
364        assert_eq!(tool.name, "decy");
365        assert_eq!(tool.version, Some("1.0.0".to_string()));
366        assert_eq!(tool.path, "/usr/local/bin/decy");
367        assert!(tool.available);
368    }
369
370    #[test]
371    fn test_tool_info_no_version() {
372        let tool = ToolInfo {
373            name: "test_tool".to_string(),
374            version: None,
375            path: "/bin/test".to_string(),
376            available: true,
377        };
378
379        assert_eq!(tool.name, "test_tool");
380        assert!(tool.version.is_none());
381    }
382
383    #[test]
384    fn test_tool_info_clone() {
385        let tool1 = ToolInfo {
386            name: "depyler".to_string(),
387            version: Some("2.0.0".to_string()),
388            path: "/usr/bin/depyler".to_string(),
389            available: true,
390        };
391
392        let tool2 = tool1.clone();
393        assert_eq!(tool1.name, tool2.name);
394        assert_eq!(tool1.version, tool2.version);
395        assert_eq!(tool1.path, tool2.path);
396        assert_eq!(tool1.available, tool2.available);
397    }
398
399    #[test]
400    fn test_tool_info_debug() {
401        let tool = ToolInfo {
402            name: "bashrs".to_string(),
403            version: Some("0.5.0".to_string()),
404            path: "/usr/local/bin/bashrs".to_string(),
405            available: true,
406        };
407
408        let debug_str = format!("{:?}", tool);
409        assert!(debug_str.contains("bashrs"));
410        assert!(debug_str.contains("0.5.0"));
411    }
412
413    // ============================================================================
414    // TOOLREGISTRY TESTS
415    // ============================================================================
416
417    fn create_test_registry() -> ToolRegistry {
418        ToolRegistry {
419            decy: Some(ToolInfo {
420                name: "decy".to_string(),
421                version: Some("1.0.0".to_string()),
422                path: "/usr/bin/decy".to_string(),
423                available: true,
424            }),
425            depyler: Some(ToolInfo {
426                name: "depyler".to_string(),
427                version: Some("2.0.0".to_string()),
428                path: "/usr/bin/depyler".to_string(),
429                available: true,
430            }),
431            bashrs: None,
432            ruchy: Some(ToolInfo {
433                name: "ruchy".to_string(),
434                version: Some("0.3.0".to_string()),
435                path: "/usr/bin/ruchy".to_string(),
436                available: true,
437            }),
438            trueno: None,
439            aprender: None,
440            realizar: None,
441            renacer: None,
442            pmat: Some(ToolInfo {
443                name: "pmat".to_string(),
444                version: Some("1.5.0".to_string()),
445                path: "/usr/bin/pmat".to_string(),
446                available: true,
447            }),
448        }
449    }
450
451    fn create_empty_registry() -> ToolRegistry {
452        ToolRegistry {
453            decy: None,
454            depyler: None,
455            bashrs: None,
456            ruchy: None,
457            trueno: None,
458            aprender: None,
459            realizar: None,
460            renacer: None,
461            pmat: None,
462        }
463    }
464
465    #[test]
466    fn test_tool_registry_clone() {
467        let registry1 = create_test_registry();
468        let registry2 = registry1.clone();
469
470        assert!(registry2.decy.is_some());
471        assert!(registry2.depyler.is_some());
472        assert!(registry2.bashrs.is_none());
473    }
474
475    #[test]
476    fn test_tool_registry_debug() {
477        let registry = create_test_registry();
478        let debug_str = format!("{:?}", registry);
479        assert!(debug_str.contains("decy"));
480        assert!(debug_str.contains("depyler"));
481    }
482
483    #[test]
484    fn test_tool_detection() {
485        // This test will only pass if tools are installed
486        let registry = ToolRegistry::detect();
487
488        // At minimum, we should detect PMAT if running on dev machine
489        // But we don't want tests to fail in CI, so we just log
490        println!("Available tools: {:?}", registry.available_tools());
491    }
492
493    #[test]
494    fn test_has_transpiler_with_tools() {
495        let registry = create_test_registry();
496        assert!(registry.has_transpiler());
497    }
498
499    #[test]
500    fn test_has_transpiler_empty() {
501        let registry = create_empty_registry();
502        assert!(!registry.has_transpiler());
503    }
504
505    #[test]
506    fn test_has_transpiler_only_decy() {
507        let mut registry = create_empty_registry();
508        registry.decy = Some(ToolInfo {
509            name: "decy".to_string(),
510            version: None,
511            path: "/usr/bin/decy".to_string(),
512            available: true,
513        });
514        assert!(registry.has_transpiler());
515    }
516
517    #[test]
518    fn test_has_transpiler_only_depyler() {
519        let mut registry = create_empty_registry();
520        registry.depyler = Some(ToolInfo {
521            name: "depyler".to_string(),
522            version: None,
523            path: "/usr/bin/depyler".to_string(),
524            available: true,
525        });
526        assert!(registry.has_transpiler());
527    }
528
529    #[test]
530    fn test_has_transpiler_only_bashrs() {
531        let mut registry = create_empty_registry();
532        registry.bashrs = Some(ToolInfo {
533            name: "bashrs".to_string(),
534            version: None,
535            path: "/usr/bin/bashrs".to_string(),
536            available: true,
537        });
538        assert!(registry.has_transpiler());
539    }
540
541    #[test]
542    fn test_get_transpiler_for_language_c() {
543        let registry = create_test_registry();
544        let tool = registry.get_transpiler_for_language(&crate::types::Language::C);
545        assert!(tool.is_some());
546        assert_eq!(tool.unwrap().name, "decy");
547    }
548
549    #[test]
550    fn test_get_transpiler_for_language_cpp() {
551        let registry = create_test_registry();
552        let tool = registry.get_transpiler_for_language(&crate::types::Language::Cpp);
553        assert!(tool.is_some());
554        assert_eq!(tool.unwrap().name, "decy");
555    }
556
557    #[test]
558    fn test_get_transpiler_for_language_python() {
559        let registry = create_test_registry();
560        let tool = registry.get_transpiler_for_language(&crate::types::Language::Python);
561        assert!(tool.is_some());
562        assert_eq!(tool.unwrap().name, "depyler");
563    }
564
565    #[test]
566    fn test_get_transpiler_for_language_shell() {
567        let mut registry = create_test_registry();
568        registry.bashrs = Some(ToolInfo {
569            name: "bashrs".to_string(),
570            version: None,
571            path: "/usr/bin/bashrs".to_string(),
572            available: true,
573        });
574
575        let tool = registry.get_transpiler_for_language(&crate::types::Language::Shell);
576        assert!(tool.is_some());
577        assert_eq!(tool.unwrap().name, "bashrs");
578    }
579
580    #[test]
581    fn test_get_transpiler_for_language_rust() {
582        let registry = create_test_registry();
583        let tool = registry.get_transpiler_for_language(&crate::types::Language::Rust);
584        assert!(tool.is_none());
585    }
586
587    #[test]
588    fn test_get_transpiler_for_language_javascript() {
589        let registry = create_test_registry();
590        let tool = registry.get_transpiler_for_language(&crate::types::Language::JavaScript);
591        assert!(tool.is_none());
592    }
593
594    #[test]
595    fn test_get_transpiler_for_language_other() {
596        let registry = create_test_registry();
597        let tool = registry
598            .get_transpiler_for_language(&crate::types::Language::Other("Kotlin".to_string()));
599        assert!(tool.is_none());
600    }
601
602    #[test]
603    fn test_available_tools_all_installed() {
604        let mut registry = create_test_registry();
605        registry.bashrs = Some(ToolInfo {
606            name: "bashrs".to_string(),
607            version: Some("1.0.0".to_string()),
608            path: "/usr/bin/bashrs".to_string(),
609            available: true,
610        });
611        registry.trueno = Some(ToolInfo {
612            name: "trueno".to_string(),
613            version: Some("2.0.0".to_string()),
614            path: "/usr/bin/trueno".to_string(),
615            available: true,
616        });
617        registry.aprender = Some(ToolInfo {
618            name: "aprender".to_string(),
619            version: Some("1.0.0".to_string()),
620            path: "/usr/bin/aprender".to_string(),
621            available: true,
622        });
623        registry.realizar = Some(ToolInfo {
624            name: "realizar".to_string(),
625            version: Some("1.0.0".to_string()),
626            path: "/usr/bin/realizar".to_string(),
627            available: true,
628        });
629        registry.renacer = Some(ToolInfo {
630            name: "renacer".to_string(),
631            version: Some("1.0.0".to_string()),
632            path: "/usr/bin/renacer".to_string(),
633            available: true,
634        });
635
636        let tools = registry.available_tools();
637        assert_eq!(tools.len(), 9);
638        assert!(tools.contains(&"Decy (C/C++ → Rust)".to_string()));
639        assert!(tools.contains(&"Depyler (Python → Rust)".to_string()));
640        assert!(tools.contains(&"Bashrs (Shell → Rust)".to_string()));
641        assert!(tools.contains(&"Ruchy (Rust scripting)".to_string()));
642        assert!(tools.contains(&"PMAT (Quality analysis)".to_string()));
643    }
644
645    #[test]
646    fn test_available_tools_empty() {
647        let registry = create_empty_registry();
648        let tools = registry.available_tools();
649        assert_eq!(tools.len(), 0);
650    }
651
652    #[test]
653    fn test_available_tools_partial() {
654        let registry = create_test_registry();
655        let tools = registry.available_tools();
656
657        // Should have decy, depyler, ruchy, pmat
658        assert_eq!(tools.len(), 4);
659        assert!(tools.contains(&"Decy (C/C++ → Rust)".to_string()));
660        assert!(tools.contains(&"Depyler (Python → Rust)".to_string()));
661        assert!(tools.contains(&"Ruchy (Rust scripting)".to_string()));
662        assert!(tools.contains(&"PMAT (Quality analysis)".to_string()));
663    }
664
665    #[test]
666    fn test_available_tools_unavailable_flag() {
667        let mut registry = create_test_registry();
668        // Mark depyler as unavailable
669        if let Some(tool) = &mut registry.depyler {
670            tool.available = false;
671        }
672
673        let tools = registry.available_tools();
674        // Should not include depyler
675        assert!(!tools.iter().any(|t| t.contains("Depyler")));
676    }
677
678    #[test]
679    fn test_get_installation_instructions_all_missing() {
680        let registry = create_empty_registry();
681        let instructions = registry.get_installation_instructions(&[
682            "decy", "depyler", "bashrs", "ruchy", "pmat", "trueno", "aprender", "realizar",
683            "renacer",
684        ]);
685
686        assert_eq!(instructions.len(), 9);
687        assert!(instructions.contains(&"Install Decy: cargo install decy".to_string()));
688        assert!(instructions.contains(&"Install Depyler: cargo install depyler".to_string()));
689        assert!(instructions.contains(&"Install Bashrs: cargo install bashrs".to_string()));
690        assert!(instructions.contains(&"Install Ruchy: cargo install ruchy".to_string()));
691        assert!(instructions.contains(&"Install PMAT: cargo install pmat".to_string()));
692        assert!(instructions
693            .contains(&"Install Trueno: Add 'trueno' to Cargo.toml dependencies".to_string()));
694        assert!(instructions
695            .contains(&"Install Aprender: Add 'aprender' to Cargo.toml dependencies".to_string()));
696        assert!(instructions
697            .contains(&"Install Realizar: Add 'realizar' to Cargo.toml dependencies".to_string()));
698        assert!(instructions.contains(&"Install Renacer: cargo install renacer".to_string()));
699    }
700
701    #[test]
702    fn test_get_installation_instructions_none_missing() {
703        let registry = create_test_registry();
704        let instructions =
705            registry.get_installation_instructions(&["decy", "depyler", "ruchy", "pmat"]);
706
707        // All are installed, should return empty
708        assert_eq!(instructions.len(), 0);
709    }
710
711    #[test]
712    fn test_get_installation_instructions_partial() {
713        let registry = create_test_registry();
714        let instructions = registry.get_installation_instructions(&["decy", "bashrs", "trueno"]);
715
716        // Only bashrs and trueno are missing
717        assert_eq!(instructions.len(), 2);
718        assert!(instructions.contains(&"Install Bashrs: cargo install bashrs".to_string()));
719        assert!(instructions
720            .contains(&"Install Trueno: Add 'trueno' to Cargo.toml dependencies".to_string()));
721    }
722
723    #[test]
724    fn test_get_installation_instructions_unknown_tool() {
725        let registry = create_empty_registry();
726        let instructions = registry.get_installation_instructions(&["unknown_tool", "decy"]);
727
728        // Should only return instruction for decy
729        assert_eq!(instructions.len(), 1);
730        assert!(instructions.contains(&"Install Decy: cargo install decy".to_string()));
731    }
732
733    #[test]
734    fn test_get_installation_instructions_empty_list() {
735        let registry = create_test_registry();
736        let instructions = registry.get_installation_instructions(&[]);
737
738        assert_eq!(instructions.len(), 0);
739    }
740
741    // ============================================================================
742    // FUNCTION ARGUMENT TESTS
743    // ============================================================================
744
745    #[test]
746    fn test_transpile_python_paths() {
747        let input = PathBuf::from("/path/to/input.py");
748        let output = PathBuf::from("/path/to/output");
749
750        // This will fail because depyler isn't installed in test environment
751        // But we can verify the function exists and accepts the right types
752        let _result = transpile_python(&input, &output);
753    }
754
755    #[test]
756    fn test_transpile_shell_paths() {
757        let input = PathBuf::from("/path/to/script.sh");
758        let output = PathBuf::from("/path/to/output");
759
760        // Will fail but verifies function signature
761        let _result = transpile_shell(&input, &output);
762    }
763
764    #[test]
765    fn test_transpile_c_cpp_paths() {
766        let input = PathBuf::from("/path/to/code.c");
767        let output = PathBuf::from("/path/to/output");
768
769        // Will fail but verifies function signature
770        let _result = transpile_c_cpp(&input, &output);
771    }
772
773    #[test]
774    fn test_analyze_quality_path() {
775        let path = PathBuf::from("/path/to/project");
776
777        // Will fail but verifies function signature
778        let _result = analyze_quality(&path);
779    }
780
781    #[test]
782    fn test_run_ruchy_script_path() {
783        let script = PathBuf::from("/path/to/script.ruchy");
784
785        // Will fail but verifies function signature
786        let _result = run_ruchy_script(&script);
787    }
788
789    #[test]
790    fn test_run_tool_basic_args() {
791        // Test with a command that should exist (echo)
792        let result = run_tool("echo", &["test"], None);
793
794        // echo should be available on most systems
795        if let Ok(output) = result {
796            assert!(output.contains("test"));
797        }
798    }
799
800    #[test]
801    fn test_run_tool_with_working_dir() {
802        use std::env;
803        let current_dir = env::current_dir().unwrap();
804
805        let result = run_tool("pwd", &[], Some(&current_dir));
806
807        // pwd should work and return the directory
808        if let Ok(output) = result {
809            assert!(!output.is_empty());
810        }
811    }
812}