AGENTS.md#
Guidance for agentic coding assistants working in this repository.
Project Overview#
elixir-mst is an Elixir library implementing AT Protocol-flavoured Merkle Search Trees (MST).
Build / Lint / Test Commands#
# Compile
mix compile
# Format (run before committing)
mix format
# Check formatting without writing
mix format --check-formatted
# Lint
mix credo
# Run all tests
mix test
# Run a single test file
mix test test/mst/foo_test.exs
# Run a single test by line number
mix test test/mst/foo_test.exs:42
# Run doctests only
mix test --only doctest
# Generate docs
mix docs
No custom Mix aliases are defined. There is no CI pipeline — validate locally.
Project Structure#
TODO
Code Style#
Module Naming#
- Domain acronyms are all-caps:
MST. - Sub-modules follow
Parent.Role. - Module file path mirrors module name exactly.
Structs#
Use TypedStruct with enforce: true for all structs. Every field must be
typed. Use default: only where a sensible zero value exists.
typedstruct enforce: true do
field :version, pos_integer(), default: 1
field :roots, list(CID.t()), default: []
field :blocks, %{CID.t() => binary()}, default: %{}
end
Typespecs#
- Every public function must have
@spec. - Every private function should have
@specwhere non-trivial. - Define named error type aliases at the top of each module, then reference them
in
@specannotations:
@type header_error() :: {:error, :header, atom()}
@type block_error() :: {:error, :block, atom()}
@type decode_error() :: header_error() | block_error()
Error Handling#
Consistent tagged-tuple convention — do not deviate:
- Success:
{:ok, value} - Simple error:
{:error, reason} - Scoped error (CAR layer):
{:error, :scope, :reason}— e.g.{:error, :header, :missing_roots},{:error, :block, :cid_mismatch}
Use with chains for multi-step fallible operations; use else to remap errors
when needed. Use Enum.reduce_while for fallible iteration — halt on first
error.
Bang variants (parse_header!, validate_block!) are only acceptable inside
StreamDecoder-style modules where the documented contract is raise-on-error.
Do not mix raise and tuple-return styles in the same module without explicit
documentation of the contract.
Pattern Matching and Guards#
- Prefer multi-clause function heads for exhaustive dispatch over nested conditionals.
- Use bit-syntax binary pattern matching for low-level binary parsing.
- Pair guards with pattern matches for validation constraints:
when hash_size == @hash_size and byte_size(digest) == @hash_size
Module Attributes for Constants#
Use @ module attributes for all magic numbers and codec identifiers. Group
them at the top of the module, after @moduledoc.
@codec_raw 0x55
@codec_drisl 0x71
@hash_sha256 0x12
@hash_size 32
Pipes#
Use pipes where they read naturally. Do not force them. Prefer with over pipes
for error-prone chains. The primary pipe use-case is stream pipelines:
chunk_stream
|> StreamDecoder.decode_stream(opts)
|> Stream.map(&transform/1)
Documentation#
- Every public module must have
@moduledocwith a prose description and, where applicable, aSpec: <url>line linking to the relevant spec. - Every public function must have
@docwith:- A prose description. Keep it high-level — do not repeat details already covered by the spec (e.g. byte-level encoding rules, magic constants, algorithm steps).
- An
## Optionssection if the function accepts an options keyword list. - An
## Examplessection withiex>doctests for the happy path and at least one error case.
- Use dashes (
-) for all Markdown lists in@moduledocand@doc. Never use asterisks (*).
Protocol Implementations#
Implement String.Chars and Inspect for domain structs at the bottom of
the file, outside the main module block — see cid.ex for the pattern.
Streaming#
Use Stream.transform/4 with explicit start/reduce/after arities (not the
3-arity shorthand) for stateful streaming parsers.
Section Separators#
Use # ---...--- comment separators (78 dashes) to group related functions
visually, consistent with existing source files.
Test Style#
- All test modules:
use ExUnit.Case, async: true. - Pull doctests in at the top:
doctest MST.ModuleName. - Use
describe/testblocks — onedescribeper public function or logical group. - Shared fixtures: define as
@module attributes ordefphelpers at the top of the test module with a brief comment on their purpose. - Assertions use pattern matching:
assert {:ok, _} = ..., not{:ok, val} = ...; assert val == .... - For stream decoder raise tests:
assert_raise RuntimeError, ~r/pattern/, fn -> ... end. - Do not couple decoder tests to encoder correctness — construct raw binaries directly in test helpers when testing a decoder in isolation.
- Test file paths must mirror
lib/paths exactly.
Dependencies#
| Dep | Purpose |
|---|---|
:dasl |
DASL primitives (CID, DRISL, CAR) |
:typedstruct |
Typed struct DSL |
:ex_doc |
Doc generation (dev only) |
:credo |
Static analysis (dev + test) |
No Dialyzer setup. No property-based testing. Do not add new dependencies without discussion — the dep surface is intentionally minimal.
Formatter#
.formatter.exs imports :typedstruct so typedstruct do ... end blocks
format correctly. Default line length (98) applies. Always run mix format
before committing.