Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/oliver-kriska-ash-framework/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 63% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 5% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 28% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 42% | 0% |
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: tofor_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 styles
mix 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 documentedelixir# 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)
elixir# 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:
elixirdefimpl 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 |
bashmix 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:
mix.lock): mcp__tidewave__get_docs(module: "Ash.Resource") mcp__tidewave__get_docs(module: "AshPhoenix.Form")
bash 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(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.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-22 | pass→pass | 12,860 | 10,072 | -22% | 1 | 1 | 0% | 2,643 | 3,455 | +31% | 0 | 0 | — |
case-01 | fail→pass | 12,534 | 7,039 | -44% | 1 | 1 | 0% | 2,366 | 2,901 | +23% | 0 | 0 | — |
case-02 | fail→pass | 9,306 | 6,394 | -31% | 1 | 1 | 0% | 1,599 | 2,602 | +63% | 0 | 0 | — |
case-03 | pass→pass | 15,214 | 11,175 | -27% | 1 | 1 | 0% | 2,374 | 3,301 | +39% | 0 | 0 | — |
case-04 | pass→pass | 7,686 | 5,659 | -26% | 1 | 1 | 0% | 1,268 | 2,435 | +92% | 0 | 0 | — |
case-05 | pass→pass | 11,876 | 5,443 | -54% | 1 | 1 | 0% | 2,128 | 2,381 | +12% | 0 | 0 | — |
case-06 | pass→pass | 11,357 | 4,495 | -60% | 1 | 1 | 0% | 1,835 | 2,164 | +18% | 0 | 0 | — |
case-07 | pass→pass | 15,307 | 6,600 | -57% | 1 | 1 | 0% | 2,371 | 2,469 | +4% | 0 | 0 | — |
case-08 | pass→pass | 9,038 | 3,860 | -57% | 1 | 1 | 0% | 1,682 | 2,013 | +20% | 0 | 0 | — |
case-09 | pass→pass | 12,310 | 2,842 | -77% | 1 | 1 | 0% | 2,174 | 1,824 | -16% | 0 | 0 | — |
case-10 | pass→pass | 16,402 | 9,561 | -42% | 1 | 1 | 0% | 2,901 | 3,188 | +10% | 0 | 0 | — |
case-11 | fail→pass | 10,287 | 3,921 | -62% | 1 | 1 | 0% | 1,958 | 2,064 | +5% | 0 | 0 | — |
case-12 | pass→pass | 12,406 | 3,780 | -70% | 1 | 1 | 0% | 2,221 | 2,040 | -8% | 0 | 0 | — |
case-13 | pass→pass | 10,550 | 2,448 | -77% | 1 | 1 | 0% | 1,863 | 1,757 | -6% | 0 | 0 | — |
case-14 | fail→pass | 8,845 | 3,005 | -66% | 1 | 1 | 0% | 1,519 | 1,943 | +28% | 0 | 0 | — |
case-15 | pass→pass | 4,465 | 2,285 | -49% | 1 | 1 | 0% | 636 | 1,733 | +172% | 0 | 0 | — |
case-16 | fail→pass | 11,340 | 7,743 | -32% | 1 | 1 | 0% | 2,096 | 2,984 | +42% | 0 | 0 | — |
case-17 | pass→pass | 6,409 | 2,705 | -58% | 1 | 1 | 0% | 1,097 | 1,870 | +70% | 0 | 0 | — |
case-18 | fail→pass | 12,283 | 4,785 | -61% | 1 | 1 | 0% | 1,972 | 2,173 | +10% | 0 | 0 | — |
case-19 | fail→pass | 10,042 | 5,969 | -41% | 1 | 1 | 0% | 1,821 | 2,503 | +37% | 0 | 0 | — |
case-20 | pass→pass | 9,721 | 7,596 | -22% | 1 | 1 | 0% | 1,809 | 2,795 | +55% | 0 | 0 | — |
case-21 | pass→pass | 10,174 | 6,300 | -38% | 1 | 1 | 0% | 1,839 | 2,538 | +38% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.