Same shape in both apps now that coverage is wired: one suite run exports the coverage, then the sensors read it. AGENTS.md said "sensors ... and finally both test suites" and told sensor authors only to list targets in VERIFY_SENSORS; add the export dependency and the "a check must fail when its input is missing" rule, since `mix test.coverage` exits 0 when it finds no export.
7.4 KiB
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): appcredo --strict+ format, blogex compile (warnings fatal) + format, thenscripts/check-pins.shmake precommit- alias ofcheck; fast, safe to run on every commitmake test- both suites (app needs PostgreSQL on localhost:5432)make verify- the Lens pass:check, then per-app heavy sensors and both suitesmake 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 per app: one suite run that exports its coverage, then
that app's sensors (sobelow, coverage, CRAP, mutation, as those yaks land), which
run cheapest-first and read the export rather than re-running the suite. Measured
2026-11-01: ~11s wall (app 178 tests + blogex 156 tests + coverage floor).
.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
credooutput 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_SENSORSinapp/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 - theVERIFY_SENSORSlist is what gets echoed. Sensors run after the app'stest.coveragestep, because the coverage export they read is produced by that suite run; if a check can pass without its input, make it fail loudly (see thecoveragetarget's guard).
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.