An Elixir implementation of AT Protocol-flavoured Merkle Search Trees (MST)
at main 193 lines 5.7 kB view raw view rendered
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.