# Elixir

> Elixir development: Ash Framework, Phoenix, Credo, LLM interaction safety. Use when working on Elixir/Ash/Phoenix projects.

- Skill: `snqb/elixir` (Agent Skill)
- Install (CLI): `npx skillmds@latest add snqb/elixir`
- Raw SKILL.md: https://api.skillmd.com/api/skills/snqb/elixir/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: snqb (https://skillmd.com/u/snqb)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/snqb/elixir

---


# 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:

1. **Disclose LLM origin**: "This code was generated by [Claude/GPT/etc]"
2. **Explain intent**: What problem you're solving, not just "fix this code"
3. **Show research**: Which docs/guides you consulted
4. **Provide context**: Versions, error messages, minimal reproduction

**Why**: Maintainers need to know if patterns come from hallucination vs misunderstanding. Saves everyone time.

**Channels**:
- Discord: https://discord.gg/ash-project
- Forum: https://elixirforum.com/c/ash-framework/
- GitHub: https://github.com/ash-project/ash/discussions

### 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
- GitHub: https://github.com/ash-project/ash_ai_dev_mcp
- `list_generators`: Discover available Ash generators
- Access to current Ash documentation context
- Framework-aware guidance

#### 2. Usage Rules (Maintainer-Vetted Patterns)

**What**: Maintainer-approved patterns aggregated from package docs
**Why**: Reduces hallucination by providing vetted examples as context

**Setup**:
```bash
# Install usage_rules package
mix deps.get

# Aggregate rules from all dependencies
mix usage_rules.sync .rules --all

# Or specify packages explicitly
mix usage_rules.sync .rules \
  ash ash_postgres ash_phoenix ash_graphql ash_json_api ash_ai

# Include .rules/ in your project for LLM context
```

**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/
- **Ash Core**: https://hexdocs.pm/ash/Ash.html
- **Ash Postgres**: https://hexdocs.pm/ash_postgres/
- **Ash Phoenix**: https://hexdocs.pm/ash_phoenix/
- **Guides**: https://hexdocs.pm/ash/readme.html

**Workflow**:
1. Search docs BEFORE generating code
2. Use `search_package_docs` (Tidewave) for version-specific APIs
3. Verify generated code against canonical examples
4. Test in running app with `project_eval` (Tidewave)

### Verification Checklist

Before trusting LLM-generated Ash code:

- [ ] Function/module names exist in current version
- [ ] Options match hexdocs signature
- [ ] DSL syntax matches 3.x patterns (if using 3.x)
- [ ] Code compiles without warnings
- [ ] Tested in running app (`project_eval` or manual)
- [ ] Matches patterns in `.rules/` files (if using usage_rules)
- [ ] Validates against Ash's built-in compilation checks

## Core Tools

### Credo - Static Analysis (5.1k⭐)
**Purpose:** Code quality, refactoring detection, complexity analysis, consistency enforcement

**Installation:**
```elixir
# mix.exs
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
```

**Usage:**
```bash
# Run full analysis
mix credo

# Run strict mode (warnings as errors)
mix credo --strict

# Focus on specific checks
mix credo --checks-without-tag formatting

# Explain a specific issue
mix credo explain path/to/file.ex:42

# CI integration (exit code on issues)
mix credo --strict --format=json
```

**Configuration (.credo.exs):**
```elixir
%{
  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
```bash
# Project management
mix new my_app              # New project
mix new my_app --sup        # With supervision tree

# Dependencies
mix deps.get                # Install dependencies
mix deps.update --all       # Update all deps
mix deps.clean --unused     # Remove unused deps

# Development
mix compile                 # Compile project
mix compile --warnings-as-errors  # Strict compilation
mix test                    # Run tests
mix test --trace            # Slow tests with full output
mix format                  # Auto-format code

# Release
mix release                 # Build production release
MIX_ENV=prod mix release    # Production 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
1. **Documentation First**: Use `search_package_docs` (Tidewave MCP) or Ash AI dev MCP to find relevant documentation
2. **Generator Foundation**: Use `list_generators` (Ash AI MCP) to discover available generators
3. **Generate**: Create code with generators (pass `--yes` for automation)
4. **Modify**: Adapt generated code as needed
5. **Interrogate**: Use `project_eval` (Tidewave MCP) to test code in running app
6. **Validate**: Compile and check logs/tests

### Setup Ash Project
```bash
# Combine rules from all dependencies
mix usage_rules.sync .rules --all

# Specific package rules
mix usage_rules.sync .rules \
  ash ash_postgres ash_phoenix ash_graphql ash_json_api ash_ai
```

### Common Ash Generators
```bash
# Generate a new resource
mix ash.gen.resource MyResource name:string age:integer

# Generate with domain
mix ash.gen.resource MyApp.Accounts.User name:string email:string

# Generate API
mix ash.gen.api MyApi

# Generate flow
mix ash.gen.flow MyFlow
```

### Ash Resource Definition
```elixir
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
```elixir
defmodule MyApp.Accounts do
  use Ash.Domain

  resources do
    resource MyApp.Accounts.User
    resource MyApp.Accounts.Profile
  end
end
```

### Ash Dependencies
```elixir
# 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
```elixir
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
```elixir
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
```bash
# Run all tests
mix test

# Run specific test file
mix test test/my_app/accounts_test.exs

# Run specific test line
mix test test/my_app/accounts_test.exs:42

# Run with coverage
mix test --cover

# Run with traces (slower but detailed)
mix test --trace
```

## Error Handling

### Ash Error Patterns
```elixir
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
```elixir
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
```elixir
# 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
```elixir
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:
```bash
# 1. Format code
mix format

# 2. Run tests
mix test

# 3. Check code quality
mix credo --strict

# 4. Compile with warnings as errors
mix compile --warnings-as-errors

# 5. Check coverage (optional)
mix test --cover
```

## Workflow Example

```bash
# Start new feature
git checkout -b feature/user-notifications

# Run Credo to see baseline
mix credo

# Write tests first
# test/my_app/notifications_test.exs

# Run tests (they should fail)
mix test

# Implement feature
# lib/my_app/notifications.ex

# Run tests again (should pass)
mix test

# Check code quality
mix credo --strict

# Format code
mix format

# Commit
git add .
git commit -m "Add user notifications feature"
```

Remember: **Verify LLM outputs, use generators first, think "Ash way", leverage MCP tools.**

