···11+# AGENTS.md
22+33+This file provides guidance for agentic coding assistants working with the
44+Drinkup codebase.
55+66+## Project Overview
77+88+Drinkup is an Elixir library for consuming events from an ATProtocol relay
99+(firehose/`com.atproto.sync.subscribeRepos`). It uses OTP principles with
1010+GenStatem for managing WebSocket connections and Task.Supervisor for concurrent
1111+event processing.
1212+1313+## Build, Lint, and Test Commands
1414+1515+### Running Tests
1616+1717+```bash
1818+# Run all tests
1919+mix test
2020+2121+# Run a single test file
2222+mix test test/drinkup_test.exs
2323+2424+# Run a specific test by line number
2525+mix test test/drinkup_test.exs:5
2626+2727+# Run tests with coverage
2828+mix test --cover
2929+3030+# Run tests matching a pattern
3131+mix test --only [tag_name]
3232+```
3333+3434+### Formatting and Linting
3535+3636+```bash
3737+# Format code (uses .formatter.exs config)
3838+mix format
3939+4040+# Check if code is formatted
4141+mix format --check-formatted
4242+4343+# Run Credo for static code analysis
4444+mix credo
4545+4646+# Run Credo strictly
4747+mix credo --strict
4848+```
4949+5050+### Compilation and Documentation
5151+5252+```bash
5353+# Compile the project
5454+mix compile
5555+5656+# Clean build artifacts
5757+mix clean
5858+5959+# Generate documentation
6060+mix docs
6161+6262+# Run Dialyzer for type checking (if configured)
6363+mix dialyzer
6464+```
6565+6666+## Code Style Guidelines
6767+6868+### Module Structure
6969+7070+- Use `defmodule` with clear, descriptive names following `Drinkup.<Component>`
7171+ namespace
7272+- Place module documentation (`@moduledoc`) immediately after `defmodule`
7373+- Group related functionality within nested modules (e.g.,
7474+ `Drinkup.Event.Commit.RepoOp`)
7575+- Order module contents: module attributes, types, public functions, private
7676+ functions
7777+7878+### Imports and Aliases
7979+8080+- Use `require` for macros (e.g., `require Logger`)
8181+- Use `alias` to shorten module names, prefer explicit aliases over `import`
8282+- Group in order: `require`, `alias`, `import`
8383+- Example:
8484+ ```elixir
8585+ require Logger
8686+ alias Drinkup.{Event, Options}
8787+ ```
8888+8989+### Type Specifications
9090+9191+- Use TypedStruct for structs with typed fields (dependency:
9292+ `{:typedstruct, "~> 0.5"}`)
9393+- Define `@type` specs for complex types, unions, and public APIs
9494+- Use `@spec` for all public functions
9595+- Use `enforce: true` for required TypedStruct fields
9696+- Example:
9797+9898+ ```elixir
9999+ use TypedStruct
100100+101101+ typedstruct enforce: true do
102102+ field :consumer, module()
103103+ field :name, atom(), default: Drinkup
104104+ field :cursor, pos_integer() | nil, enforce: false
105105+ end
106106+ ```
107107+108108+### Naming Conventions
109109+110110+- Modules: PascalCase (`Drinkup.Event.Commit`)
111111+- Functions: snake_case (`handle_event/1`, `from/1`)
112112+- Variables: snake_case (`repo_op`, `last_seq`)
113113+- Private functions: prefix with `defp`, mark with `@spec` if complex
114114+- Atoms: lowercase with underscores (`:ok`, `:connect_timeout`)
115115+- Behaviours: use `@behaviour` (not `@behavior`)
116116+117117+### Function Definitions
118118+119119+- Pattern match in function heads when possible
120120+- Use guard clauses for simple type/value checks
121121+- Prefer multiple function heads over large case statements
122122+- Example:
123123+ ```elixir
124124+ def valid_seq?(nil, seq) when is_integer(seq), do: true
125125+ def valid_seq?(last_seq, nil) when is_integer(last_seq), do: true
126126+ def valid_seq?(last_seq, seq) when is_integer(last_seq) and is_integer(seq),
127127+ do: seq > last_seq
128128+ def valid_seq?(_last_seq, _seq), do: false
129129+ ```
130130+131131+### Error Handling
132132+133133+- Use `try/rescue` for expected errors, catch and log appropriately
134134+- Use Logger for errors:
135135+ `Logger.error("Message: #{Exception.format(:error, e, __STACKTRACE__)}")`
136136+- Return tagged tuples: `{:ok, result}` or `{:error, reason}`
137137+- Use `with` for chaining operations that may fail
138138+- Example from Socket module:
139139+ ```elixir
140140+ with {:ok, header, next} <- CAR.DagCbor.decode(frame),
141141+ {:ok, payload, _} <- CAR.DagCbor.decode(next),
142142+ {%{"op" => @op_regular}, _} <- {header, payload} do
143143+ # happy path
144144+ else
145145+ {:error, reason} -> Logger.warning("Failed to decode: #{inspect(reason)}")
146146+ end
147147+ ```
148148+149149+### OTP and Concurrency Patterns
150150+151151+- Use `child_spec/1` for custom supervisor specifications
152152+- Prefer `GenServer` for stateful processes, `:gen_statem` for state machines
153153+- Use `Task.Supervisor` for concurrent, fire-and-forget work (see
154154+ `Event.dispatch/2`)
155155+- Register processes via Registry for named lookups
156156+- Define proper restart strategies (`:permanent`, `:transient`, `:temporary`)
157157+158158+### Comments
159159+160160+- Avoid obvious comments; prefer self-documenting code
161161+- Use `# TODO:` for future improvements (see existing TODOs in codebase)
162162+- Use `# DEPRECATED` for deprecated fields (see Commit struct)
163163+- Document complex algorithms or non-obvious business logic
164164+- Use module-level `@moduledoc` and function-level `@doc` for public APIs
165165+166166+### Formatting
167167+168168+- Use `mix format` (configured in `.formatter.exs`)
169169+- Import deps for formatting: `import_deps: [:typedstruct]`
170170+- Line length: default Elixir formatter settings
171171+- Use 2-space indentation (enforced by formatter)
172172+173173+### Testing
174174+175175+- Use ExUnit for tests (files in `test/` with `_test.exs` suffix)
176176+- Use `use ExUnit.Case` in test modules
177177+- Use `doctest Module` for testing documentation examples
178178+- Tag tests for selective running: `@tag :integration`
179179+- Use descriptive test names: `test "validates sequence numbers correctly"`
180180+181181+## Project-Specific Patterns
182182+183183+### Consumer Behaviour Pattern
184184+185185+- Implement `@behaviour Drinkup.Consumer` with `handle_event/1` callback
186186+- Use pattern matching to handle different event types
187187+- Return any value; errors are caught by Task.Supervisor wrapper
188188+189189+### RecordConsumer Macro Pattern
190190+191191+- Use `use Drinkup.RecordConsumer` with `collections:` opt for filtering
192192+- Override `handle_create/1`, `handle_update/1`, `handle_delete/1` as needed
193193+- Collections can be exact strings or Regex patterns: `~r/app\.bsky\.graph\..+/`
194194+195195+### WebSocket State Machine
196196+197197+- Socket module uses `:gen_statem` with states: `:disconnected`,
198198+ `:connecting_http`, `:connecting_ws`, `:connected`
199199+- State functions match on events: `state_name(:enter, from, data)` or
200200+ `state_name(:info, msg, data)`
201201+- Use `{:next_event, :internal, event}` for internal state transitions
202202+203203+## Dependencies
204204+205205+- `{:gun, "~> 2.2"}` - HTTP/WebSocket client
206206+- `{:car, "~> 0.1.0"}` - CAR (Content Addressable aRchive) format
207207+- `{:cbor, "~> 1.0.0"}` - CBOR encoding/decoding
208208+- `{:typedstruct, "~> 0.5"}` - Typed structs
209209+- `{:credo, "~> 1.7"}` - Static analysis (dev/test only)
210210+211211+## Common Tasks
212212+213213+### Adding a New Event Type
214214+215215+1. Create `lib/event/your_event.ex` with TypedStruct definition
216216+2. Add `from/1` function to parse payload
217217+3. Add pattern match in `Drinkup.Event.from/2`
218218+4. Add to `@type t()` union in `Drinkup.Event`
219219+5. Update `CHANGELOG.md` under `[Unreleased]` section with the new feature
220220+221221+### Debugging Connection Issues
222222+223223+- Check `:gun` connection logs in Socket module
224224+- Verify sequence tracking with `Event.valid_seq?/2`
225225+- Monitor state transitions: `:disconnected` → `:connecting_http` →
226226+ `:connecting_ws` → `:connected`
227227+228228+## Changelog Management
229229+230230+**IMPORTANT**: After completing any feature or fixing a bug from a previous
231231+release, you MUST update `CHANGELOG.md`.
232232+233233+### Changelog Format
234234+235235+- Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format
236236+- Uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
237237+- Group changes under appropriate sections: `Added`, `Changed`, `Deprecated`,
238238+ `Removed`, `Fixed`, `Security`
239239+240240+### When to Update
241241+242242+- **New features**: Add under `## [Unreleased]` → `### Added`
243243+- **Bug fixes**: Add under `## [Unreleased]` → `### Fixed`
244244+- **Breaking changes**: Add under `## [Unreleased]` → `### Breaking Changes`
245245+- **Deprecations**: Add under `## [Unreleased]` → `### Deprecated`
246246+- **Security fixes**: Add under `## [Unreleased]` → `### Security`
247247+248248+### Example Entry
249249+250250+```markdown
251251+## [Unreleased]
252252+253253+### Added
254254+255255+- Support for `#handle` event type in firehose consumer
256256+257257+### Fixed
258258+259259+- Sequence validation now correctly handles nil cursor on initial connection
260260+```