# AGENTS.md ## Project: Roux A personal/family recipe management site. See [README.org](./README.org) for full project description. ### Key facts - **Backend**: Haskell + SQLite - **Frontend**: Static HTML + Pico CSS - **Recipe format**: Cooklang (https://cooklang.org/) - **Development**: Docker-based; nothing required locally except Docker - **Subprojects**: - `cooklang-hs/` — Cooklang parser in Haskell - `roux-server/` — Web server, file indexing, HTML rendering ### Development environment All Haskell tooling runs via the `hs` wrapper script, which runs inside the [flipstone/haskell-tools](https://github.com/flipstone/haskell-tools) Docker image. Adopted from the pattern in the Atlas project. #### Setup 1. Create the `hs` wrapper script in the project root: ```bash cat > hs << 'SCRIPT' #!/usr/bin/env bash # Thin wrapper to run Haskell tooling (stack, hpack, fourmolu, hlint, ...) # inside the flipstone/haskell-tools Docker image. Usage: ./hs [args] # e.g. ./hs stack build, ./hs stack test, ./hs hpack, ./hs fourmolu set -euo pipefail IMAGE="${HAWAT_HASKELL_TOOLS_IMAGE:-ghcr.io/flipstone/haskell-tools:debian-ghc-9.10.3-5d6640d}" PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" STACK_ROOT_HOST="${PROJECT_DIR}/.stack-root" mkdir -p "${STACK_ROOT_HOST}" exec docker run --rm -i $([ -t 0 ] && printf -- -t) \ -v "${PROJECT_DIR}:/work" \ -v "${STACK_ROOT_HOST}:/stack-root" \ -e STACK_ROOT=/stack-root \ -w /work \ "${IMAGE}" \ "$@" SCRIPT chmod +x hs ``` 2. Create `.stack-root/` in the project root to persist GHC/cache across runs: ```bash mkdir -p .stack-root ``` 3. Initialize a Haskell project with `stack` or `hpack`: ```bash ./hs stack new cooklang-hs simple --dir cooklang-hs # or for an existing project: ./hs stack build ``` #### Usage examples ```bash ./hs stack build # Build all subprojects ./hs stack test # Run tests ./hs stack ghci # REPL ./hs hpack # Regenerate .cabal from package.yaml ./hs fourmolu -i **/*.hs # Format Haskell sources ./hs hlint src/ # Lint sources ``` #### Environment variables - `HAWAT_HASKELL_TOOLS_IMAGE` — override the Docker image tag (defaults to `ghcr.io/flipstone/haskell-tools:debian-ghc-9.10.3-5d6640d`) ### Agent conventions - Prefer the `hs` wrapper over local tooling — match the Docker-first dev environment. - Run `./hs stack build` and `./hs stack test` before marking work as complete. - When adding a new subproject (e.g. `roux-server`), initialize it with `./hs stack new`. - Use `hpack` (via `./hs hpack`) for package metadata in `package.yaml` format. - When agent-created files are owned by root, fix ownership with `sudo chown -R $(id -u):$(id -g) .`. ### Type safety and pattern matching This project compiles with `-Wall -Werror`, including `-Wincomplete-uni-patterns` and `-Wincomplete-record-updates`. Partial functions and irrefutable pattern matches (`let (Just x) = ...`, `(Just x, _) <- ...`) will fail at runtime or be rejected by the compiler. **Rules:** - **Never** use irrefutable/partial pattern matches on `Maybe`, `Either`, or tuple results from `IO` actions. Always use `case` with an explicit branch for each constructor, or use pattern-matching helpers (`maybe`, `either`). - **Never** call partial functions: `head`, `tail`, `init`, `last`, `fromJust`, `read` (use `readMaybe`), `succ`, `pred`, `(!!)`. Use total alternatives: `foldl'` for `head`/`tail` on lists, `Data.Map.lookup` for map access, `take 1` or pattern matching on `NonEmpty`. - **Never** use `error`, `undefined`, or `panic` for recoverable failures. Use `Either`, `Maybe`, or explicit `IO` error handling (`ioError`, `Control.Exception.throwIO`). - **Always** fully handle all constructors in case expressions. Use a wildcard (`_ ->`) only when you genuinely don't care about the value, and add a comment explaining why. - **Prefer** `case` over `<-` pattern binds in `do` notation when the pattern could conceivably fail at runtime.