| name | elixir |
| description | Elixir development: Ash Framework, Phoenix, Credo, LLM interaction safety. Use when working on Elixir/Ash/Phoenix projects. |
Elixir Development Specialist
Specialist in Elixir development with focus on code quality, static analysis, and modern framework patterns (Ash, Phoenix).
⚠️ Working with LLMs & Ash
Official Guidance: https://hexdocs.pm/ash/working-with-llms.html
Critical: LLMs Hallucinate Ash APIs
Reality Check: LLMs frequently hallucinate Ash APIs, function signatures, and patterns despite training data. Ash evolves rapidly (3.x is very different from 2.x), and LLMs often mix outdated patterns with current APIs.
Common Hallucinations:
- Non-existent action options (e.g.,
:allow_nil? instead of :allow_nil?)
- Invalid relationship configurations
- Deprecated DSL syntax (pre-3.0 patterns)
- Wrong module names (e.g.,
Ash.Query.get vs Ash.get)
- Incorrect attribute types or constraints
Your Responsibility: Always verify against official docs before using generated code. When it compiles but fails at runtime, suspect hallucination first.
Support Protocol
When seeking help in Ash community (Discord/GitHub/Forum), you MUST:
- Disclose LLM origin: "This code was generated by [Claude/GPT/etc]"
- Explain intent: What problem you're solving, not just "fix this code"
- Show research: Which docs/guides you consulted
- Provide context: Versions, error messages, minimal reproduction
Why: Maintainers need to know if patterns come from hallucination vs misunderstanding. Saves everyone time.
Channels:
Tools for Accuracy
1. MCP Tools (Highest Accuracy)
Tidewave MCP - Interrogate running Phoenix apps
- GitHub: https://github.com/scharris/tidewave-mcp
project_eval: Execute Elixir code in live app context
search_package_docs: Search Hexdocs for current package versions
- Requirement: Phoenix app must be running (
mix phx.server)
- Never: Attempt to start/stop app via MCP (breaks connection)
Ash AI dev MCP - Ash-specific development server
2. Usage Rules (Maintainer-Vetted Patterns)
What: Maintainer-approved patterns aggregated from package docs
Why: Reduces hallucination by providing vetted examples as context
Setup:
mix deps.get
mix usage_rules.sync .rules --all
mix usage_rules.sync .rules \
ash ash_postgres ash_phoenix ash_graphql ash_json_api ash_ai
Result: .rules/ directory contains markdown files with canonical patterns for each package.
Provide to LLM: When working on Ash code, include relevant .rules/*.md files in context.
3. Documentation-First Development
Official Hexdocs: https://hexdocs.pm/ash/
Workflow:
- Search docs BEFORE generating code
- Use
search_package_docs (Tidewave) for version-specific APIs
- Verify generated code against canonical examples
- Test in running app with
project_eval (Tidewave)
Verification Checklist
Before trusting LLM-generated Ash code:
Core Tools
Credo - Static Analysis (5.1k⭐)
Purpose: Code quality, refactoring detection, complexity analysis, consistency enforcement
Installation:
# mix.exs
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
Usage:
mix credo
mix credo --strict
mix credo --checks-without-tag formatting
mix credo explain path/to/file.ex:42
mix credo --strict --format=json
Configuration (.credo.exs):
%{
configs: [
%{
name: "default",
files: %{
included: ["lib/", "src/", "test/", "web/", "apps/"],
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
},
checks: [
# Enable/disable specific checks
{Credo.Check.Design.AliasUsage, priority: :low, if_nested_deeper_than: 2},
{Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 12},
{Credo.Check.Refactor.FunctionArity, max_arity: 5},
# Disable checks you don't need
{Credo.Check.Readability.ModuleDoc, false}
]
}
]
}
Key Checks:
- Refactoring: Cyclomatic complexity, function arity, nesting depth
- Consistency: Module names, variable naming, alias usage
- Readability: Line length, function names, specs
- Design: Alias usage, tag usage
- Warnings: Unused imports, operation with constant result
Editor Integration:
- VSCode: ElixirLS extension (auto-runs Credo)
- Neovim: coc-elixir, elixir-ls
- IntelliJ: Elixir plugin with Credo support
Mix - Build Tool
mix new my_app
mix new my_app --sup
mix deps.get
mix deps.update --all
mix deps.clean --unused
mix compile
mix compile --warnings-as-errors
mix test
mix test --trace
mix format
mix release
MIX_ENV=prod mix release
Ash Framework Specialization
Ash-First Development Principles
- Always use Ash concepts, almost never Ecto concepts directly
- Think hard about the "Ash way" to do things
- Follow Ash's resource-based architecture patterns
- Use Ash's domain-specific language and conventions
- Leverage Ash's built-in query and change management systems
Generator-First Workflow
- Documentation First: Use
search_package_docs (Tidewave MCP) or Ash AI dev MCP to find relevant documentation
- Generator Foundation: Use
list_generators (Ash AI MCP) to discover available generators
- Generate: Create code with generators (pass
--yes for automation)
- Modify: Adapt generated code as needed
- Interrogate: Use
project_eval (Tidewave MCP) to test code in running app
- Validate: Compile and check logs/tests
Setup Ash Project
mix usage_rules.sync .rules --all
mix usage_rules.sync .rules \
ash ash_postgres ash_phoenix ash_graphql ash_json_api ash_ai
Common Ash Generators
mix ash.gen.resource MyResource name:string age:integer
mix ash.gen.resource MyApp.Accounts.User name:string email:string
mix ash.gen.api MyApi
mix ash.gen.flow MyFlow
Ash Resource Definition
defmodule MyApp.Accounts.User do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshJsonApiResource, AshGraphqlResource]
attributes do
uuid_primary_key :id
attribute :name, :string, allow_nil?: false
attribute :email, :string, allow_nil?: false
attribute :age, :integer
timestamps()
end
relationships do
belongs_to :profile, MyApp.Accounts.Profile
has_many :posts, MyApp.Blog.Post
end
actions do
defaults [:read, :create, :update, :destroy]
read :by_name do
argument :name, :string
filter expr(name == ^arg(:name))
end
end
code_interface do
define_for MyApp.Accounts
define :create_user, args: [:name, :email]
define :get_user_by_name, args: [:name], action: :by_name
end
end
Ash Domain Context
defmodule MyApp.Accounts do
use Ash.Domain
resources do
resource MyApp.Accounts.User
resource MyApp.Accounts.Profile
end
end
Ash Dependencies
# mix.exs
defp deps do
[
{:ash, "~> 3.0"},
{:ash_postgres, "~> 2.0"},
{:ash_phoenix, "~> 2.0"},
{:ash_json_api, "~> 1.0"},
{:ash_graphql, "~> 1.0"},
{:phoenix, "~> 1.7"},
{:ecto_sql, "~> 3.10"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
]
end
Phoenix Integration
LiveView Patterns
defmodule MyAppWeb.UserLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
{:ok, assign(socket, :users, MyApp.Accounts.read_user!())}
end
def handle_event("create", %{"user" => params}, socket) do
case MyApp.Accounts.create_user(params) do
{:ok, user} ->
{:noreply, assign(socket, users: [user | socket.assigns.users])}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to create user")}
end
end
end
Contexts vs Ash Resources
- Prefer Ash resources over Phoenix contexts
- Use Ash code interfaces for clean public APIs
- Leverage Ash policies for authorization
- Use Ash changesets for data validation
Testing Patterns
ExUnit Tests
defmodule MyApp.Accounts.UserTest do
use ExUnit.Case
use MyApp.DataCase
test "creates user with valid attributes" do
attrs = %{name: "John", email: "john@example.com"}
assert {:ok, user} = MyApp.Accounts.create_user(attrs)
assert user.name == "John"
assert user.email == "john@example.com"
end
test "fails with invalid email" do
attrs = %{name: "John", email: "invalid"}
assert {:error, error} = MyApp.Accounts.create_user(attrs)
assert error.errors[:email]
end
end
Test Commands
mix test
mix test test/my_app/accounts_test.exs
mix test test/my_app/accounts_test.exs:42
mix test --cover
mix test --trace
Error Handling
Ash Error Patterns
case MyApp.Accounts.create_user(params) do
{:ok, user} ->
# Success path
{:error, %Ash.Error.Changes.Invalid{} = error} ->
# Handle validation errors
{:error, error} ->
# Handle other errors
end
With Pattern
with {:ok, user} <- MyApp.Accounts.create_user(params),
{:ok, profile} <- MyApp.Accounts.create_profile(user, profile_params) do
{:ok, {user, profile}}
else
{:error, error} -> {:error, error}
end
Configuration Files
Application Config
# config/dev.exs
config :my_app, MyApp.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "my_app_dev",
stacktrace: true,
show_sensitive_data_on_connection_error: true
# Runtime configuration
config :my_app, :ash_apis, [MyApp.Api]
# Logger configuration
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
Migration Patterns
Ecto Migrations
defmodule MyApp.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users, primary_key: false) do
add :id, :binary_id, primary_key: true
add :name, :string, null: false
add :email, :string, null: false
add :age, :integer
timestamps()
end
create unique_index(:users, [:email])
end
end
Best Practices
DO ✅
- Run
mix credo regularly (integrate into CI)
- Use generators as starting points (Ash)
- Follow domain modeling patterns (Ash)
- Use pattern matching for control flow
- Leverage pipe operator
|> for data transformations
- Write tests first (TDD)
- Keep functions small (Credo will flag complexity)
- Use
with for multiple operations
- Format code with
mix format
- Add typespecs for public functions
DON'T ❌
- Ignore Credo warnings (they're educational)
- Use raw Ecto queries when using Ash
- Start/stop Phoenix applications via Tidewave (requires active connection)
- Trust LLM-generated Ash code without verification
- Write boilerplate from scratch (use generators)
- Skip tests
- Nest functions too deeply (Credo max: 3-4 levels)
- Have functions with >5 parameters (use maps/structs)
- Leave unused imports/aliases
- Skip documentation for public APIs
Quality Checklist
Before committing:
mix format
mix test
mix credo --strict
mix compile --warnings-as-errors
mix test --cover
Workflow Example
git checkout -b feature/user-notifications
mix credo
mix test
mix test
mix credo --strict
mix format
git add .
git commit -m "Add user notifications feature"
Remember: Verify LLM outputs, use generators first, think "Ash way", leverage MCP tools.