Move the registry and Lua runtime into the library
main.rs re-declared mod assertions/error/utils, so the domain code
compiled twice and only the binary copy ran the registry — which is why
coverage ignored it and 10 mutants survived there. The binary now
depends on the library and keeps only argument parsing and rendering.

Assisted-by: Claude Opus 5 via Claude Code
change wmzxrpsvvztxrtylymwykzknvvlslzvw
commit 47fda5cae40054b0471ee666ad5dfe475827ccc3
author Alpha Chen <alpha@kejadlen.dev>
date
parent qksuvukl
diff --git a/frork-cli/src/lib.rs b/frork-cli/src/lib.rs
index 4f80e5e..da66d2c 100644
--- a/frork-cli/src/lib.rs
+++ b/frork-cli/src/lib.rs
@@ -13,4 +13,6 @@
 
 pub mod assertions;
 pub mod error;
+pub mod registry;
+pub mod runtime;
 pub mod utils;
diff --git a/frork-cli/src/main.rs b/frork-cli/src/main.rs
index 7dc302f..15727e7 100644
--- a/frork-cli/src/main.rs
+++ b/frork-cli/src/main.rs
@@ -1,47 +1,14 @@
-// Panic discipline applies to code that handles Fennel scripts and shell
-// output; test code asserts on known-good fixtures.
-#![cfg_attr(
-    test,
-    allow(
-        clippy::unwrap_used,
-        clippy::expect_used,
-        clippy::panic,
-        clippy::indexing_slicing,
-        clippy::arithmetic_side_effects
-    )
-)]
-
-mod assertions;
-mod error;
-mod utils;
-
-use std::cell::RefCell;
-use std::collections::HashMap;
-use std::rc::Rc;
-
-use assertions::AssertionType;
-use assertions::AssertionTypeFactory;
-use assertions::Brew;
-use assertions::BrewBundle;
-use assertions::Debug;
-use assertions::Directory;
-use assertions::Git;
-use assertions::LuaAssertion;
-use assertions::LuaAssertionType;
-use assertions::Status;
-use assertions::Symlink;
-use assertions::TypedFactory;
 use clap::CommandFactory;
 use clap::Parser;
 use clap::Subcommand;
 use clap_complete::Shell;
-use error::FrorkError;
+use frork_cli::assertions::AssertionType;
+use frork_cli::assertions::Status;
+use frork_cli::runtime::run_code;
+use frork_cli::runtime::run_script;
 use miette::IntoDiagnostic as _;
 use miette::Result;
 use miette::miette;
-use mlua::prelude::*;
-use tracing::info;
-use utils::Utils;
 
 /// CalVer, set by build.rs — the crate version is not what ships.
 const VERSION: &str = env!("FRORK_VERSION");
@@ -67,6 +34,9 @@ enum Commands {
 }
 
 fn main() -> Result<()> {
+    // Assertion types panic through todo!() for cases that are not implemented
+    // yet; this renders those with the same formatting as recoverable errors.
+    miette::set_panic_hook();
     tracing_subscriber::fmt::init();
 
     let cli = Cli::parse();
@@ -144,240 +114,3 @@ fn satisfy(status: &Status, assertion: &dyn AssertionType) -> Result<()> {
     }
     Ok(())
 }
-
-fn run_code(
-    code: &str,
-    handle_status: impl Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static,
-) -> Result<()> {
-    let (lua, frork_module, fennel_module) = setup_lua(handle_status)?;
-
-    let ok_fn: LuaFunction = frork_module
-        .get("ok")
-        .map_err(|e| miette!("Failed to get frork.ok: {e}"))?;
-    lua.globals()
-        .set("ok", ok_fn)
-        .map_err(|e| miette!("Failed to set ok global: {e}"))?;
-
-    let eval_fn: LuaFunction = fennel_module
-        .get("eval")
-        .map_err(|e| miette!("Failed to get fennel.eval: {e}"))?;
-
-    eval_fn
-        .call::<()>(code)
-        .map_err(|e| miette!("Failed to execute fennel code: {e}"))?;
-
-    Ok(())
-}
-
-fn run_script(
-    script: &str,
-    handle_status: impl Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static,
-) -> Result<()> {
-    let (lua, _frork_module, fennel_module) = setup_lua(handle_status)?;
-
-    // Add script directory to Lua and Fennel search paths.
-    let script_path = std::path::Path::new(script);
-    if let Some(script_dir) = script_path.parent()
-        && let Some(script_dir_str) = script_dir.to_str()
-    {
-        let package_table: LuaTable = lua
-            .globals()
-            .get("package")
-            .map_err(|e| miette!("Failed to get package table: {e}"))?;
-
-        // Add to Lua search path.
-        let current_path: String = package_table
-            .get("path")
-            .map_err(|e| miette!("Failed to get current Lua path: {e}"))?;
-        let new_path = format!(
-            "{};{}/?.lua;{}/?/init.lua",
-            current_path, script_dir_str, script_dir_str
-        );
-        package_table
-            .set("path", new_path)
-            .map_err(|e| miette!("Failed to set Lua path: {e}"))?;
-
-        // Add to Fennel search path.
-        let current_fennel_path: String = fennel_module
-            .get("path")
-            .unwrap_or_else(|_| "./?.fnl;./?/init.fnl".to_string());
-        let new_fennel_path = format!(
-            "{};{}/?.fnl;{}/?/init.fnl",
-            current_fennel_path, script_dir_str, script_dir_str
-        );
-        fennel_module
-            .set("path", new_fennel_path)
-            .map_err(|e| miette!("Failed to set Fennel path: {e}"))?;
-    }
-
-    lua.load(format!(
-        r#"require("fennel").install().dofile("{}")"#,
-        script
-    ))
-    .exec()
-    .map_err(|e| miette!("Failed to execute script: {e}"))?;
-
-    Ok(())
-}
-
-fn setup_lua(
-    handle_status: impl Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static,
-) -> Result<(Lua, LuaTable, LuaTable)> {
-    let lua = Lua::new();
-
-    let fennel_code = include_str!("../fennel-1.6.0.lua");
-    let fennel_module: LuaTable = lua
-        .load(fennel_code)
-        .eval()
-        .map_err(|e| miette!("Failed to load Fennel: {e}"))?;
-    lua.register_module("fennel", &fennel_module)
-        .map_err(|e| miette!("Failed to register fennel module: {e}"))?;
-
-    let frork_table = match Frork::new(handle_status, lua.clone())
-        .into_lua(&lua)
-        .map_err(|e| miette!("Failed to create frork table: {e}"))?
-    {
-        LuaValue::Table(table) => table,
-        _ => unreachable!(),
-    };
-    lua.register_module("frork", &frork_table)
-        .map_err(|e| miette!("Failed to register frork module: {e}"))?;
-
-    Ok((lua, frork_table, fennel_module))
-}
-
-struct Frork<F> {
-    // RefCell needed for interior mutability — register() adds new assertion
-    // types at runtime when called from Lua/Fennel code.
-    registry: RefCell<Registry>,
-    handle_status: F,
-    lua: Lua,
-}
-
-impl<F> Frork<F> {
-    fn new(handle_status: F, lua: Lua) -> Self {
-        Self {
-            registry: RefCell::new(Registry::default()),
-            handle_status,
-            lua,
-        }
-    }
-}
-
-impl<F> Frork<F>
-where
-    F: Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static,
-{
-    fn register(&self, name: &str, lua_assertion_type: LuaAssertionType) -> LuaResult<()> {
-        self.registry
-            .borrow_mut()
-            .register(name, lua_assertion_type);
-        info!("Registered assertion type: {}", name);
-        Ok(())
-    }
-
-    fn ok(&self, args: LuaMultiValue) -> LuaResult<()> {
-        if args.is_empty() {
-            return Err(LuaError::external(FrorkError::NoOperation));
-        }
-
-        let mut args_iter = args.into_iter();
-        let assertion_type = args_iter
-            .next()
-            .and_then(|v| v.to_string().ok())
-            .ok_or_else(|| LuaError::external(FrorkError::MissingAssertionType))?;
-
-        let assertion_args: LuaMultiValue = args_iter.collect();
-
-        let factory = self
-            .registry
-            .borrow()
-            .get_factory(&assertion_type)
-            .map_err(LuaError::external)?;
-        let assertion = factory
-            .create(&self.lua, assertion_args)
-            .map_err(LuaError::external)?;
-        let status = assertion.status().map_err(LuaError::external)?;
-
-        (self.handle_status)(&status, assertion.as_ref()).map_err(LuaError::external)?;
-        Ok(())
-    }
-}
-
-impl<F> IntoLua for Frork<F>
-where
-    F: Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static,
-{
-    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
-        let frork_table = lua.create_table()?;
-        let frork = Rc::new(self);
-
-        let frork_clone = frork.clone();
-        let register_fn = lua.create_function(
-            move |_lua, (name, lua_assertion_type): (String, LuaAssertionType)| {
-                frork_clone.register(&name, lua_assertion_type)
-            },
-        )?;
-
-        frork_table.set("register", register_fn)?;
-
-        let frork_clone = frork.clone();
-        let ok_fn = lua.create_function(move |_lua, args: LuaMultiValue| frork_clone.ok(args))?;
-        frork_table.set("ok", ok_fn)?;
-
-        frork_table.set("utils", Utils {})?;
-
-        Ok(LuaValue::Table(frork_table))
-    }
-}
-
-#[derive(Default)]
-struct Registry {
-    lua_assertion_types: HashMap<String, LuaAssertionType>,
-}
-
-impl Registry {
-    fn register(&mut self, name: &str, lua_assertion_type: LuaAssertionType) {
-        self.lua_assertion_types
-            .insert(name.to_string(), lua_assertion_type);
-    }
-
-    fn get_factory(&self, assertion_type: &str) -> Result<Box<dyn AssertionTypeFactory>> {
-        // Check Lua assertions first.
-        if let Some(lua_assertion) = self.lua_assertion_types.get(assertion_type) {
-            return Ok(Box::new(LuaAssertionFactory {
-                assertion_type: assertion_type.to_string(),
-                lua_assertion_type: lua_assertion.clone(),
-            }));
-        }
-
-        // Return factory for built-in types.
-        match assertion_type {
-            "brew" => Ok(Box::new(TypedFactory::<Brew>::new())),
-            "brew-bundle" => Ok(Box::new(TypedFactory::<BrewBundle>::new())),
-            "debug" => Ok(Box::new(TypedFactory::<Debug>::new())),
-            "directory" => Ok(Box::new(TypedFactory::<Directory>::new())),
-            "git" => Ok(Box::new(TypedFactory::<Git>::new())),
-            "symlink" => Ok(Box::new(TypedFactory::<Symlink>::new())),
-            _ => Err(FrorkError::UnknownAssertionType {
-                assertion_type: assertion_type.to_string(),
-            }
-            .into()),
-        }
-    }
-}
-
-struct LuaAssertionFactory {
-    assertion_type: String,
-    lua_assertion_type: LuaAssertionType,
-}
-
-impl AssertionTypeFactory for LuaAssertionFactory {
-    fn create(&self, _lua: &Lua, args: LuaMultiValue) -> Result<Box<dyn AssertionType>> {
-        Ok(Box::new(LuaAssertion::new(
-            &self.assertion_type,
-            args,
-            self.lua_assertion_type.clone(),
-        )))
-    }
-}
diff --git a/frork-cli/src/registry.rs b/frork-cli/src/registry.rs
new file mode 100644
index 0000000..549a765
--- /dev/null
+++ b/frork-cli/src/registry.rs
@@ -0,0 +1,212 @@
+use std::collections::HashMap;
+
+use miette::Result;
+use mlua::prelude::*;
+
+use crate::assertions::AssertionType;
+use crate::assertions::AssertionTypeFactory;
+use crate::assertions::Brew;
+use crate::assertions::BrewBundle;
+use crate::assertions::Debug;
+use crate::assertions::Directory;
+use crate::assertions::Git;
+use crate::assertions::LuaAssertion;
+use crate::assertions::LuaAssertionType;
+use crate::assertions::Symlink;
+use crate::assertions::TypedFactory;
+use crate::error::FrorkError;
+
+/// Dispatches an assertion type name to the factory that builds it. Types
+/// registered from Fennel shadow the built-ins.
+#[derive(Default)]
+pub struct Registry {
+    lua_assertion_types: HashMap<String, LuaAssertionType>,
+}
+
+impl Registry {
+    pub fn register(&mut self, name: &str, lua_assertion_type: LuaAssertionType) {
+        self.lua_assertion_types
+            .insert(name.to_string(), lua_assertion_type);
+    }
+
+    pub fn get_factory(&self, assertion_type: &str) -> Result<Box<dyn AssertionTypeFactory>> {
+        // Check Lua assertions first.
+        if let Some(lua_assertion) = self.lua_assertion_types.get(assertion_type) {
+            return Ok(Box::new(LuaAssertionFactory {
+                assertion_type: assertion_type.to_string(),
+                lua_assertion_type: lua_assertion.clone(),
+            }));
+        }
+
+        // Return factory for built-in types.
+        match assertion_type {
+            "brew" => Ok(Box::new(TypedFactory::<Brew>::new())),
+            "brew-bundle" => Ok(Box::new(TypedFactory::<BrewBundle>::new())),
+            "debug" => Ok(Box::new(TypedFactory::<Debug>::new())),
+            "directory" => Ok(Box::new(TypedFactory::<Directory>::new())),
+            "git" => Ok(Box::new(TypedFactory::<Git>::new())),
+            "symlink" => Ok(Box::new(TypedFactory::<Symlink>::new())),
+            _ => Err(FrorkError::UnknownAssertionType {
+                assertion_type: assertion_type.to_string(),
+            }
+            .into()),
+        }
+    }
+}
+
+struct LuaAssertionFactory {
+    assertion_type: String,
+    lua_assertion_type: LuaAssertionType,
+}
+
+impl AssertionTypeFactory for LuaAssertionFactory {
+    fn create(&self, _lua: &Lua, args: LuaMultiValue) -> Result<Box<dyn AssertionType>> {
+        Ok(Box::new(LuaAssertion::new(
+            &self.assertion_type,
+            args,
+            self.lua_assertion_type.clone(),
+        )))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::assertions::Status;
+
+    fn lua_assertion_type(lua: &Lua) -> LuaAssertionType {
+        let value = lua
+            .load(r#"return {status = function() return "ok" end, install = function() end}"#)
+            .eval::<LuaValue>()
+            .unwrap();
+        LuaAssertionType::from_lua(value, lua).unwrap()
+    }
+
+    /// Every built-in name resolves to a factory that builds a working
+    /// assertion, checked through the factory rather than by name alone.
+    #[test]
+    fn test_built_in_types_resolve() {
+        let lua = Lua::new();
+        let registry = Registry::default();
+
+        let cases = [
+            ("brew", LuaMultiValue::new(), "brew"),
+            (
+                "brew-bundle",
+                lua.load(r#"return "/tmp/Brewfile""#).eval().unwrap(),
+                "brew-bundle /tmp/Brewfile",
+            ),
+            ("debug", lua.load(r#"return {}"#).eval().unwrap(), "debug"),
+            (
+                "directory",
+                lua.load(r#"return "/tmp""#).eval().unwrap(),
+                "directory /tmp",
+            ),
+            (
+                "git",
+                lua.load(r#"return "/tmp/r", "https://example.com/r.git""#)
+                    .eval()
+                    .unwrap(),
+                "git /tmp/r https://example.com/r.git",
+            ),
+            (
+                "symlink",
+                lua.load(r#"return "/tmp/link", "/tmp/source""#)
+                    .eval()
+                    .unwrap(),
+                "symlink /tmp/link /tmp/source",
+            ),
+        ];
+
+        for (name, args, expected) in cases {
+            let factory = registry
+                .get_factory(name)
+                .unwrap_or_else(|e| panic!("{name} did not resolve: {e}"));
+            let assertion = factory
+                .create(&lua, args)
+                .unwrap_or_else(|e| panic!("{name} failed to build: {e}"));
+            assert_eq!(assertion.to_string(), expected, "for type {name}");
+        }
+    }
+
+    #[test]
+    fn test_unknown_type_is_rejected() {
+        let registry = Registry::default();
+
+        let Err(error) = registry.get_factory("nope") else {
+            panic!("expected an unknown assertion type error"); // cov-excl-line
+        };
+        assert!(error.to_string().contains("Unknown assertion type: nope"));
+    }
+
+    #[test]
+    fn test_registered_types_are_dispatched() {
+        let lua = Lua::new();
+        let mut registry = Registry::default();
+        registry.register("custom", lua_assertion_type(&lua));
+
+        let factory = registry.get_factory("custom").unwrap();
+        let assertion = factory
+            .create(&lua, lua.load(r#"return "arg""#).eval().unwrap())
+            .unwrap();
+
+        assert_eq!(assertion.to_string(), "custom arg");
+    }
+
+    #[test]
+    fn test_registered_types_shadow_built_ins() {
+        let lua = Lua::new();
+        let mut registry = Registry::default();
+
+        // Reports missing for a directory that exists, so the built-in (which
+        // would report Ok for /tmp) cannot produce this result.
+        let shadow = lua
+            .load(
+                r#"return {
+                    display = function() return "shadowed" end,
+                    status = function() return "missing" end,
+                    install = function() end,
+                }"#,
+            )
+            .eval::<LuaValue>()
+            .unwrap();
+        registry.register(
+            "directory",
+            LuaAssertionType::from_lua(shadow, &lua).unwrap(),
+        );
+
+        let factory = registry.get_factory("directory").unwrap();
+        let assertion = factory
+            .create(&lua, lua.load(r#"return "/tmp""#).eval().unwrap())
+            .unwrap();
+
+        assert_eq!(assertion.to_string(), "shadowed");
+        assert!(matches!(assertion.status().unwrap(), Status::Missing));
+    }
+
+    #[test]
+    fn test_re_registering_replaces_the_previous_type() {
+        let lua = Lua::new();
+        let mut registry = Registry::default();
+
+        registry.register("custom", lua_assertion_type(&lua));
+        let replacement = lua
+            .load(
+                r#"return {
+                    display = function() return "replaced" end,
+                    status = function() return "ok" end,
+                    install = function() end,
+                }"#,
+            )
+            .eval::<LuaValue>()
+            .unwrap();
+        registry.register(
+            "custom",
+            LuaAssertionType::from_lua(replacement, &lua).unwrap(),
+        );
+
+        let factory = registry.get_factory("custom").unwrap();
+        let assertion = factory.create(&lua, LuaMultiValue::new()).unwrap();
+        assert_eq!(assertion.to_string(), "replaced");
+    }
+}
diff --git a/frork-cli/src/runtime.rs b/frork-cli/src/runtime.rs
new file mode 100644
index 0000000..bc164da
--- /dev/null
+++ b/frork-cli/src/runtime.rs
@@ -0,0 +1,359 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use miette::Result;
+use miette::miette;
+use mlua::prelude::*;
+use tracing::info;
+
+use crate::assertions::AssertionType;
+use crate::assertions::LuaAssertionType;
+use crate::assertions::Status;
+use crate::error::FrorkError;
+use crate::registry::Registry;
+use crate::utils::Utils;
+
+/// Handles each assertion's status once it has been evaluated. The binary
+/// supplies the rendering and any prompting; nothing here writes to stdout.
+pub trait StatusHandler: Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static {}
+
+impl<T> StatusHandler for T where T: Fn(&Status, &dyn AssertionType) -> Result<()> + Clone + 'static {}
+
+/// Evaluates inline Fennel code.
+pub fn run_code(code: &str, handle_status: impl StatusHandler) -> Result<()> {
+    let (lua, frork_module, fennel_module) = setup_lua(handle_status)?;
+
+    let ok_fn: LuaFunction = frork_module
+        .get("ok")
+        .map_err(|e| miette!("Failed to get frork.ok: {e}"))?;
+    lua.globals()
+        .set("ok", ok_fn)
+        .map_err(|e| miette!("Failed to set ok global: {e}"))?;
+
+    let eval_fn: LuaFunction = fennel_module
+        .get("eval")
+        .map_err(|e| miette!("Failed to get fennel.eval: {e}"))?;
+
+    eval_fn
+        .call::<()>(code)
+        .map_err(|e| miette!("Failed to execute fennel code: {e}"))?;
+
+    Ok(())
+}
+
+/// Evaluates a `.fnl` script file, with its directory on the search path.
+pub fn run_script(script: &str, handle_status: impl StatusHandler) -> Result<()> {
+    let (lua, _frork_module, fennel_module) = setup_lua(handle_status)?;
+
+    // Add script directory to Lua and Fennel search paths.
+    let script_path = std::path::Path::new(script);
+    if let Some(script_dir) = script_path.parent()
+        && let Some(script_dir_str) = script_dir.to_str()
+    {
+        let package_table: LuaTable = lua
+            .globals()
+            .get("package")
+            .map_err(|e| miette!("Failed to get package table: {e}"))?;
+
+        // Add to Lua search path.
+        let current_path: String = package_table
+            .get("path")
+            .map_err(|e| miette!("Failed to get current Lua path: {e}"))?;
+        let new_path = format!(
+            "{};{}/?.lua;{}/?/init.lua",
+            current_path, script_dir_str, script_dir_str
+        );
+        package_table
+            .set("path", new_path)
+            .map_err(|e| miette!("Failed to set Lua path: {e}"))?;
+
+        // Add to Fennel search path.
+        let current_fennel_path: String = fennel_module
+            .get("path")
+            .unwrap_or_else(|_| "./?.fnl;./?/init.fnl".to_string());
+        let new_fennel_path = format!(
+            "{};{}/?.fnl;{}/?/init.fnl",
+            current_fennel_path, script_dir_str, script_dir_str
+        );
+        fennel_module
+            .set("path", new_fennel_path)
+            .map_err(|e| miette!("Failed to set Fennel path: {e}"))?;
+    }
+
+    lua.load(format!(
+        r#"require("fennel").install().dofile("{}")"#,
+        script
+    ))
+    .exec()
+    .map_err(|e| miette!("Failed to execute script: {e}"))?;
+
+    Ok(())
+}
+
+fn setup_lua(handle_status: impl StatusHandler) -> Result<(Lua, LuaTable, LuaTable)> {
+    let lua = Lua::new();
+
+    let fennel_code = include_str!("../fennel-1.6.0.lua");
+    let fennel_module: LuaTable = lua
+        .load(fennel_code)
+        .eval()
+        .map_err(|e| miette!("Failed to load Fennel: {e}"))?;
+    lua.register_module("fennel", &fennel_module)
+        .map_err(|e| miette!("Failed to register fennel module: {e}"))?;
+
+    let frork_value = Frork::new(handle_status, lua.clone())
+        .into_lua(&lua)
+        .map_err(|e| miette!("Failed to create frork table: {e}"))?;
+    let LuaValue::Table(frork_table) = frork_value else {
+        // Frork::into_lua always returns a table.
+        unreachable!();
+    };
+    lua.register_module("frork", &frork_table)
+        .map_err(|e| miette!("Failed to register frork module: {e}"))?;
+
+    Ok((lua, frork_table, fennel_module))
+}
+
+struct Frork<F> {
+    // RefCell needed for interior mutability — register() adds new assertion
+    // types at runtime when called from Lua/Fennel code.
+    registry: RefCell<Registry>,
+    handle_status: F,
+    lua: Lua,
+}
+
+impl<F> Frork<F> {
+    fn new(handle_status: F, lua: Lua) -> Self {
+        Self {
+            registry: RefCell::new(Registry::default()),
+            handle_status,
+            lua,
+        }
+    }
+}
+
+impl<F: StatusHandler> Frork<F> {
+    fn register(&self, name: &str, lua_assertion_type: LuaAssertionType) -> LuaResult<()> {
+        self.registry
+            .borrow_mut()
+            .register(name, lua_assertion_type);
+        info!("Registered assertion type: {}", name);
+        Ok(())
+    }
+
+    fn ok(&self, args: LuaMultiValue) -> LuaResult<()> {
+        if args.is_empty() {
+            return Err(LuaError::external(FrorkError::NoOperation));
+        }
+
+        let mut args_iter = args.into_iter();
+        let assertion_type = args_iter
+            .next()
+            .and_then(|v| v.to_string().ok())
+            .ok_or_else(|| LuaError::external(FrorkError::MissingAssertionType))?;
+
+        let assertion_args: LuaMultiValue = args_iter.collect();
+
+        let factory = self
+            .registry
+            .borrow()
+            .get_factory(&assertion_type)
+            .map_err(LuaError::external)?;
+        let assertion = factory
+            .create(&self.lua, assertion_args)
+            .map_err(LuaError::external)?;
+        let status = assertion.status().map_err(LuaError::external)?;
+
+        (self.handle_status)(&status, assertion.as_ref()).map_err(LuaError::external)?;
+        Ok(())
+    }
+}
+
+impl<F: StatusHandler> IntoLua for Frork<F> {
+    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
+        let frork_table = lua.create_table()?;
+        let frork = Rc::new(self);
+
+        let frork_clone = frork.clone();
+        // The &Lua annotation is required: without it the closure infers a
+        // single concrete lifetime and no longer satisfies create_function.
+        let register = move |_lua: &Lua, (name, assertion_type): (String, LuaAssertionType)| {
+            frork_clone.register(&name, assertion_type)
+        };
+        let register_fn = lua.create_function(register)?;
+        frork_table.set("register", register_fn)?;
+
+        let frork_clone = frork.clone();
+        let ok_fn = lua.create_function(move |_lua, args: LuaMultiValue| frork_clone.ok(args))?;
+        frork_table.set("ok", ok_fn)?;
+
+        frork_table.set("utils", Utils {})?;
+
+        Ok(LuaValue::Table(frork_table))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use fs_err as fs;
+    use tempfile::TempDir;
+
+    type Recorded = Rc<RefCell<Vec<String>>>;
+
+    /// A status handler that records what it was handed, standing in for the
+    /// binary's printing.
+    fn recorder() -> (Recorded, impl StatusHandler) {
+        let seen: Recorded = Rc::new(RefCell::new(Vec::new()));
+        let sink = seen.clone();
+        let handler = move |status: &Status, assertion: &dyn AssertionType| {
+            sink.borrow_mut().push(format!("{status:?}: {assertion}"));
+            Ok(())
+        };
+        (seen, handler)
+    }
+
+    #[test]
+    fn test_run_code_evaluates_fennel_and_reports_status() {
+        let (seen, handler) = recorder();
+
+        run_code(r#"(ok :directory "/tmp")"#, handler).unwrap();
+
+        assert_eq!(seen.borrow().as_slice(), ["Ok: directory /tmp"]);
+    }
+
+    #[test]
+    fn test_run_code_reports_each_assertion_in_order() {
+        let (seen, handler) = recorder();
+
+        run_code(
+            r#"(ok :directory "/tmp")
+               (ok :directory "/frork-does-not-exist")"#,
+            handler,
+        )
+        .unwrap();
+
+        assert_eq!(
+            seen.borrow().as_slice(),
+            [
+                "Ok: directory /tmp",
+                "Missing: directory /frork-does-not-exist",
+            ]
+        );
+    }
+
+    #[test]
+    fn test_register_adds_a_type_usable_from_fennel() {
+        let (seen, handler) = recorder();
+
+        run_code(
+            r#"(local frork (require :frork))
+               (frork.register :always-missing
+                 {:status (fn [] "missing")
+                  :install (fn [] nil)
+                  :display (fn [a] (.. "custom " a))})
+               (ok :always-missing "thing")"#,
+            handler,
+        )
+        .unwrap();
+
+        assert_eq!(seen.borrow().as_slice(), ["Missing: custom thing"]);
+    }
+
+    #[test]
+    fn test_ok_without_arguments_is_rejected() {
+        let (_seen, handler) = recorder();
+
+        let error = run_code("(ok)", handler).unwrap_err();
+        assert!(error.to_string().contains("Failed to execute fennel code"));
+    }
+
+    #[test]
+    fn test_ok_rejects_unknown_assertion_types() {
+        let (seen, handler) = recorder();
+
+        assert!(run_code(r#"(ok :not-a-real-type)"#, handler).is_err());
+        assert!(seen.borrow().is_empty());
+    }
+
+    #[test]
+    fn test_run_code_propagates_handler_failures() {
+        let handler =
+            |_status: &Status, _assertion: &dyn AssertionType| Err(miette!("handler said no"));
+
+        assert!(run_code(r#"(ok :directory "/tmp")"#, handler).is_err());
+    }
+
+    #[test]
+    fn test_run_code_rejects_invalid_fennel() {
+        let (_seen, handler) = recorder();
+
+        assert!(run_code("(this is not (valid", handler).is_err());
+    }
+
+    #[test]
+    fn test_frork_utils_are_exposed_to_fennel() {
+        let (seen, handler) = recorder();
+
+        run_code(
+            r#"(local frork (require :frork))
+               (ok :directory (frork.utils.dirname "/tmp/b.txt"))"#,
+            handler,
+        )
+        .unwrap();
+
+        assert_eq!(seen.borrow().as_slice(), ["Ok: directory /tmp"]);
+    }
+
+    #[test]
+    fn test_run_script_evaluates_a_file() {
+        let dir = TempDir::new().unwrap();
+        let script = dir.path().join("check.fnl");
+        fs::write(
+            &script,
+            r#"(local frork (require :frork))
+               (frork.ok :directory "/tmp")"#,
+        )
+        .unwrap();
+
+        let (seen, handler) = recorder();
+        run_script(script.to_str().unwrap(), handler).unwrap();
+
+        assert_eq!(seen.borrow().as_slice(), ["Ok: directory /tmp"]);
+    }
+
+    #[test]
+    fn test_run_script_puts_the_script_directory_on_the_search_path() {
+        let dir = TempDir::new().unwrap();
+        fs::write(dir.path().join("helper.fnl"), r#"{:target "/tmp"}"#).unwrap();
+        let script = dir.path().join("main.fnl");
+        fs::write(
+            &script,
+            r#"(local frork (require :frork))
+               (local helper (require :helper))
+               (frork.ok :directory helper.target)"#,
+        )
+        .unwrap();
+
+        let (seen, handler) = recorder();
+        run_script(script.to_str().unwrap(), handler).unwrap();
+
+        assert_eq!(seen.borrow().as_slice(), ["Ok: directory /tmp"]);
+    }
+
+    #[test]
+    fn test_run_script_without_a_parent_directory() {
+        let (_seen, handler) = recorder();
+
+        // An empty path has no parent, so the search-path block is skipped.
+        assert!(run_script("", handler).is_err());
+    }
+
+    #[test]
+    fn test_run_script_reports_a_missing_file() {
+        let (_seen, handler) = recorder();
+
+        let error = run_script("/frork-does-not-exist/nope.fnl", handler).unwrap_err();
+        assert!(error.to_string().contains("Failed to execute script"));
+    }
+}