ワンクリックで
refactor
Review changed code for Elixir/Ash/Phoenix best practices, anti-patterns, and idiomatic style — then fix issues found
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Review changed code for Elixir/Ash/Phoenix best practices, anti-patterns, and idiomatic style — then fix issues found
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Create or manage Architecture Decision Records (ADRs) in docs/decisions/
Use this skill working with Ash Framework or any of its extensions. Always consult this when making any domain changes, features or fixes.
Record a development session summary in the project devlog
Run E2E browser tests via Tidewave browser_eval against the running Phoenix dev server
Inspect LiveView state (assigns, components) via Phoenix.LiveView.Debug in project_eval
Morning briefing — where we left off, what to work on today. Fast, compact report from canonical sources.
| name | refactor |
| description | Review changed code for Elixir/Ash/Phoenix best practices, anti-patterns, and idiomatic style — then fix issues found |
| argument-hint | [file-path or glob] — defaults to files changed in current git diff |
Review code against Elixir/Ash/Phoenix best practices, anti-patterns, and GsNet project conventions. Identify issues and apply fixes directly.
/refactor # Review files in current git diff
/refactor lib/gs_net/legal/authoring.ex # Review specific file
/refactor lib/gs_net/ai/*.ex # Review matching files
/refactor --dry-run # Report issues without fixing
/refactor --scope=style # Only check Elixir style (Section E)
/refactor --scope=ash # Only check Ash patterns (Section C)
/refactor --scope=phoenix # Only check Phoenix patterns (Section B)
/refactor --scope=all # Full checklist (default)
git diff --name-only HEAD (staged + unstaged).git diff --name-only HEAD~1 HEAD..ex and .exs files.--dry-run is specified, report findings without making edits.--scope=<section> is specified, only apply that checklist section.Read every target file. Also read:
Based on what the code touches:
ash-framework skill referencesphoenix-framework skill referencesEvaluate the code against all applicable sections. For each finding, classify severity:
| Severity | Meaning | Action |
|---|---|---|
| Fix | Bug, safety issue, or rule violation — must change | Auto-fix |
| Improve | Non-idiomatic or suboptimal — clear better alternative | Auto-fix |
| Consider | Trade-off worth noting but subjective | Report only |
For each Fix and Improve finding:
For Consider findings, report without editing.
After all edits, output the summary report in the format below.
String.to_atom/1 at runtime. Use compile-time maps, explicit atom literals, or String.to_existing_atom/1 with guaranteed pre-existing atoms.rescue _ -> or rescue _e -> that silently swallows errors. Rescue specific exceptions, or at minimum log the error.with, or early returns instead of throw/catch for normal control flow.String.to_atom on user input, DB values, or API responses.def handle_event(event, _, socket) ignoring params that should be validated.handle_call/handle_cast blocks the process mailbox. Offload to Task and send results back via message._.handle_event.socket = from block result, not assign inside.{:noreply, socket} vs redirect correctly.handle_params or handle_async for data loading.phx-debounce on text inputs — Prevents excessive server round-trips.style= attributes.scope: not scattered tenant:/authorize?: (CLAUDE.md rule).Ash.Query macros.index?: true (CLAUDE.md rule).run callbacks (CLAUDE.md rule).argument :tenant_id — Multitenancy handles it (CLAUDE.md rule).Ash.bulk_create/Ash.bulk_update for >100 records.case/pattern matching instead of if for changeset attribute/argument checks.input.tenant, NOT input.context[:tenant].set_attribute(:status, ...).Types.parse_bit/1, never == 1 (CLAUDE.md rule).MnetTime (CLAUDE.md rule).min-height: 0; overflow: hidden;.Map.get/2 not dot access for hot-reloadable state.Review all code for idiomatic Elixir. Apply Improve for clear anti-patterns, Consider for minor style preferences.
Use pattern matching and multi-clause functions instead of if/else, unless, cond, or || for value dispatch.
# ❌ if/else for value dispatch
def process(value) do
if is_nil(value), do: :default, else: transform(value)
end
# ✅ Multi-clause
def process(nil), do: :default
def process(value), do: transform(value)
# ❌ Conditional on a known set of values
def handle(type) do
if type == :admin, do: admin_path(), else: user_path()
end
# ✅ Multi-clause
def handle(:admin), do: admin_path()
def handle(_type), do: user_path()
with for multi-step happy pathsUse with when chaining operations that can fail, instead of nested case or sequential variable assignments that need error handling.
# ❌ Nested case
case fetch_user(id) do
{:ok, user} ->
case authorize(user) do
:ok -> do_work(user)
error -> error
end
error -> error
end
# ✅ with
with {:ok, user} <- fetch_user(id),
:ok <- authorize(user) do
do_work(user)
end
Do NOT use with for simple assignments that cannot fail — plain variables or a pipeline is clearer.
|| defaults → Keyword.get/3 or pattern matchingReplace opts[:key] || default with Keyword.get(opts, :key, default) for keyword lists. For maps, use Map.get/3 or pattern match in the function head.
# ❌ JavaScript-style defaults
tool_names = tool_opts[:tool_names] || true
extra = tool_opts[:extra] || []
# ✅ Keyword.get with defaults
tool_names = Keyword.get(tool_opts, :tool_names, :all)
extra = Keyword.get(tool_opts, :extra, [])
if in reduce/map callbacksWhen a function inside Enum.reduce, Enum.map, etc. branches on a value, extract to a named multi-clause helper.
# ❌ Inline conditional in reduce
Enum.reduce(items, acc, fn item, acc ->
if item.type == :special do
handle_special(item, acc)
else
acc
end
end)
# ✅ Multi-clause helper
Enum.reduce(items, acc, &accumulate/2)
defp accumulate(%{type: :special} = item, acc), do: handle_special(item, acc)
defp accumulate(_item, acc), do: acc
Use native data structure operations instead of list manipulations when working with sets or maps.
# ❌ List concat + dedup + filter for set logic
(list_a ++ MapSet.to_list(set_b))
|> Enum.uniq()
|> Enum.filter(&MapSet.member?(allowed, &1))
# ✅ Set operations
MapSet.new(list_a)
|> MapSet.union(set_b)
|> MapSet.intersection(allowed)
|> MapSet.to_list()
Map.merge/2 over chained Map.put/3When adding multiple keys to a map, use a single Map.merge/2 instead of chaining Map.put/3.
# ❌ Chained puts
map
|> Map.put(:key_a, val_a)
|> Map.put(:key_b, val_b)
|> Map.put(:key_c, val_c)
# ✅ Single merge
Map.merge(map, %{key_a: val_a, key_b: val_b, key_c: val_c})
ifUse when guards in function heads instead of if checks at the top of function bodies.
# ❌ Defensive if at top
def notify(pid, msg) do
if is_pid(pid), do: send(pid, msg), else: :ok
end
# ✅ Multi-clause with guard
def notify(pid, msg) when is_pid(pid), do: send(pid, msg)
def notify(_pid, _msg), do: :ok
Extract values from maps/keyword lists in function heads or with bindings, not via sequential opts[:key] in the function body.
# ❌ Sequential extraction
def handle_cast({:send_message, text, opts}, state) do
scope = opts[:scope]
page = opts[:page_context]
pdf = opts[:pdf_contents]
# ...
end
# ✅ Destructure required keys; access optional ones individually
def handle_cast({:send_message, text, opts}, state) do
with scope when not is_nil(scope) <- opts[:scope],
page_tools <- resolve_page_tools(opts[:page_context]) do
# scope is bound, optional keys accessed as needed
end
end
For keyword lists where you need many optional keys with defaults, Keyword.validate!/2 or Keyword.merge/2 with a defaults keyword list is acceptable.
Don't mix pipeline style and variable rebinding in the same transformation. Pick one.
# ❌ Mixed
result = fetch_data(id)
result = result |> transform()
result = Enum.map(result, &format/1)
# ✅ Single pipeline
result =
id
|> fetch_data()
|> transform()
|> Enum.map(&format/1)
Functions with more than 5 parameters should group related params into a map or struct. This is especially important for recursive functions that thread state.
# ❌ Too many positional args
defp loop(messages, model, llm_opts, registry, context, target_pid, iteration, tool_state)
# ✅ Group into a loop state struct/map
defp loop(messages, %{model: model, registry: registry} = loop_state, iteration)
Use then/1 instead of assigning to a variable just to pass into an anonymous function.
# ❌ Intermediate variable
result = compute_value(input)
callback.(result)
# ✅ Pipe with then
input
|> compute_value()
|> then(callback)
Enum.map + Enum.reject(nil) over Enum.reduce for filter-mapWhen building a list by conditionally including transformed items, prefer Enum.map + Enum.reject(&is_nil/1) or Enum.flat_map returning []/[item] over Enum.reduce with list accumulator reversal.
# ❌ Reduce with conditional append
Enum.reduce(items, [], fn item, acc ->
case transform(item) do
nil -> acc
val -> [val | acc]
end
end)
|> Enum.reverse()
# ✅ flat_map
Enum.flat_map(items, fn item ->
case transform(item) do
nil -> []
val -> [val]
end
end)
Prefer "Hello #{name}" over "Hello " <> name for readability, unless building IO lists.
Enum.count for emptiness checksUse Enum.empty?/1, match?([_ | _], list), or pattern match on [] — never Enum.count(x) == 0 or length(x) == 0 for large lists.
Check for issues the refactoring should address:
Important: Only flag contextual issues that are relevant to the target files. Don't audit unrelated code.
After applying fixes, output a summary report:
## Refactor Report
**Files reviewed**: [list]
**Checklist sections applied**: A, B, C, D, E, F
### Changes Applied
| # | File | Line | Severity | Rule | Description |
|---|------|------|----------|------|-------------|
| 1 | `path/to/file.ex` | 42 | Fix | A.2 | Bare rescue → rescue specific exception |
| 2 | `path/to/file.ex` | 87 | Improve | E.1 | if/else → multi-clause function |
### Consider (not auto-fixed)
- **`path/to/file.ex:120`** — [E.10] Function has 6 args, could group into struct. Left as-is because [reason].
### Summary
- **X fixes applied** across Y files
- **Z items noted** for consideration
--dry-runSame format but under "Proposed Changes" instead of "Changes Applied", and no edits are made.
mix test for affected test files.