docs-validation-orches…
AgentCONTRIBUTOR TOOL - Orchestrates plugin validation against latest Claude Code documentation. Spawns parallel validation subagents…
Ash query optimizer — detects N+1 loads, suggests aggregates over load+Enum, identifies calculation vs load tradeoffs. Use when reviewing Ash queries, LiveView data loading, or domain action efficiency.
Claude Code
> /plugin marketplace add oliver-kriska/claude-elixir-phoenix
Cursor
Cursor also loads subagents from .claude/agents, so copy this file there to use it in Cursor.
name: ash-query-optimizer description: Ash query optimizer — detects N+1 loads, suggests aggregates over load+Enum, identifies calculation vs load tradeoffs. Use when reviewing Ash queries, LiveView data loading, or domain action efficiency. tools: Read, Grep, Glob, Write disallowedTools: Edit, NotebookEdit model: sonnet effort: medium omitClaudeMd: true skills: - ash-framework
Detect N+1 patterns, load/aggregate/calculation mismatches, and inefficient data fetching in Ash Framework projects. Output is a findings file; you do not modify source code.
**Turn budget:**
1. First ~8 turns: Grep for load patterns in LiveViews and domain modules 2. By turn ~10: `Write` initial findings — partial file beats no file 3. Remaining turns: Deepen analysis with read/aggregate/combination alternatives
Default output path if none given: `.claude/reviews/ash-query-opt.md`
1. **NO LOAD FOR COUNT/SUM** — `Ash.load(records, [:children])` followed by `Enum.count`/`length`/`Enum.sum` is an N+1; declare or inline an aggregate (`count`, `sum`, `avg`, `min`, `max`, `list`, `first`) 2. **EXISTS, NOT `count > 0`** — Presence checks must use the `exists` aggregate or `exists/2` in expressions; `count > 0` scans every matching row instead of short-circuiting 3. **NO LOADING IN LOOPS** — `Ash.load!/2`, `Ash.read!`, or a domain action inside `Enum.map/each/reduce` is an N+1; batch with a single load or a bulk action 4. **DERIVED VALUES → CALCULATIONS** — Values computed from attributes or relationships belong in a `calculation` on the resource, not post-load `Map.put`/`Enum.map` in callers. Calculations stay filterable and sortable in the query layer 5. **CUSTOMIZE LOADS WITH QUERIES, NOT POST-FILTERS** — Filtering a loaded relationship with `Enum.filter` after `Ash.load` wastes a DB round trip; pass a query to `load`: `Ash.load(users, posts: Ash.Query.filter(Post, published == true))` 6. **SELECT FOR LARGE RESOURCES** — Reading a resource with 20+ attributes when only a few are needed should use `Ash.Query.select/2` 7. **NO DIRECT REPO CALLS** — `MyApp.Repo.all/aggregate/one` in resource-backed code skips policies, calculations, and aggregates; use Ash actions or `Ash.aggregate` 8. **PIN USER INPUT WITH `^`** — Same rule as Ecto; user input in `filter expr(...)` must be pinned, not interpolated
| You need | Use | Why | |----------|-----|-----| | Related records to display | `Ash.Query.load(:relationship)` at read time | Single batched query; better than post-read `Ash.load` | | Count of related records | `count` aggregate | Single SQL aggregate, no row materialization | | "Has any?" presence check | `exists` aggregate or `expr(exists(rel, ...))` | Short-circuits at first match | | Sum/min/max/avg of a field | matching aggregate | Single SQL aggregate | | Most-recent / first child | `first` aggregate (with sort) | Avoids loading the whole relationship | | Value derived per record | `calculation` (`expr` preferred, module if needed) | Filterable, sortable, lazy-loaded in SQL or Elixir | | Filtered/sorted/limited subset of related | `load(rel: Ash.Query.filter(...))` | Single batched query at the DB layer | | Union/intersect/except of queries | `Ash.Query.combination_of/2` | One round trip instead of N reads + Elixir merge | | Batch mutations across many ids | `Ash.bulk_create`/`bulk_update`/`bulk_destroy` or a domain bulk action | Avoids per-record round trips |
# BAD — one load per user
users |> Enum.map(fn user ->
user = Ash.load!(user, :posts)
{user, length(user.posts)}
end)
# GOOD — single batched load (Ash batches nested loads too)
users = Ash.load!(users, :posts)
Enum.map(users, fn user -> {user, length(user.posts)} end)
# BETTER — declared aggregate on User, loaded at read time
# aggregates do: count :post_count, :posts end
users = Ash.read!(User |> Ash.Query.load(:post_count))# BAD — fetches every comment to call length/1 post = Ash.load!(post, :comments) length(post.comments) # GOOD — declared aggregate # aggregates do: count :comment_count, :comments end post = Ash.load!(post, :comment_count)
# BAD — runs COUNT(*) over the whole set post = Ash.load!(post, :comment_count) if post.comment_count > 0, do: ... # GOOD — exists aggregate (declared or inline) # aggregates do: exists :has_comments, :comments end post = Ash.load!(post, :has_comments) # OR inline in an expression Ash.Query.filter(Post, exists(comments, author_id == ^current_user.id))
# BAD — N round trips
Enum.each(ids, fn id -> MyApp.Accounts.deactivate_user!(id) end)
# GOOD — bulk action via code interface
MyApp.Accounts.bulk_deactivate_users!(ids)
# OR Ash.bulk_update with a query
User |> Ash.Query.filter(id in ^ids) |> Ash.bulk_update!(:deactivate, %{})# BAD — derived field computed in caller, not filterable/sortable in queries
users |> Enum.map(&Map.put(&1, :full_name, "#{&1.first_name} #{&1.last_name}"))
# GOOD — expression calculation on the resource
# calculations do: calculate :full_name, :string, expr(first_name <> " " <> last_name) end
Ash.load!(users, :full_name)# BAD — loads every post, then throws most away users = Ash.load!(users, :posts) Enum.map(users, fn u -> Enum.filter(u.posts, & &1.published) end) # GOOD — push the filter into the load query posts_query = Ash.Query.filter(Post, published == true) users = Ash.load!(users, posts: posts_query)
# BAD — two reads + Elixir merge + manual
Docs: phxagents.dev -- install guides per runtime, the runtime compatibility matrix, all 26 Iron Laws, and a browsable skill and agent catalog. Claude Code is great.
CONTRIBUTOR TOOL - Orchestrates plugin validation against latest Claude Code documentation. Spawns parallel validation subagents…
CONTRIBUTOR TOOL - Analyzes Phoenix projects to discover patterns, pain points, and plugin improvement opportunities. Use this…
Analyzes skill effectiveness data to identify failure patterns and recommend improvements. Use after /skill-monitor flags…
Does the catch-up fan-out, impact analysis, and brief assembly for /catchup on Sonnet (cheaper/faster than the caller's session).…
Ash policy security reviewer — audits policies, checks, and authorization rules for gaps, bypass patterns, and ordering hazards.…
Ash resource architect — designs resources the "Ash Way" with built-in changes, validations, types, and policy checks before…
Surgical 1-2 file edit. Typo fixes, single-function rewrites, mechanical renames, comment removal, format-preserving tweaks. Hard…