omh 0.6.0

Launch any coding harness, in a sandbox, with your setup already there.
//! The definitions omh ships — adapters, editors, the base set, the stacks —
//! embedded in the binary at compile time.
//!
//! They used to be read from `env!("CARGO_MANIFEST_DIR")` at *runtime*, which
//! is the build machine's source path. On the machine that built it that path
//! exists and everything works; anywhere else `read_dir` fails, the error is
//! discarded, and `omh init` delivers nothing while reporting success. A
//! downloaded binary got `no usable base manifest in … — run omh init` from
//! `omh init` itself, and zero adapters with no message at all.
//!
//! A `cargo install` would have escaped it, but only by accident: cargo leaves
//! the extracted crate under `~/.cargo/registry/src/`, so the baked path would
//! have resolved for as long as that directory survived — and it is a cache.
//! Stated as reasoning rather than history: omh has never been published, so
//! nobody has actually been bitten this way. The measured case was a binary
//! separated from the tree it was built in.
//!
//! Embedding is what makes the binary the unit of distribution. Note that the
//! files still land on disk in `~/.omh` exactly as before — the base set stays
//! reviewable by the people it is imposed on, which was always the point of
//! shipping it as data. Only where `init` copies *from* has changed.

/// One shipped file: what it is called, and what is in it.
///
/// Named fields rather than a tuple because both halves are `&'static str`, so
/// a table that swapped them would type-check and install files named after
/// their own contents.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct File {
    pub name: &'static str,
    pub contents: &'static str,
}

// Generated by build.rs: the `Shipped` enum, `ALL`, and `Shipped::files`.
// A directory omh does not ship is not a value this module can be handed, so
// there is no unknown-kind error to raise, propagate, or test.
include!(concat!(env!("OUT_DIR"), "/bundled.rs"));

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

    /// What ships and what is in the repo are the same set.
    ///
    /// This is not the guard against the bug that prompted the change — no
    /// in-process test can be, because during `cargo test` the source tree is
    /// right there and the old code worked fine. That one is a CI job that
    /// runs the built binary with no checkout at all.
    ///
    /// What this catches is the next failure: a `.toml` added to the repo that
    /// `build.rs` does not pick up, which would ship an omh missing an adapter
    /// while every other test stayed green.
    #[test]
    fn everything_in_the_repo_is_embedded_byte_for_byte() {
        let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
        for kind in ALL {
            let mut on_disk: Vec<String> = std::fs::read_dir(root.join(kind.dir()))
                .unwrap()
                .map(|e| e.unwrap().path())
                .filter(|p| p.extension().is_some_and(|x| x == kind.ext()))
                .map(|p| p.file_name().unwrap().to_string_lossy().into_owned())
                .collect();
            on_disk.sort();

            let names: Vec<String> = kind.files().iter().map(|f| f.name.to_string()).collect();
            assert_eq!(
                names,
                on_disk,
                "{}: the binary and the repo disagree about what omh ships",
                kind.dir()
            );

            for file in kind.files() {
                let disk = std::fs::read_to_string(root.join(kind.dir()).join(file.name)).unwrap();
                assert_eq!(
                    file.contents,
                    disk,
                    "{}/{} was embedded stale",
                    kind.dir(),
                    file.name
                );
            }
        }
    }

    /// A directory that embedded as empty would install nothing and say so
    /// nowhere — the exact shape of the bug this module exists to remove.
    /// `build.rs` asserts it too; this is the half that fails in a test run
    /// rather than only on a machine that rebuilds.
    #[test]
    fn no_shipped_directory_is_empty() {
        for kind in ALL {
            assert!(!kind.files().is_empty(), "{} embedded as empty", kind.dir());
        }
    }

    /// `omh <anything>` is a harness, so a missing adapter is not a small loss.
    #[test]
    fn the_adapters_omh_launches_are_among_them() {
        let names: Vec<&str> = Shipped::Adapters.files().iter().map(|f| f.name).collect();
        assert!(names.contains(&"claude.toml"), "got: {names:?}");
        assert!(names.contains(&"opencode.toml"), "got: {names:?}");
    }

    /// Every shipped file carries the extension its directory declares.
    ///
    /// This used to assert `.toml` of everything, and said why: `install_bundled`
    /// named a backup `<file>.toml.yours` by *replacing* the extension, so the
    /// claim and the code held each other up. `hooks/` is JSON, so the claim is
    /// now false and the naming had to stop assuming it — the extension travels
    /// with the directory instead, and this checks the table against itself.
    #[test]
    fn every_shipped_file_carries_its_directorys_extension() {
        for kind in ALL {
            for file in kind.files() {
                assert!(
                    file.name.ends_with(&format!(".{}", kind.ext())),
                    "{}/{} is not a .{}",
                    kind.dir(),
                    file.name,
                    kind.ext()
                );
            }
        }
    }

    /// More than one extension actually ships, or the guard above is a
    /// tautology and `install_bundled`'s backup naming is untested for the case
    /// it was got wrong in.
    #[test]
    fn omh_ships_more_than_one_kind_of_file() {
        let kinds: std::collections::BTreeSet<&str> = ALL.iter().map(|k| k.ext()).collect();
        assert!(
            kinds.len() > 1,
            "every shipped directory is .{}, so nothing exercises a non-TOML \
             name: {kinds:?}",
            kinds.iter().next().unwrap_or(&"?")
        );
    }
}