# Agent Instructions This project has a *zero defects policy*. When you are unsure about something, ALWAYS ask the user. # Communication When creating yaks or working on features, always ask clarifying questions to ensure understanding. Ask questions one at a time to maintain clear communication and allow for focused discussion. You run in a `nono` sandbox. Always ask the user to run commands that require privilege, like docker, sudo or package installation. # Repository structure This is an Elixir monorepo with two parts: ``` firehose/ ├── app/ # Phoenix application (OTP app: :firehose) │ ├── lib/firehose/ # Application logic │ ├── lib/firehose_web/ # Web layer (controllers, live views, components) │ ├── lib/firehose/blogs/ # Blog definitions (engineering, release notes) │ ├── priv/blog/ # Markdown posts │ └── mix.exs ├── blogex/ # Blogex library (multi-blog engine) │ ├── lib/ │ └── mix.exs ├── mise.toml # Runtime versions (Elixir, Erlang, Node) ├── Dockerfile # Deploy build; ARG pins must match mise.toml ├── scripts/check-pins.sh # Verifies the pin sync; runs in `make check` ├── .githooks/pre-push # Lens pass on push; install once with `make hooks` └── Makefile # make (check, verify, test, hooks) ``` Two blogs are configured: # Toolchain pins `mise.toml` and the `Dockerfile` ARG pins must agree; `scripts/check-pins.sh` (runs as part of `make check`) enforces this. When bumping versions, change both files in one commit. Upstream drift (newer Elixir/OTP/Debian releases) is reported by `dokku-updates firehose check` (dokku-maintenance repo). Upgrading pins or mix packages is largely unscripted: for such tasks, a Pi sub agent can be started in this directory — it reads this AGENTS.md and follows the zero-defects policy above. | Blog | Route | Description | |------|-------|-------------| | Engineering | `/blog/engineering` | Main blog | | Release Notes | `/blog/releases` | What's new in an app | # Permissions ALWAYS ask the user for permission when you want to add a fallback, or are unsure something work. The user can perform exploratory testing. We have a zero defects policy, so never continue when there are errors, warnings or test failures, even when they are pre-existing. ALWAYS report these to the user and discuss an action plan with root cause analysis. (see TDD below). # Planning we use `yx` for planning run yx --help to see options. Invoke when the user wants to make a plan, work on something for a plan or mentions 'yak' or 'yaks'. `yx list` - shows open yaks `yx add` - adds yaks `yx context "yak name"` - adds human-readable description (pipe via stdin) `yx show "yak name"` - shows yak details including context # Building This is a Phoenix Liveview monorepo, the blogging library is in blogex, the application is in 'app'. Use `make` to build and `brief` to find out more about mix. Main make targets: - `make check` - cheap static gate for both apps (no DB): app `credo --strict` + format, blogex compile (warnings fatal) + format, then `scripts/check-pins.sh` - `make precommit` - alias of `check`; fast, safe to run on every commit - `make test` - both suites (app needs PostgreSQL on localhost:5432) - `make verify` - the Lens pass: `check`, then per-app heavy sensors and both suites - `make hooks` - install `.githooks/*` into this clone (once per clone) # Verification: fast loop vs Lens pass Two loops, deliberately split so commits stay cheap and pushes stay honest. **Fast loop (every commit).** `make check` is static analysis only: no database, no coverage, about a second. `make precommit` is the same target. Per app, `mix precommit` (run inside `app/` or `blogex/`) is the fuller loop: compile with warnings fatal, format, test. Run these as often as you like while working. **Lens pass (every push, and before handing off).** `make verify` runs `check` first (cheapest), then each app's heavy sensors - sobelow, coverage, CRAP, mutation, as those yaks land - and finally both test suites. Measured 2026-10-26: ~10s wall (app 178 tests + blogex 156 tests). `.githooks/pre-push` runs `make verify` and blocks the push when anything fails. Install it once per clone: make hooks It symlinks `.githooks/*` into the clone's hooks directory; `core.hooksPath` is deliberately left alone so bd's own hooks (pre-commit JSONL flush, post-merge import) keep firing. On failure the hook prints one summary - last step started, log path, bypass - and exits non-zero; the full output is in `.git/lens-pass.log`. Deliberate override (e.g. pushing a branch you know is red): `git push --no-verify`. ## Lens practice The Lens pass is a *separate pass with its own context*. It is not a way to find work mid-feature: finish and commit what you are building, then run it. - Do not chase hook or `credo` output inline while building a feature. If something interesting surfaces, note it and file a yak; take it up in a Lens session. - A failing Lens pass blocks the push, and therefore the hand-off. Triage it; never re-run until green and never ignore it. - Adding a sensor: implement it as its own make target, add it to that app's `VERIFY_SENSORS` in `app/Makefile` / `blogex/Makefile` (cheapest first), and update the slot comments there. A gate that reports a check it did not run is worse than no gate - the `VERIFY_SENSORS` list is what gets echoed. ### Triage classes | Class | Looks like | What to do | |-------|-----------|------------| | *Real bug* | behavior is wrong; a test fails for a real reason; a sensor reports a real risk | fix inline, with a test that pins the behavior | | *Environment-dependent test* | passes alone, fails in the pass; depends on ambient state (rows left behind, wall clock, ordering, network, locale) | the test is the bug: make it deterministic by construction - own fixtures, explicit clock, no ambient state as fixtures. Never retry-until-green | | *Style or design nit* | credo or format suggestion, naming, structure, "this could be smaller" | do not refactor mid-build: note it and file a yak; the Lens session decides | Reconciling this with the zero-defects policy: "never continue when there are errors" governs *what you do with a failure* - report, triage, decide - not *when* you fix it. Never silently absorb a finding, and never let a pre-existing failure ride along: if it predates your change, stop and discuss it with the user rather than working around it. Browser tests (`make playwright*`) are *external* verification and are **not** part of the Lens pass. Whether they ever join it is an open decision (see the yak "ratchets, deferred decisions, handoff practice"). # Development Always fix failing tests, credo issues and format isseus. When developing new features, we apply TDD. Work Test-First, and Refactor when all tests are passing. If you believe an issue is pre-existing, stop work, and have a conversation with the user on how to address the pre-existing issue, as well as how to prevent this in the future.