Skip to content
BobbieBarkerPublic

About

Your coding agent knows Elixir syntax. These are the mechanism-level rules it gets wrong. 42 ADRs on OTP, conventions, Ecto, and the official anti-patterns, each with a Wrong example, a Correct example, and the BEAM reason behind the difference. Drops into Cursor and Claude Code.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

Repository files navigation

adrs

Architecture Decision Records, packaged as agent context. ADRs contain independently citable Rules with Correct/Wrong/Why examples, or a complete narrative decision whose sections need to be read together. Written for engineers and shaped for LLMs to consume: the format reads cleanly as a reference and drops into Cursor, Claude Code, Aider, and custom retrieval-based agents so the guidance loads automatically when you write or review code.

The rules are grouped into domains. Each domain is a self-contained set with its own pre-rendered bundles under dist/<domain>/; adopt one or several.

Domains

Domain Covers ADRs
software-design Language-agnostic module boundaries, information hiding, cohesion, coupling, and interface costs 1
elixir-otp OTP, GenServer, supervision, BEAM scheduling, interprocess data, message routing, stateful-process testing 13
elixir-conventions Structural dispatch, runtime algorithm selection, pipeline composition, structs, validation-primitive selection, with chains, streaming, effect compensation, structured errors 9
elixir-ecto Transaction boundaries and the commit contract 1
elixir-resilience Calls to external services under failure: upstream rate limits, shared backpressure, coordinated recovery 1
elixir-code-anti-patterns The official Elixir code anti-patterns 10
elixir-design-anti-patterns The official Elixir design anti-patterns 6
elixir-macro-anti-patterns The official Elixir meta-programming anti-patterns 5

software-design

How to organize responsibilities so a change can remain local: identify the knowledge a module owns, preserve its invariants, and compare the benefit of a boundary with the understanding and coordination its interface requires. The decision applies across languages and includes cases where keeping related code together produces the better design.

elixir-otp

Working with GenServer and the BEAM rewards a precise mental model of how they actually behave. The wrong model produces the same predictable production failures: a callback blocks the processing loop, a process hoards state and pays for it in GC pauses, a cast pipeline grows an unbounded mailbox, terminate/2 cleanup is silently skipped on a brutal kill, and a shared PubSub topic makes message volume quadratic while every receiver looks like it is filtering. Humans build the wrong model. LLMs build the wrong model, often more confidently.

elixir-conventions

These are the decisions a reader makes constantly and rarely writes down: which construct expresses a branch, whether an algorithm is chosen by the value or by the wiring, when a pipeline beats a rebinding, what a domain entity is made of, which primitive validates a value on the way in, what may appear inside a with, and what a failure looks like once it leaves the function that produced it. Each rule names the compiler or runtime mechanism that separates the correct form from the plausible one.

elixir-ecto

Database access decisions. Currently one: the transaction wrapper whose commit is decided by inspecting what your callback returned, rather than by whether it happened to raise.

elixir-resilience

What happens at the boundary when the other side pushes back. HTTP clients model retry as a property of a request, but a rate limit applies to a provider-defined quota bucket. Callers in that bucket should share a known cooldown without stopping unrelated traffic.

elixir-code-anti-patterns

The code-related anti-patterns from the official Elixir documentation, each codified as one rule: the mistake as the Wrong example, the refactoring as the Correct example, and the mechanism in the Why.

elixir-design-anti-patterns

The design-related anti-patterns from the official Elixir documentation: return-type consistency, boolean and primitive obsession, exceptions for control flow, unrelated multi-clause functions, and library configuration.

elixir-macro-anti-patterns

The meta-programming anti-patterns from the official Elixir documentation: compile-time dependencies (tracked and untracked), code-generation size, unnecessary macros, and use versus import.

The anti-pattern domains codify the patterns documented at https://hexdocs.pm/elixir/what-anti-patterns.html. The process anti-patterns are not a separate domain because elixir-otp already covers that ground: Code organization by process and Scattered process interfaces fall under ADR-001 and ADR-002, and Sending unnecessary data and Unsupervised processes are ADR-009 and ADR-010. Nothing is restated across domains.

Integration

Pre-rendered bundles live in dist/<domain>/, one set per domain. Pick your domain (the examples below use elixir-otp; substitute any domain from the table above) and your harness.

Cursor

# from your project root
mkdir -p .cursor/rules
cp /path/to/adrs/dist/elixir-otp/cursor/*.mdc .cursor/rules/

Each rule's behavior is set by its frontmatter:

  • Rules with globs auto-attach when you edit a matching file ("Apply to Specific Files").
  • Rules without globs are pulled in by the agent when their description is relevant ("Apply Intelligently").

Reference: https://cursor.com/docs/context/rules

Claude Code

# from your project root
cp /path/to/adrs/dist/elixir-otp/claude-code/CLAUDE.md ./CLAUDE.md
mkdir -p .claude/rules
cp /path/to/adrs/dist/elixir-otp/claude-code/.claude/rules/*.md .claude/rules/

If your project already has a CLAUDE.md, append the contents of the rendered one rather than overwriting. The per-file copy of the rules avoids touching any existing .claude/agents/, .claude/settings.json, or unrelated rule files.

Claude Code reads CLAUDE.md at the start of every session and picks up files in .claude/rules/ automatically. Rules with paths: frontmatter auto-attach when Claude reads matching files; rules without paths load into every session unconditionally.

Reference: https://code.claude.com/docs/en/memory

Aider, raw API harnesses, one-off use

aider --read /path/to/adrs/dist/elixir-otp/bundle.md

Aider has no auto-discovery; you always pass the file via --read. To avoid retyping the path on every invocation, put it in .aider.conf.yml:

# .aider.conf.yml
read:
  - /path/to/adrs/dist/elixir-otp/bundle.md

bundle.md is the domain's ADRs concatenated into one file. It pays full token cost on every turn and gets cached if prompt caching is enabled. Use the harness-specific bundles above when you can; fall back to this when you can't.

References: https://aider.chat/docs/usage/conventions.html, https://aider.chat/docs/config/aider_conf.html

Custom harness or your own retriever (RAG)

New retrievers should consume dist/<domain>/retrieval.jsonl. It is a model-neutral, versioned package of four record kinds:

  • adr_summary routes broad architectural questions and carries the ADR's Context, direct Decision prose, and Consequences.
  • rule is the minimum answer unit. Its retrieval_text contains the prescription and Why; its display_text preserves the complete Rule, including examples.
  • example makes Correct and Wrong code independently searchable while retaining explicit positive or negative polarity. Its hydrate_id points back to the complete Rule.
  • supporting preserves useful Decision subsections that are not numbered Rules.

The v1 Rule authoring grammar has exactly one Correct, one Wrong, and one Why region, in that order. A Correct or Wrong region may contain multiple fenced snippets, prose between those fences, and deeper Markdown headings; those pieces remain one example region. Repeating a Correct, Wrong, or Why label is rejected, and the generated Correct and Wrong record IDs therefore end in the per-polarity index :01.

A registry entry can explicitly declare structure: narrative when the decision's sections need to travel together. The default remains structure: rules with the grammar above. A narrative ADR produces an adr_summary and one supporting record, <domain>:adr-NNN:supporting:decision, containing the complete Decision through any review criteria before Consequences. Its examples remain inside that record; they are not assigned standalone Correct or Wrong polarity. The supporting record hydrates to itself and links to its ADR summary for Context. Narrative mode rejects numbered Rule headings and Correct/Wrong/Why labels to prevent accidental mixing of the two structures. Both modes use the existing retrieval-v1 schema.

The v1 contract is published as schema/retrieval-v1.schema.json, and dist/retrieval-catalog.json inventories every domain artifact with counts and SHA-256 checksums. Stable semantic record_id values support deterministic lookup for explicit citations such as elixir-otp ADR-005 Rule 2; content hashes report change, but are never identifiers. Future incompatible retrieval changes require a new schema and artifact name or an explicit migration; they must not silently redefine retrieval-v1 semantics.

For every record, source_start_line and source_end_line are the inclusive provenance envelope containing the source regions that contributed to display_text. Rule, example, and supporting display_text each draw from one contiguous source region; an adr_summary combines Context, direct Decision prose, and Consequences, so its contributing regions may be discontiguous and its envelope may contain intervening Rule text absent from display_text. retrieval_text may add routing metadata, parent Context, a Rule statement, or Why text outside that display envelope; the source line fields do not claim to bound those search-only enrichments. source_sha256 is the SHA-256 of the exact UTF-8 bytes of display_text, not a hash of every source line in that envelope. retrieval_sha256 likewise hashes the exact UTF-8 bytes of retrieval_text.

A recommended retrieval flow is:

  1. Resolve explicit ADR or Rule citations by record_id before similarity search.
  2. Embed retrieval_text for semantic search.
  3. Build a lexical/BM25 index over both retrieval_text and display_text, then fuse semantic and lexical rankings, preferably with reciprocal rank fusion (RRF).
  4. Filter or boost by accepted status, an explicitly requested domain, tags, and applies_to.
  5. Collapse matches by hydrate_id so several example hits do not crowd out other Rules.
  6. Return the complete Rule and load its ADR parent's Context.

Never present a negative-polarity example as standalone guidance: hydrate it to its Rule so the prescription and Why travel with the anti-pattern. With only a few hundred records, exhaustive cosine or dot-product search is sufficient. Approximate-nearest-neighbor indexes, rerankers, embedding providers, and vector databases remain downstream choices.

The evaluation judgments under eval/ exercise terminology, paraphrases, code anti-patterns, hard negatives, cross-domain questions, citation routing, and abstention. tools/score_retrieval.exs scores a retriever's JSONL results without depending on any embedding vendor. Supply one result row per query, in this form:

{"query_id":"q:elixir-otp:adr-005:rule-03:scenario","results":[{"record_id":"elixir-otp:adr-005:rule-03","score":0.9}],"abstained":false}

Then run:

elixir tools/score_retrieval.exs --queries eval --results results.jsonl --manifest dist --format json

Whole-ADR compatibility

dist/<domain>/adrs.jsonl remains one ADR per row, with its existing id, domain, title, description, tags, applies_to, and body fields. Existing consumers may continue embedding body; no migration is required. New integrations should prefer retrieval.jsonl because its rule-level hierarchy, polarity, source locations, and hydration links provide more precise results.

The applies_to shape (paths globs and content_match substrings) is the same routing data used to generate the harness-specific bundles, so a custom retriever can match those bundles' behavior by consuming it directly.

Obsidian + qmd / ClawVault

The ADRs use vault-conformant frontmatter (type: adr, id, title, status, date, tags, description). ISO date and optional updated values are intentionally quoted so YAML readers retain YYYY-MM-DD strings rather than implicitly converting them to date objects. Clone the repo into your vault's directory and existing semantic-retrieval tooling will index them.

Repo layout

adrs/<domain>/
├── adr-rules.yaml          # path/content patterns per ADR
└── adr-NNN-*.md            # one ADR per file

dist/
├── retrieval-catalog.json  # domain inventory, counts, and checksums
└── <domain>/               # generated; do not edit by hand
    ├── cursor/             # Cursor .mdc rules
    ├── claude-code/        # CLAUDE.md + .claude/rules/
    ├── retrieval.jsonl     # hierarchical retrieval-v1 records
    ├── adrs.jsonl          # legacy one-ADR-per-row interface
    └── bundle.md           # concatenated

schema/
└── retrieval-v1.schema.json

eval/
├── <domain>/queries.jsonl  # domain relevance judgments
└── cross-domain/queries.jsonl

tools/
├── build_dist.exs          # regenerates and validates dist/
└── score_retrieval.exs     # scores vendor-neutral result JSONL

Run elixir tools/build_dist.exs after editing the source ADRs or manifests. Requires Elixir 1.17 or later (uses Mix.install). The build parses and validates the complete corpus before replacing dist/, resolves ADR references across domains, validates generated retrieval rows, and emits deterministic artifacts. CI runs the tooling tests and blocks PRs when dist/ is out of sync with adrs/.

License

MIT.

About

Your coding agent knows Elixir syntax. These are the mechanism-level rules it gets wrong. 42 ADRs on OTP, conventions, Ecto, and the official anti-patterns, each with a Wrong example, a Correct example, and the BEAM reason behind the difference. Drops into Cursor and Claude Code.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages