elixir-boundaries

Audit context boundaries and coupling in portal/apps/emisar via mix xref + greps — find the web/MCP layer reaching past contexts, one context reaching into another's Query/Schema/Changeset, and depend

SKILL.md

GitHub

Context boundary audit

The architecture has one rule that everything else hangs off: the context module is the only public surface. This skill checks that the rule holds.

What's allowed to call what

Layer May call May NOT call
LiveView / controller / channel / MCP context public functions, CoreComponents, PubSub Repo, a schema's Query/Changeset/Schema directly
Context (lib/emisar/<ctx>.ex) its own schemas/Query/Changeset, Repo, other contexts' public API another context's Query/Changeset/Schema internals; web-layer modules
Query / Changeset / Schema (pure building blocks) Repo (Query/Changeset), each other

Checks

1. Web/MCP bypassing contexts (the most common drift):

cd portal
rg -n '\bRepo\.' apps/emisar_web/lib            # should be ~none — go through a context
rg -n '\b[A-Z]\w*\.(Query|Changeset)\.\w+' apps/emisar_web/lib   # web reaching into internals (ignore Plug.Conn.Query, Ecto.Changeset)

2. One context reaching into another's internals:

# in lib/emisar/<ctx>.ex, references to ANOTHER context's <Schema>.Query/Changeset
# (full or alias-relative names); keep the hits whose context is not the file's own
rg -n '\b[A-Z]\w*\.[A-Z]\w*\.(Query|Changeset)\b' apps/emisar/lib/emisar/*.ex

A context should call OtherContext.fetch_thing(id, subject), not OtherContext.Thing.Query.by_id/1.

3. Coupling + cycles (mix xref — confirm the flags with mix help xref):

cd portal
mix xref graph --format stats                       # fan-in / fan-out per module
mix xref graph --format cycles --label compile      # compile-time cycles (should be none)
mix xref callers Emisar.<Ctx>.<fun>                 # who depends on a function before you change it

Output

Report each violation as file:line → which boundary → the fix (almost always: "add a public function to context X and call that"). List the coupling hotspots (a context with high fan-out may be doing too much) and any cycle. Don't move modules around without running xref first — refactoring boundaries blind creates new violations. Fixes that add a context function: hand to /elixir-context-fn.