dbg-cli 0.2.1

A universal debugger CLI that lets AI agents observe runtime state instead of guessing from source code
Documentation
use super::{Backend, Dependency, DependencyCheck, SpawnConfig, shell_escape};
use crate::daemon::session_tmp;

pub struct JitDasmBackend;

impl Backend for JitDasmBackend {
    fn name(&self) -> &'static str {
        "jitdasm"
    }

    fn description(&self) -> &'static str {
        ".NET JIT disassembly analyzer"
    }

    fn types(&self) -> &'static [&'static str] {
        &["jitdasm"]
    }

    fn spawn_config(&self, target: &str, args: &[String]) -> anyhow::Result<SpawnConfig> {
        let project = target.to_string();
        let out_dir = session_tmp("jitdasm");
        let out_dir_str = out_dir.display().to_string();
        let out_file = out_dir.join("capture.asm");
        let out_file_str = out_file.display().to_string();

        let pattern = if args.is_empty() {
            "*".to_string() // default: capture everything
        } else {
            args[0].clone()
        };

        let extra_args: Vec<String> = if args.len() > 1 {
            args[1..].to_vec()
        } else {
            vec![]
        };

        let extra = if extra_args.is_empty() {
            String::new()
        } else {
            let escaped: Vec<String> = extra_args.iter().map(|a| shell_escape(a)).collect();
            format!(" {}", escaped.join(" "))
        };

        let dbg_bin = super::self_exe();

        let mkdir_cmd = format!("mkdir -p {}", out_dir_str);

        let build_cmd = format!(
            "echo 'Building...' && dotnet build {} -c Release --nologo -v q 2>&1 | tail -1",
            shell_escape(&project)
        );

        let run_cmd = format!(
            "echo 'Disassembling: {}' && DOTNET_TieredCompilation=0 DOTNET_JitDisasm='{}' DOTNET_JitDiffableDasm=1 dotnet run --project {} -c Release --no-build{} > {} 2>&1",
            pattern, pattern, shell_escape(&project), extra, out_file_str
        );

        // Replace the bash shell with our Rust REPL
        let exec_repl = format!("exec {} --jitdasm-repl {}", dbg_bin, out_file_str);

        Ok(SpawnConfig {
            bin: "bash".into(),
            args: vec!["--norc".into(), "--noprofile".into()],
            env: vec![("PS1".into(), "jitdasm> ".into())],
            init_commands: vec![mkdir_cmd, build_cmd, run_cmd, exec_repl],
        })
    }

    fn prompt_pattern(&self) -> &str {
        r"jitdasm> $"
    }

    fn dependencies(&self) -> Vec<Dependency> {
        vec![Dependency {
            name: "dotnet",
            check: DependencyCheck::Binary {
                name: "dotnet",
                alternatives: &["dotnet"],
                version_cmd: None,
            },
            install: "https://dot.net/install",
        }]
    }

    fn run_command(&self) -> &'static str {
        "stats"
    }

    fn quit_command(&self) -> &'static str {
        "exit"
    }

    fn parse_help(&self, _raw: &str) -> String {
        "jitdasm: methods, disasm <pattern>, search <instr>, stats, hotspots [N], simd, help".to_string()
    }

    fn adapters(&self) -> Vec<(&'static str, &'static str)> {
        vec![("jitdasm.md", include_str!("../../skills/adapters/jitdasm.md"))]
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn spawn_config_with_pattern() {
        let cfg = JitDasmBackend
            .spawn_config("bench/tq-quick/tq-quick.csproj", &["SimdOps:DotProduct".into()])
            .unwrap();
        assert_eq!(cfg.bin, "bash");
        assert!(cfg.init_commands[0].contains("mkdir"));
        assert!(cfg.init_commands[1].contains("dotnet build"));
        assert!(cfg.init_commands[2].contains("DOTNET_JitDisasm"));
        assert!(cfg.init_commands[2].contains("SimdOps:DotProduct"));
    }

    #[test]
    fn spawn_config_default_captures_all() {
        let cfg = JitDasmBackend
            .spawn_config("myapp.csproj", &[])
            .unwrap();
        assert!(cfg.init_commands[2].contains("DOTNET_JitDisasm='*'"));
    }

    #[test]
    fn spawn_config_with_extra_args() {
        let cfg = JitDasmBackend
            .spawn_config(
                "myapp.csproj",
                &["MyMethod".into(), "--ef".into(), "64".into()],
            )
            .unwrap();
        assert!(cfg.init_commands[2].contains("--ef 64"));
    }

    #[test]
    fn spawn_config_execs_repl() {
        let cfg = JitDasmBackend
            .spawn_config("myapp.csproj", &["Foo:Bar".into()])
            .unwrap();
        assert!(cfg.init_commands[3].contains("--jitdasm-repl"));
        assert!(cfg.init_commands[3].contains("exec"));
    }

    #[test]
    fn prompt_pattern_matches() {
        let re = regex::Regex::new(JitDasmBackend.prompt_pattern()).unwrap();
        assert!(re.is_match("jitdasm> "));
    }

    #[test]
    fn format_breakpoint_empty() {
        assert_eq!(JitDasmBackend.format_breakpoint("anything"), "");
    }
}