Skip to main content

batuta/
tools.rs

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