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
change tokzmzmsynssrvlrzrxxlvtnmtzxotxt
commit fe9a05572ba9780eda2ffa19ca1878e06e4ca22d
author Alpha Chen <alpha@kejadlen.dev>
date
parent zunnrknz
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.