targets/opencode/skills/ash-framework/SKILL.md
Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.
npx skillsauth add oliver-kriska/claude-elixir-phoenix ash-frameworkInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference for Ash Framework in Phoenix/LiveView projects. Ash complements Phoenix/Ecto — LiveView, security, and OTP Iron Laws still apply. Only data access patterns shift toward Ash actions and domain code interfaces.
Ash.create/Ash.read directly in LiveViews or Controllers; use domain code interfaces: MyApp.Accounts.register_user() not Ash.create(User, attrs)actor: or scope: to
for_read/for_create/for_action (prep), NOT to Ash.read!/Ash.create! (execution);
execution-level actor bypasses row-level policy evaluation. If project uses Ash.Scope,
pass scope: consistently instead of bare actor: — do not mix stylesmix ash.gen.resource or mix ash.gen.domain with --yes; check mix help ash.gen.<task> for optionsmix ash.codegen after modifying resources; this generates migrations from resource snapshots — never write AshPostgres migrations by handpriv/resource_snapshots/ is owned exclusively by mix ash.codegen; manual edits corrupt migration trackingRepo.* IN ASH PROJECTS — Repo.all/get/insert bypass Ash policies and notifications; use domain code interfaces. Any Repo call in an Ash project is an escape hatch and must be documented# Domain definition
defmodule MyApp.Accounts do
use Ash.Domain
resources do
resource MyApp.Accounts.User do
define :register_user, action: :create, args: [:email, :password]
define :get_user_by_email, action: :read, get_by: [:email]
end
end
end
# In LiveView/Controller — always via domain, never Ash.create directly
{:ok, user} = MyApp.Accounts.register_user(email, password, actor: nil)
user = MyApp.Accounts.get_user_by_email!(email, actor: current_user)
# CORRECT — actor at query prep, policies evaluated per-row
MyApp.Post
|> Ash.Query.for_read(:list_published, %{}, actor: current_user)
|> Ash.read!()
# CORRECT with Ash.Scope (carries actor + tenant + context; use if project adopts it)
MyApp.Post
|> Ash.Query.for_read(:list_published, %{}, scope: scope)
|> Ash.read!()
# WRONG — actor at execution bypasses row-level policy evaluation
MyApp.Post
|> Ash.Query.for_read(:list_published)
|> Ash.read!(actor: current_user)
Ash.Scope bundles actor + tenant + context into a single struct passed through actions.
Implement Ash.Scope.ToOpts on a project-defined scope struct:
defimpl Ash.Scope.ToOpts, for: MyApp.Scope do
def get_actor(%{current_user: u}), do: {:ok, u}
def get_tenant(%{current_tenant: t}), do: {:ok, t}
def get_context(%{locale: l}), do: {:ok, %{shared: %{locale: l}}}
def get_tracer(_), do: :error
def get_authorize?(_), do: :error
end
Detection: if the project has a Scope module implementing Ash.Scope.ToOpts, use
scope: everywhere instead of bare actor:. Do NOT mix the two styles in the same codebase.
See mix usage_rules.docs Ash.Scope for full protocol spec.
mix ash.gen.*)| File | Location | Behaviour |
| -------------- | --------------------------------- | ----------------------------- |
| Changes | lib/app/ctx/changes/name.ex | use Ash.Resource.Change |
| Policy Checks | lib/app/ctx/checks/name.ex | use Ash.Policy.Check |
| Custom Actions | lib/app/ctx/actions/name.ex | generic action logic |
| Custom Types | lib/app/ctx/types/name.ex | use Ash.Type |
| Validations | lib/app/ctx/validations/name.ex | use Ash.Resource.Validation |
mix ash.gen.resource MyApp.Accounts.User --yes
mix ash.gen.domain MyApp.Accounts --yes
mix ash.codegen # reads resource snapshots → generates migration
mix ash.migrate
Prefer the highest-fidelity source available:
Tidewave (exact version from mix.lock):
mcp__tidewave__get_docs(module: "Ash.Resource")
mcp__tidewave__get_docs(module: "AshPhoenix.Form")
usage_rules (project-synced to your installed ash_* dep versions):
mix usage_rules.search_docs "<topic>" -p ash -p ash_phoenix -p ash_postgres -p ash_authentication -p ash_oban
mix usage_rules.docs Ash.Resource
WebFetch hexdocs.pm (fallback when neither is available):
WebFetch(url: "https://hexdocs.pm/ash/Ash.Resource.html", prompt: "Extract module docs.")
If usage_rules is not configured, the SessionStart hook suggests how to install it.
tools
Compatibility alias for the Elixir/Phoenix plugin's LiveView assigns audit. Invoke explicitly with /lv:assigns.
development
Trace Elixir call trees from entry points via mix xref. Use when debugging data flow, planning signature changes, or understanding how a bug reaches code.
tools
Compatibility alias for the Elixir/Phoenix plugin's N+1 query checker. Invoke explicitly with /ecto:n1-check.
tools
Compatibility alias for the Elixir/Phoenix plugin's Ecto constraint debugger. Invoke explicitly with /ecto:constraint-debug.