Preconditions
- Ordering law: Read
skills/organize-file/SKILL.mdbefore moving code. Placement always defers to it (LiveView lifecycle,describe/testclustering, do not reorder clauses when that would change pattern match order). - Single path: If the user gives one file path, only that file is in scope; merge-base comparison still applies.
Orchestrator (parent agent)
Role: Triage, git inspection, and launching workers only. Never edit *.ex / *.exs or apply reorders in the orchestrator turn.
- Every path that needs organize work—including exactly one—goes to a subagent (
TaskwithgeneralPurposeor equivalent). No “small enough to inline” exception. - Zero paths after triage: spawn no workers; do not edit sources.
- Cannot spawn a worker: stop with one line stating the blocker only (not
complete). Do not edit files yourself. Taskspawns: Never passmodel; workers inherit the orchestrator’s model.
Sequence
- Set merge base
BASE_BRANCH=main
MB="$(git merge-base HEAD "$BASE_BRANCH")"
If main is missing locally, use origin/main or the repo default. Use $BASE_BRANCH / $MB only in git commands and worker prompts—not in user-facing output.
-
Collect paths (committed ∪ working tree)
git diff --name-status "$MB"...HEAD git status --shortUnion both outputs. User gave an explicit path → only that path (still compute
MB). -
Filter to in-scope Elixir:
*.ex,*.exsunderlib/,test/,apps/*/lib,apps/*/test(adjust for monorepo). Drop non-sources (priv/,assets/, lockfiles) unless the user’s path overrides. -
Triage each in-scope path with
git diff "$MB" -- <path>(-U0optional).- Dispatch when the diff touches a
def/defp/defmacroclause head or atest/describeline—including parameter renames on an unchanged name (+/-on the same head line). - Skip when edits are clearly outside heads/titles (body only, typespec-only, comments-only, non-definition data). The worker’s candidate rules are final if ambiguous.
- Do not triage via name-only inventory (set subtract of
defnames)—that misses param-only head changes.
No paths survive → skip step 5.
- Dispatch when the diff touches a
-
Execute (subagents only) — one worker per remaining path; parallel when multiple (disjoint files). Wait for all workers before step 6. Orchestrator does not open files to reorder. A worker may finish with
actions: nonewhen triage was a false positive or there are no real candidates.Per-worker prompt:
MB,BASE_BRANCH, singlepath, absolute paths to this skill andorganize-file/SKILL.md(required when skills live outside the repo), repo root if useful. Require the worker completion shape (below). Prefergit grep/grepover assumingrgon PATH. -
Reply
- Finished (all workers succeeded, or none needed): exactly
complete—no prose, markdown, table, recap, or code fence. - Worker failed: one plain line for that failure (not
complete). - Could not finish (e.g. cannot spawn): one plain line for the blocker (not
complete).
- Finished (all workers succeeded, or none needed): exactly
Placement (workers — read before moving)
git diffshows what changed, not where definitions belong. Never place candidates together because the PR added them adjacently, they sit in one patch hunk, or they call each other.- Global slots: Enumerate every
def/defp/defmacro(and everydescribe/testin tests) in organize-file order for the whole module. Each candidate goes in the slot that full list dictates—as if organizing the entire file from scratch. Neighbors are often non-candidates far from the diff hunk. - Plain modules —
defp: Merge alldefpnames (candidates + non-candidates), sort lexicographically, file order must match that list. Ifinterpolate_variablessorts betweencheck_campaign_limitandlock_account_and_check_campaign_limit, it must sit between them in source—never stack branchdefps and skip intervening names. Call graph does not override sort order.
Candidates
Elixir: def, defp, or defmacro is a candidate vs merge-base when any of: function name differs; arity differs; clause heads differ (parameter/binding names or other head text)—treat all clauses of the same name+arity as one movable unit. Name+arity removed and replaced → rename = remove + add; place the new name only.
Tests: test "…" or describe "…" whose string literal is new or meaningfully renamed (use diff judgment for tiny edits).
Not a candidate: same name, arity, and clause heads as merge-base with only body, typespec, attribute, whitespace, or comment changes.
Per-file worker (subagent only)
Parent must not run this sequence. Read in order: (1) this skill—placement, candidates, steps below, anti-patterns, completion shape; (2) organize-file for ordering law. Do not rely on a short Task summary alone.
-
Confirm diff —
git diff "$MB" -- "<path>". Empty andgit show "HEAD:<path>"equals merge-base → no branch delta (if unstaged-only matters, alsogit diff -- "<path>"vs index). -
Build candidate set — Inventory
$MB:<path>vs working tree (prefer on-disk file when uncommitted changes exist). Per name/arity unit: name or arity change → candidate; else compare each clause head. Usegit diff "$MB" -- <path>for rename vs delete+add, test title changes, head-only edits. -
Classify —
Phoenix.LiveView,Phoenix.LiveComponent, plain module, or*_test.exs; apply matching organize-file section. -
Compute global slots — Full-module ordered list (step 3 + Placement). Plain
defpregion: merged lexicographic name list, not “candidate names only.” Per candidate, record block (first line throughend) and global neighbors in that list. Sanity check: no two consecutive planned candidates with anotherdefp/defname that should sit between them. -
Edit — Move whole candidate units only; preserve bodies and non-definition code. Apply global slots only—not minimal shuffle keeping branch defs clustered. Re-inventory after edits; plain modules:
defporder matches merged list. Clause order per organize-file where pattern match order is safe. Already between correct global neighbors → do not move (still fix intra-unit clause order if required). -
Completion message
path: <path> candidates: <names or test titles> actions: <moved | reordered clauses | already correct | none> notes: <optional: e.g. ambiguous rename>
Definition inventory
# Working tree — defs
grep -E '^\s*(def|defp|defmacro)\s+[a-zA-Z_?!]' -- "<path>" || true
git show "$MB:<path>" 2>/dev/null | grep -E '^\s*(def|defp|defmacro)\s+[a-zA-Z_?!]' || true
# Tests
grep -E '^\s*(test|describe)\s' -- "<path>" || true
git show "$MB:<path>" 2>/dev/null | grep -E '^\s*(test|describe)\s' || true
Line-based grep can miss rare multiline heads; use git diff when inconclusive.
Anti-patterns
- Orchestrator: edit sources, pass
modelonTask, or success replies other thancomplete(except single-line failure/blocker cases). - Worker: skip reading full organize-changes then organize-file; treat every changed line as a candidate; move non-candidates; cluster candidates by branch/diff/call graph; sort only candidate
defpnames; minimal shuffle in the wrong file region; move units already in the correct global slot (except allowed clause reorder). - Both: paths outside merge-base…HEAD ∪ working tree (unless user gave one path); destructive git (
reset,checkout --).