docs: Sync AGENTS.md with actual repo state
Layout listed a nonexistent root Dockerfile (moved to .ramekin/),
had a duplicate fennel-1.6.0.lua entry, and omitted config files
(.cargo/, .clippy.toml, .envrc, README.md). Architecture section
now covers all six built-in assertion types, CLI subcommands,
frork.utils, and the Registry dispatch mechanism.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/AGENTS.md b/AGENTS.md
index b4f8b01..4f61ca9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,27 +7,38 @@ frork is a Fennel-based configuration management tool inspired by [bork](https:/
## Repository layout
```
-Cargo.toml # Workspace root (frork-cli, frork-lib, frork-lua)
-Dockerfile # Ramekin agent container (Node.js + pi + Rust nightly)
+Cargo.toml # Workspace root (frork-cli, frork-lib, frork-lua)
+Cargo.lock
+README.md
+justfile # Local dev tasks (fmt, clippy, coverage, mutants, install)
+.clippy.toml # Enforces fs-err over std::fs, bans for_each
+.envrc # direnv config
+.gitignore
+.cargo/
+ config.toml # macOS linker flags for cdylib, rust-analyzer target dir
+ mutants.toml # cargo-mutants exclusions
+.ramekin/
+ Dockerfile # Agent container (Node.js + pi + Rust nightly)
+.github/workflows/
+ ci.yml # fmt + clippy + coverage (mutants commented out)
+ release.yml # CalVer release on CI success
frork-cli/
- Cargo.toml # Binary crate — the `frork` CLI
- fennel-1.6.0.lua # Vendored Fennel compiler
+ Cargo.toml # Binary crate — the `frork` CLI
+ fennel-1.6.0.lua # Vendored Fennel compiler
src/
- main.rs # Entrypoint, clap CLI, Lua/Fennel setup
- lib.rs # Library root — re-exports modules
- assertions.rs # Assertion types (symlink, directory, git, brew, lua)
- error.rs # thiserror enum (FrorkError)
- utils.rs # Shell helpers, path expansion, Lua bindings
+ main.rs # Entrypoint, clap CLI, Lua/Fennel setup, Frork runtime
+ lib.rs # Library root — re-exports modules
+ assertions.rs # Assertion types and the AssertionType trait
+ error.rs # thiserror + miette::Diagnostic enum (FrorkError)
+ utils.rs # Shell helpers, path expansion, Lua bindings
tests/
- cli.rs # Integration tests via assert_cmd
+ cli.rs # Integration tests via assert_cmd
frork-lib/
- Cargo.toml # Shared library crate (currently empty)
+ Cargo.toml # Shared library crate (currently empty)
src/lib.rs
frork-lua/
- Cargo.toml # Lua C module crate (cdylib)
- src/lib.rs # mlua module exposing frork to Lua
-justfile # Local dev tasks (fmt, check, clippy, coverage)
-.github/workflows/ # CI (fmt + clippy + coverage) and CalVer release
+ Cargo.toml # Lua C module crate (cdylib)
+ src/lib.rs # mlua module exposing frork to Lua
```
## Build and test
@@ -42,23 +53,77 @@ cargo test -p frork-cli # Run tests
Or use `just` which runs fmt, clippy, and coverage together:
```sh
-just
+just # default: fmt + clippy + coverage
+just mutants # mutation testing via cargo-mutants
+just install # cargo install --locked --path frork-cli
```
-**Note:** `cargo check --workspace` fails due to mutually exclusive mlua features (`vendored` in frork-cli vs `module` in frork-lua). Check crates individually.
+`cargo check --workspace` fails because frork-cli uses mlua's `vendored` feature and frork-lua uses `module` — these are mutually exclusive. Always check crates individually.
## Conventions
-- **Rust edition 2024**, resolver v3 workspace.
+- Rust edition 2024, resolver v3 workspace.
- Error handling: `miette` in the binary, `thiserror` in library code. Library errors derive `miette::Diagnostic`.
- Logging uses `tracing` with `tracing-subscriber`. Use `tracing::info`, `tracing::debug`, etc. — not `println!` for diagnostic output.
-- Lua integration via `mlua` with vendored Lua 5.4. Fennel compiler is vendored as a Lua source file.
+- Lua integration via `mlua` with vendored Lua 5.4. The Fennel compiler is vendored as a Lua source file.
+- Filesystem operations use `fs-err` instead of `std::fs`. The `.clippy.toml` disallows bare `std::fs` types and methods so this is enforced at lint time.
+- `.clippy.toml` also bans `Iterator::for_each` and `try_for_each` — use `for` loops for side effects.
- All CI checks must pass: `cargo fmt --all --check`, `cargo clippy`, `cargo test`.
## Architecture notes
-- **Assertion model:** Each assertion type implements the `AssertionType` trait (`status`, `install`, `upgrade`, `remove`). Status returns `Ok`, `Missing`, or `ConflictUpgrade`.
-- **Lua/Fennel bridge:** `setup_lua` creates a Lua VM, loads the Fennel compiler, and registers a `frork` module with `ok` (assert) and `register` (define custom assertion types) functions.
-- **Custom assertions:** Fennel scripts can register new assertion types via `frork.register(name, {status=fn, install=fn})` — these become `LuaAssertionType` values dispatched through the same trait.
-- **Path expansion:** `ExpandedPath` handles `~` and `$ENV_VAR` expansion at the Lua/Rust boundary.
-- **frork-lua vs frork-cli:** frork-lua builds a `cdylib` for use as a standalone Lua module (`require("frork")`). frork-cli embeds Lua/Fennel and is the primary interface. The two crates cannot be built together due to conflicting mlua features.
+### CLI
+
+The CLI (`frork`) uses clap with four subcommands:
+
+- `check <code>` — evaluate inline Fennel code, report status only
+- `do <code>` — evaluate inline Fennel code, satisfy missing assertions
+- `status <script>` — evaluate a `.fnl` script file, report status only
+- `satisfy <script>` — evaluate a `.fnl` script file, satisfy missing assertions
+
+A `--completions <shell>` flag generates shell completions and exits.
+
+### Assertion model
+
+Each assertion type implements the `AssertionType` trait (`status`, `install`, `upgrade`, `remove`). Status returns `Ok`, `Missing`, or `ConflictUpgrade`.
+
+Built-in assertion types:
+
+- `symlink` — manages symlinks (target, source)
+- `directory` — ensures a directory exists
+- `git` — clones a git repo to a directory, checks the remote URL
+- `brew` — checks whether Homebrew is installed
+- `brew-bundle` — runs `brew bundle check`/`install` against a Brewfile (macOS only)
+- `debug` — accepts Lua functions for status/install, used for testing and one-off assertions
+
+The `Registry` struct dispatches assertion types. It checks Lua-registered types first, then falls back to built-in types. Each built-in type is created through a `TypedFactory<T>` that handles Lua argument conversion via `FromLuaMulti`.
+
+### Lua/Fennel bridge
+
+`setup_lua` creates a Lua VM, loads the Fennel compiler, and registers a `frork` module with:
+
+- `frork.ok(type, ...)` — assert that a condition holds (dispatches through the registry)
+- `frork.register(name, {status=fn, install=fn})` — register a custom assertion type from Fennel
+- `frork.utils` — utility functions exposed to Lua/Fennel scripts
+
+### Utils module
+
+`frork.utils` exposes these functions to Lua:
+
+- `expand-path` — expands `~` and `$ENV_VAR` in paths
+- `dirname` — returns the parent directory of a path
+- `chomp` — trims trailing newlines
+- `platform` — returns the lowercase OS name (via `uname -s`)
+- `sh` — runs a shell command, returns `(stdout, exit_code)` or `(nil, -1)` on failure
+- `sh!` — like `sh` but propagates errors to Lua
+- `assert-bin` — checks that a binary exists in PATH
+
+On the Rust side, `ExpandedPath` handles `~` and `$ENV_VAR` expansion at the Lua/Rust boundary and implements `FromLua` for transparent conversion.
+
+### Custom assertions
+
+Fennel scripts can register new assertion types via `frork.register(name, {status=fn, install=fn, display=fn})`. These become `LuaAssertionType` values dispatched through the same `AssertionType` trait. The optional `display` function controls how the assertion is printed.
+
+### frork-lua vs frork-cli
+
+frork-lua builds a `cdylib` for use as a standalone Lua module (`require("frork")`). frork-cli embeds Lua/Fennel and is the primary interface. The two crates cannot be built together due to conflicting mlua features.