An Elixir implementation of AT Protocol-flavoured Merkle Search Trees (MST)
1# AGENTS.md
2
3Guidance for agentic coding assistants working in this repository.
4
5## Project Overview
6
7`elixir-mst` is an Elixir library implementing AT Protocol-flavoured Merkle Search Trees (MST).
8
9## Build / Lint / Test Commands
10
11```bash
12# Compile
13mix compile
14
15# Format (run before committing)
16mix format
17
18# Check formatting without writing
19mix format --check-formatted
20
21# Lint
22mix credo
23
24# Run all tests
25mix test
26
27# Run a single test file
28mix test test/mst/foo_test.exs
29
30# Run a single test by line number
31mix test test/mst/foo_test.exs:42
32
33# Run doctests only
34mix test --only doctest
35
36# Generate docs
37mix docs
38```
39
40No custom Mix aliases are defined. There is no CI pipeline — validate locally.
41
42## Project Structure
43
44TODO
45
46## Code Style
47
48### Module Naming
49
50- Domain acronyms are all-caps: `MST`.
51- Sub-modules follow `Parent.Role`.
52- Module file path mirrors module name exactly.
53
54### Structs
55
56Use `TypedStruct` with `enforce: true` for all structs. Every field must be
57typed. Use `default:` only where a sensible zero value exists.
58
59```elixir
60typedstruct enforce: true do
61 field :version, pos_integer(), default: 1
62 field :roots, list(CID.t()), default: []
63 field :blocks, %{CID.t() => binary()}, default: %{}
64end
65```
66
67### Typespecs
68
69- Every public function must have `@spec`.
70- Every private function should have `@spec` where non-trivial.
71- Define named error type aliases at the top of each module, then reference them
72 in `@spec` annotations:
73
74```elixir
75@type header_error() :: {:error, :header, atom()}
76@type block_error() :: {:error, :block, atom()}
77@type decode_error() :: header_error() | block_error()
78```
79
80### Error Handling
81
82Consistent tagged-tuple convention — do not deviate:
83
84- Success: `{:ok, value}`
85- Simple error: `{:error, reason}`
86- Scoped error (CAR layer): `{:error, :scope, :reason}` — e.g.
87 `{:error, :header, :missing_roots}`, `{:error, :block, :cid_mismatch}`
88
89Use `with` chains for multi-step fallible operations; use `else` to remap errors
90when needed. Use `Enum.reduce_while` for fallible iteration — halt on first
91error.
92
93Bang variants (`parse_header!`, `validate_block!`) are only acceptable inside
94`StreamDecoder`-style modules where the documented contract is raise-on-error.
95Do not mix raise and tuple-return styles in the same module without explicit
96documentation of the contract.
97
98### Pattern Matching and Guards
99
100- Prefer multi-clause function heads for exhaustive dispatch over nested
101 conditionals.
102- Use bit-syntax binary pattern matching for low-level binary parsing.
103- Pair guards with pattern matches for validation constraints:
104
105```elixir
106when hash_size == @hash_size and byte_size(digest) == @hash_size
107```
108
109### Module Attributes for Constants
110
111Use `@` module attributes for all magic numbers and codec identifiers. Group
112them at the top of the module, after `@moduledoc`.
113
114```elixir
115@codec_raw 0x55
116@codec_drisl 0x71
117@hash_sha256 0x12
118@hash_size 32
119```
120
121### Pipes
122
123Use pipes where they read naturally. Do not force them. Prefer `with` over pipes
124for error-prone chains. The primary pipe use-case is stream pipelines:
125
126```elixir
127chunk_stream
128|> StreamDecoder.decode_stream(opts)
129|> Stream.map(&transform/1)
130```
131
132### Documentation
133
134- Every public module must have `@moduledoc` with a prose description and, where
135 applicable, a `Spec: <url>` line linking to the relevant spec.
136- Every public function must have `@doc` with:
137 - A prose description. Keep it high-level — do not repeat details already
138 covered by the spec (e.g. byte-level encoding rules, magic constants,
139 algorithm steps).
140 - An `## Options` section if the function accepts an options keyword list.
141 - An `## Examples` section with `iex>` doctests for the happy path and at
142 least one error case.
143- Use dashes (`-`) for all Markdown lists in `@moduledoc` and `@doc`. Never use
144 asterisks (`*`).
145
146### Protocol Implementations
147
148Implement `String.Chars` and `Inspect` for domain structs at the **bottom** of
149the file, outside the main module block — see `cid.ex` for the pattern.
150
151### Streaming
152
153Use `Stream.transform/4` with explicit start/reduce/after arities (not the
1543-arity shorthand) for stateful streaming parsers.
155
156### Section Separators
157
158Use `# ---...---` comment separators (78 dashes) to group related functions
159visually, consistent with existing source files.
160
161## Test Style
162
163- All test modules: `use ExUnit.Case, async: true`.
164- Pull doctests in at the top: `doctest MST.ModuleName`.
165- Use `describe/test` blocks — one `describe` per public function or logical
166 group.
167- Shared fixtures: define as `@` module attributes or `defp` helpers at the top
168 of the test module with a brief comment on their purpose.
169- Assertions use pattern matching: `assert {:ok, _} = ...`, not
170 `{:ok, val} = ...; assert val == ...`.
171- For stream decoder raise tests:
172 `assert_raise RuntimeError, ~r/pattern/, fn -> ... end`.
173- Do not couple decoder tests to encoder correctness — construct raw binaries
174 directly in test helpers when testing a decoder in isolation.
175- Test file paths must mirror `lib/` paths exactly.
176
177## Dependencies
178
179| Dep | Purpose |
180| -------------- | --------------------------------- |
181| `:dasl` | DASL primitives (CID, DRISL, CAR) |
182| `:typedstruct` | Typed struct DSL |
183| `:ex_doc` | Doc generation (dev only) |
184| `:credo` | Static analysis (dev + test) |
185
186No Dialyzer setup. No property-based testing. Do not add new dependencies
187without discussion — the dep surface is intentionally minimal.
188
189## Formatter
190
191`.formatter.exs` imports `:typedstruct` so `typedstruct do ... end` blocks
192format correctly. Default line length (98) applies. Always run `mix format`
193before committing.