Claude Skill

phx-boundaries

Analyze Phoenix context boundaries and module coupling via mix xref.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download oliver-kriska-claude-elixir-phoenix-targets_amp_skills_phx-boundaries-9767a82.zip · 5 KB
Part of oliver-kriska/claude-elixir-phoenix — 93 skills

Install

skills CLI npx skills add https://github.com/oliver-kriska/claude-elixir-phoenix/tree/main/targets/amp/skills/phx-boundaries
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install oliver-kriska-claude-elixir-phoenix@llmmart
Git git clone https://github.com/oliver-kriska/claude-elixir-phoenix.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole oliver-kriska/claude-elixir-phoenix collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Phoenix Context Boundary Validation

Analyze module dependencies to ensure clean context separation and proper architectural boundaries.

Usage

phx-boundaries              # Check for violations
phx-boundaries --assess     # Score context health (0-100)
phx-boundaries --fix        # Suggest fixes for violations

--assess Mode: Context Health Score

Evaluate overall boundary health with a quantified score.

Metrics Calculated

Metric Healthy Range Red Flag Weight
Modules per context 3-15 >20 or <2 20%
Public API surface 5-30 funcs >40 funcs 15%
Fan-out (contexts called) 1-4 >6 20%
Fan-in (called by contexts) 1-6 >10 15%
Circular dependencies 0 >0 15%
Boundary violations 0 >0 15%

Commands for Assessment

Use Glob to count .ex files per context directory under lib/my_app/*/. Use Grep to count public function definitions per context file under lib/my_app/*.ex. Run mix xref graph --format stats for dependency analysis. Run mix xref graph --format cycles --label compile for compile-time circular dependencies.

Output Format

## Context Health Assessment

### Overall Score: 82/100 (Good)

| Context | Modules | API | Fan-Out | Fan-In | Score |
|---------|---------|-----|---------|--------|-------|
| Accounts | 5 | 12 | 2 | 4 | 95 |
| Orders | 18 | 45 | 8 | 3 | 62 |
| Shared | 2 | 8 | 0 | 12 | 78 |

### Issues Found

1. **Orders** - Too large (18 modules, 45 funcs)
   - Consider: Extract Fulfillment, Invoicing sub-contexts

2. **Orders** - High fan-out (8 contexts)
   - Consider: Review if all dependencies necessary

### Recommendations

- Split Orders into Orders + Fulfillment
- Review Accounts ← Billing dependency

Iron Laws - Never Violate These

  1. Controllers call only contexts - No direct Repo access from web layer
  2. Schemas are pure data - No side effects, no Repo calls in schema modules
  3. Contexts own their schemas - Don't import schemas from other contexts
  4. Explicit dependencies only - Cross-context calls must be intentional
  5. DO NOT refactor context boundaries without running mix xref first — Refactoring without dependency data creates new violations; always map the dependency graph before moving modules

Dependency Rules

Layer Can Call Cannot Call
Controllers Contexts, Plug, Conn Repo, Schemas directly
LiveViews Contexts, Components, PubSub Repo, Schemas directly
Contexts Own schemas, Repo, other contexts Web layer modules
Schemas Ecto types, validations Contexts, Repo

Analysis Commands

Check Compile Dependencies

Run mix xref graph --label compile-connected.

Find What Depends on a Context

Run mix xref graph --sink MyApp.Accounts --label compile.

Find What a Module Calls

Run mix xref callers MyApp.Accounts.get_user!/1.

Check for Circular Dependencies

Run mix xref graph --format cycles --label compile.

Red Flags to Detect

Issue Detection Command Fix
Repo in web layer grep -r "Repo\." lib/my_app_web/ Move to context
Schema with queries grep -r "import Ecto.Query" lib/my_app/**/schemas/ Move queries to context
Cross-context schema import grep -r "alias MyApp.Other.Schema" lib/my_app/ctx/ Call context API
Business logic in LiveView grep -r "Repo\.\|Ecto\.Multi" lib/my_app_web/live/ Extract to context

Boundary Verification Process

  1. Run mix xref graph --label compile-connected for overview
  2. Check for context cross-contamination
  3. Verify no direct Repo calls from web layer
  4. Ensure schemas have no side effects
  5. Validate explicit cross-context dependencies

Next Steps

Always end with actionable follow-up — findings without a plan get lost:

- `phx-plan` — Create a plan to fix violations (recommended for 3+ issues)
- `phx-quick` — Fix a single boundary violation directly
- `phx-review` — Review specific modules for deeper issues

References

For detailed patterns, see:

  • references/context-design.md - Context design principles
  • references/refactoring-boundaries.md - Fixing boundary violations
Files (claude-elixir-phoenix)
  • references
    • context-design.md 4.4 KB
      # Context Design Principles
      
      Guidelines for designing and maintaining Phoenix context boundaries.
      
      ## Context Responsibilities
      
      ### What Belongs in a Context
      
      - Business logic and domain rules
      - Data access (Repo calls)
      - Schema ownership
      - Changeset definitions
      - Transaction coordination
      - PubSub broadcasting
      
      ### What Does NOT Belong in a Context
      
      - HTTP concerns (conn, params parsing)
      - Presentation logic
      - View helpers
      - WebSocket handling
      - External API clients (use separate modules)
      
      ## Context API Design
      
      ### Public Functions
      
      ```elixir
      defmodule MyApp.Accounts do
        @moduledoc """
        The Accounts context handles user management and authentication.
        """
      
        # List operations
        def list_users(opts \\ [])
        def list_active_users
      
        # Get operations (return nil or raise)
        def get_user(id)
        def get_user!(id)
        def get_user_by_email(email)
      
        # Create operations
        def create_user(attrs)
        def register_user(attrs)
      
        # Update operations
        def update_user(user, attrs)
        def change_user_email(user, attrs)
      
        # Delete operations
        def delete_user(user)
      
        # Changeset functions (for forms)
        def change_user(user, attrs \\ %{})
      end
      ```
      
      ### Function Naming Conventions
      
      | Prefix | Meaning | Returns |
      |--------|---------|---------|
      | `list_` | Collection | `[%Schema{}]` |
      | `get_` | Single item, may not exist | `%Schema{} \| nil` |
      | `get_!` | Single item, must exist | `%Schema{}` or raises |
      | `create_` | New record | `{:ok, %Schema{}} \| {:error, changeset}` |
      | `update_` | Modify record | `{:ok, %Schema{}} \| {:error, changeset}` |
      | `delete_` | Remove record | `{:ok, %Schema{}} \| {:error, changeset}` |
      | `change_` | Return changeset | `%Changeset{}` |
      
      ## Cross-Context Communication
      
      ### Option 1: Direct Function Calls
      
      For simple, synchronous operations:
      
      ```elixir
      defmodule MyApp.Orders do
        alias MyApp.Accounts
      
        def create_order(user_id, attrs) do
          user = Accounts.get_user!(user_id)
          # ... create order
        end
      end
      ```
      
      ### Option 2: PubSub for Decoupling
      
      For events that trigger side effects:
      
      ```elixir
      # In Orders context
      defmodule MyApp.Orders do
        def complete_order(order) do
          with {:ok, order} <- update_order(order, %{status: :completed}) do
            Phoenix.PubSub.broadcast(MyApp.PubSub, "orders", {:order_completed, order})
            {:ok, order}
          end
        end
      end
      
      # In Notifications context (subscriber)
      defmodule MyApp.Notifications do
        def handle_info({:order_completed, order}, state) do
          send_order_confirmation(order)
          {:noreply, state}
        end
      end
      ```
      
      ### Option 3: Domain Events
      
      For complex workflows:
      
      ```elixir
      defmodule MyApp.Events do
        def dispatch(%{type: :order_completed} = event) do
          MyApp.Notifications.handle(event)
          MyApp.Analytics.handle(event)
          MyApp.Inventory.handle(event)
        end
      end
      ```
      
      ## Context Boundaries Checklist
      
      ### When Creating New Context
      
      - [ ] Single responsibility (one bounded context)
      - [ ] Clear API surface (public functions documented)
      - [ ] Owns its schemas (no shared schemas)
      - [ ] No web layer dependencies
      - [ ] Tests don't require web layer
      
      ### When Adding Cross-Context Dependency
      
      - [ ] Dependency is intentional (not accidental)
      - [ ] Using public API (not internal functions)
      - [ ] No circular dependencies created
      - [ ] Consider if PubSub is better fit
      
      ## Anti-Patterns to Avoid
      
      ### Bloated Contexts
      
      ```elixir
      # BAD: One context doing too much
      defmodule MyApp.Core do
        def create_user(attrs)
        def create_order(attrs)
        def create_product(attrs)
        def send_email(to, subject, body)
        def process_payment(amount)
      end
      
      # GOOD: Separate concerns
      defmodule MyApp.Accounts do ... end
      defmodule MyApp.Orders do ... end
      defmodule MyApp.Catalog do ... end
      defmodule MyApp.Mailer do ... end
      defmodule MyApp.Payments do ... end
      ```
      
      ### Leaky Abstractions
      
      ```elixir
      # BAD: Exposing internal query
      def get_active_users_query do
        from u in User, where: u.active == true
      end
      
      # GOOD: Return data, not queries
      def list_active_users do
        from(u in User, where: u.active == true)
        |> Repo.all()
      end
      ```
      
      ### Schema Sharing
      
      ```elixir
      # BAD: Sharing schemas between contexts
      defmodule MyApp.Orders do
        alias MyApp.Accounts.User  # Cross-context schema access
      
        def create_order(%User{} = user, attrs) do
          # Tight coupling
        end
      end
      
      # GOOD: Use IDs, call context API
      defmodule MyApp.Orders do
        alias MyApp.Accounts
      
        def create_order(user_id, attrs) do
          user = Accounts.get_user!(user_id)
          # Create order with user data
        end
      end
      ```
      
    • refactoring-boundaries.md 4.7 KB
      # Refactoring Boundary Violations
      
      Step-by-step guide to fixing common Phoenix context boundary issues.
      
      ## Fixing Direct Repo Access in Controllers
      
      ### Before (Violation)
      
      ```elixir
      defmodule MyAppWeb.UserController do
        alias MyApp.Repo
        alias MyApp.Accounts.User
      
        def show(conn, %{"id" => id}) do
          user = Repo.get!(User, id)  # Direct Repo access!
          render(conn, :show, user: user)
        end
      end
      ```
      
      ### After (Fixed)
      
      ```elixir
      # In context
      defmodule MyApp.Accounts do
        def get_user!(id), do: Repo.get!(User, id)
      end
      
      # In controller
      defmodule MyAppWeb.UserController do
        alias MyApp.Accounts
      
        def show(conn, %{"id" => id}) do
          user = Accounts.get_user!(id)
          render(conn, :show, user: user)
        end
      end
      ```
      
      ## Fixing Business Logic in LiveView
      
      ### Before (Violation)
      
      ```elixir
      defmodule MyAppWeb.OrderLive do
        def handle_event("complete", %{"id" => id}, socket) do
          order = Repo.get!(Order, id)
      
          # Business logic in LiveView!
          Ecto.Multi.new()
          |> Ecto.Multi.update(:order, Order.changeset(order, %{status: :completed}))
          |> Ecto.Multi.insert(:notification, Notification.changeset(%{...}))
          |> Repo.transaction()
      
          {:noreply, socket}
        end
      end
      ```
      
      ### After (Fixed)
      
      ```elixir
      # In context
      defmodule MyApp.Orders do
        def complete_order(order) do
          Ecto.Multi.new()
          |> Ecto.Multi.update(:order, Order.changeset(order, %{status: :completed}))
          |> Ecto.Multi.run(:notification, fn repo, _ ->
            MyApp.Notifications.create_order_notification(order)
          end)
          |> Repo.transaction()
        end
      end
      
      # In LiveView
      defmodule MyAppWeb.OrderLive do
        alias MyApp.Orders
      
        def handle_event("complete", %{"id" => id}, socket) do
          order = Orders.get_order!(id)
      
          case Orders.complete_order(order) do
            {:ok, _} -> {:noreply, put_flash(socket, :info, "Order completed")}
            {:error, _} -> {:noreply, put_flash(socket, :error, "Failed")}
          end
        end
      end
      ```
      
      ## Fixing Schema with Queries
      
      ### Before (Violation)
      
      ```elixir
      defmodule MyApp.Accounts.User do
        use Ecto.Schema
        import Ecto.Query  # Violation!
      
        schema "users" do
          field :email, :string
        end
      
        def active_query do
          from u in __MODULE__, where: u.active == true
        end
      end
      ```
      
      ### After (Fixed)
      
      ```elixir
      # Schema is pure
      defmodule MyApp.Accounts.User do
        use Ecto.Schema
      
        schema "users" do
          field :email, :string
          field :active, :boolean
        end
      
        def changeset(user, attrs) do
          user
          |> cast(attrs, [:email, :active])
          |> validate_required([:email])
        end
      end
      
      # Queries in context
      defmodule MyApp.Accounts do
        import Ecto.Query
      
        def list_active_users do
          from(u in User, where: u.active == true)
          |> Repo.all()
        end
      end
      ```
      
      ## Fixing Cross-Context Schema Access
      
      ### Before (Violation)
      
      ```elixir
      defmodule MyApp.Orders do
        alias MyApp.Accounts.User  # Tight coupling!
        alias MyApp.Orders.Order
      
        def create_order(%User{id: user_id, email: email}, attrs) do
          %Order{}
          |> Order.changeset(Map.put(attrs, :user_id, user_id))
          |> Repo.insert()
          |> tap(fn {:ok, _} -> send_confirmation(email) end)
        end
      end
      ```
      
      ### After (Fixed)
      
      ```elixir
      defmodule MyApp.Orders do
        alias MyApp.Accounts
        alias MyApp.Orders.Order
      
        def create_order(user_id, attrs) when is_integer(user_id) do
          # Fetch needed data through context API
          user = Accounts.get_user!(user_id)
      
          %Order{}
          |> Order.changeset(Map.put(attrs, :user_id, user_id))
          |> Repo.insert()
          |> tap(fn {:ok, _} -> send_confirmation(user.email) end)
        end
      end
      ```
      
      ## Migration Strategy
      
      ### Step 1: Identify Violations
      
      ```bash
      # Find Repo access in web layer
      grep -r "Repo\." lib/my_app_web/ --include="*.ex"
      
      # Find cross-context aliases
      grep -r "alias MyApp\.\w\+\.\w\+" lib/my_app/ --include="*.ex"
      
      # Find import Ecto.Query in schemas
      grep -r "import Ecto.Query" lib/my_app/**/schemas/ --include="*.ex"
      ```
      
      ### Step 2: Create Context Functions
      
      For each violation, create appropriate context function:
      
      | Violation | Solution |
      |-----------|----------|
      | `Repo.get(Schema, id)` | `Context.get_schema(id)` |
      | `Repo.all(Schema)` | `Context.list_schemas()` |
      | `Repo.insert(changeset)` | `Context.create_schema(attrs)` |
      | `Ecto.Multi` in controller | `Context.complex_operation(...)` |
      
      ### Step 3: Update Callers
      
      Replace direct calls with context API calls.
      
      ### Step 4: Verify with xref
      
      ```bash
      # After refactoring, verify no web -> Repo dependencies
      mix xref graph --source lib/my_app_web/ --sink MyApp.Repo
      
      # Should return empty or only through contexts
      ```
      
      ## Incremental Refactoring Tips
      
      1. **Don't refactor everything at once** - Fix one boundary at a time
      2. **Add tests first** - Ensure behavior is preserved
      3. **Use deprecation warnings** - Mark old functions as deprecated before removing
      4. **Keep commits atomic** - One boundary fix per commit
      
  • SKILL.md 4.4 KB
    ---
    name: phx-boundaries
    description: Analyze Phoenix context boundaries and module coupling via mix xref.
      Use when checking cross-context calls, validating dependencies, before splitting
      modules, or reviewing architecture.
    ---
    
    # Phoenix Context Boundary Validation
    
    Analyze module dependencies to ensure clean context separation and proper architectural boundaries.
    
    ## Usage
    
    ```
    phx-boundaries              # Check for violations
    phx-boundaries --assess     # Score context health (0-100)
    phx-boundaries --fix        # Suggest fixes for violations
    ```
    
    ## `--assess` Mode: Context Health Score
    
    Evaluate overall boundary health with a quantified score.
    
    ### Metrics Calculated
    
    | Metric | Healthy Range | Red Flag | Weight |
    |--------|---------------|----------|--------|
    | Modules per context | 3-15 | >20 or <2 | 20% |
    | Public API surface | 5-30 funcs | >40 funcs | 15% |
    | Fan-out (contexts called) | 1-4 | >6 | 20% |
    | Fan-in (called by contexts) | 1-6 | >10 | 15% |
    | Circular dependencies | 0 | >0 | 15% |
    | Boundary violations | 0 | >0 | 15% |
    
    ### Commands for Assessment
    
    Use Glob to count `.ex` files per context directory under `lib/my_app/*/`.
    Use Grep to count public function definitions per context file under `lib/my_app/*.ex`.
    Run `mix xref graph --format stats` for dependency analysis.
    Run `mix xref graph --format cycles --label compile` for compile-time circular dependencies.
    
    ### Output Format
    
    ```markdown
    ## Context Health Assessment
    
    ### Overall Score: 82/100 (Good)
    
    | Context | Modules | API | Fan-Out | Fan-In | Score |
    |---------|---------|-----|---------|--------|-------|
    | Accounts | 5 | 12 | 2 | 4 | 95 |
    | Orders | 18 | 45 | 8 | 3 | 62 |
    | Shared | 2 | 8 | 0 | 12 | 78 |
    
    ### Issues Found
    
    1. **Orders** - Too large (18 modules, 45 funcs)
       - Consider: Extract Fulfillment, Invoicing sub-contexts
    
    2. **Orders** - High fan-out (8 contexts)
       - Consider: Review if all dependencies necessary
    
    ### Recommendations
    
    - Split Orders into Orders + Fulfillment
    - Review Accounts ← Billing dependency
    ```
    
    ## Iron Laws - Never Violate These
    
    1. **Controllers call only contexts** - No direct Repo access from web layer
    2. **Schemas are pure data** - No side effects, no Repo calls in schema modules
    3. **Contexts own their schemas** - Don't import schemas from other contexts
    4. **Explicit dependencies only** - Cross-context calls must be intentional
    5. **DO NOT refactor context boundaries without running `mix xref` first** — Refactoring without dependency data creates new violations; always map the dependency graph before moving modules
    
    ## Dependency Rules
    
    | Layer | Can Call | Cannot Call |
    |-------|----------|-------------|
    | Controllers | Contexts, Plug, Conn | Repo, Schemas directly |
    | LiveViews | Contexts, Components, PubSub | Repo, Schemas directly |
    | Contexts | Own schemas, Repo, other contexts | Web layer modules |
    | Schemas | Ecto types, validations | Contexts, Repo |
    
    ## Analysis Commands
    
    ### Check Compile Dependencies
    
    Run `mix xref graph --label compile-connected`.
    
    ### Find What Depends on a Context
    
    Run `mix xref graph --sink MyApp.Accounts --label compile`.
    
    ### Find What a Module Calls
    
    Run `mix xref callers MyApp.Accounts.get_user!/1`.
    
    ### Check for Circular Dependencies
    
    Run `mix xref graph --format cycles --label compile`.
    
    ## Red Flags to Detect
    
    | Issue | Detection Command | Fix |
    |-------|------------------|-----|
    | Repo in web layer | `grep -r "Repo\." lib/my_app_web/` | Move to context |
    | Schema with queries | `grep -r "import Ecto.Query" lib/my_app/**/schemas/` | Move queries to context |
    | Cross-context schema import | `grep -r "alias MyApp.Other.Schema" lib/my_app/ctx/` | Call context API |
    | Business logic in LiveView | `grep -r "Repo\.\|Ecto\.Multi" lib/my_app_web/live/` | Extract to context |
    
    ## Boundary Verification Process
    
    1. Run `mix xref graph --label compile-connected` for overview
    2. Check for context cross-contamination
    3. Verify no direct Repo calls from web layer
    4. Ensure schemas have no side effects
    5. Validate explicit cross-context dependencies
    
    ## Next Steps
    
    Always end with actionable follow-up — findings without a plan
    get lost:
    
    ```
    - `phx-plan` — Create a plan to fix violations (recommended for 3+ issues)
    - `phx-quick` — Fix a single boundary violation directly
    - `phx-review` — Review specific modules for deeper issues
    ```
    
    ## References
    
    For detailed patterns, see:
    
    - `references/context-design.md` - Context design principles
    - `references/refactoring-boundaries.md` - Fixing boundary violations
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related