ワンクリックで
review-plan
Review an implementation plan against Elixir/Phoenix/Ash best practices, anti-patterns, and GsNet project rules
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Review an implementation plan against Elixir/Phoenix/Ash best practices, anti-patterns, and GsNet project rules
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 | review-plan |
| description | Review an implementation plan against Elixir/Phoenix/Ash best practices, anti-patterns, and GsNet project rules |
| argument-hint | [plan-file-path] — defaults to most recent .claude/plans/*.md |
Review an implementation plan for correctness, best practices, anti-patterns, and alignment with GsNet project conventions.
/review-plan # Review most recent plan
/review-plan .claude/plans/my-plan.md # Review specific plan
/review-plan docs/plans/active/foo-plan.md # Review from docs/plans
*.md in .claude/plans/ by modification time.docs/plans/active/ as fallback.Read the plan file. Extract:
Read every file the plan proposes to change. Also read:
menu_builder.ex, also read route_catalog.ex and the sidebar component that consumes it).Do NOT read the entire codebase — limit to files within 1 hop of the change set.
Load relevant skill references based on what the plan touches:
ash-framework skillphoenix-framework skillEvaluate each plan item against the checklist below. For each item, assign a verdict:
| Verdict | Meaning |
|---|---|
| Good | Correct, idiomatic, no issues |
| Replace | Wrong approach — provide specific alternative |
| Reconsider | Not wrong but has trade-offs worth discussing |
| Missing | Plan should address this but doesn't |
Use the exact output format described 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.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.test/gs_net/, policy tests in test/policy_tests/.set_attribute(:status, ...).Review all code snippets in the plan for idiomatic Elixir. Flag non-idiomatic patterns as Replace when a clearly better alternative exists, Reconsider when it's a minor style preference.
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()
# ❌ Enum.reduce to build a MapSet
Enum.reduce(names, existing_set, &MapSet.put(&2, &1))
# ✅ MapSet.union
MapSet.union(existing_set, MapSet.new(names))
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)
Check for issues the plan should address but doesn't mention:
Important: Only flag contextual issues that are relevant to the plan's changes. Don't audit the entire file for unrelated issues.
## Plan Review: [plan title from first heading]
**Plan file**: `path/to/plan.md`
**Files in scope**: [list of files read]
**Checklist sections applied**: A, B, C, D, E, F (list which were relevant)
### Verdicts
| # | Plan Item | Verdict | Issue |
|---|-----------|---------|-------|
| 1 | [concise description] | Good | — |
| 2 | [concise description] | Replace | [1-line reason] → see detail below |
| 3 | [concise description] | Reconsider | [1-line trade-off] |
| 4 | — | Missing | [thing plan should address] |
For each non-Good verdict, provide a detail section:
### #2: [Plan Item] → Replace
**Checklist**: A.1 (Atom safety)
**Problem**: [What's wrong and why, referencing the specific anti-pattern]
**Suggested fix**:
```elixir
# concrete code showing the better approach
Impact: [What breaks or degrades if this isn't changed]
### Contextual Findings
```markdown
### Contextual Findings
Issues in changed files that interact with the plan's changes:
- **[file:line]** — [issue description]. [Checklist ref if applicable].
### Summary
- **X/Y items Good** — ready to implement as-is
- **Z items need changes** — [1-line summary of most important change]
- **Overall**: [Go / Go with changes / Needs rework]