SuperAPI AI Directory

conventions

Agents & models

View repository

Updated 10-15 22:41

About this skill

General

Output and writing

  • Never write – (en dash) or — (em dash). Use -, instead.
  • Never use "copy" to mean on-screen UI text or labels (e.g. not "dashboard copy"). Say "text", "labels", or "wording". Reserve "copy" for duplication.
  • In chat output, use ~~~ for the outermost code block; only use ``` for blocks nested inside it.

Editing files

  • Do not run mix format after every change, regardless of other instructions.
  • Never use terminal commands (cat, echo, sed, python scripts, etc.) to read or edit files. Use the native agent file tools.
  • If you find changes you don't remember making, leave them alone. If they significantly affect what you're doing, stop and alert the user.

Git

  • "this branch" means all changes made on the branch - committed, staged, and unstaged. "current" or "working" changes means uncommitted changes (staged and unstaged).
# committed branch-only changes
git diff --name-status "$(git merge-base HEAD origin/main)"...HEAD

# working tree changes
git status --short

Tooling

  • When asked to run the linter or credo, run mix credo --strict.
  • Use gh for advanced GitHub tasks.
  • Do not write scripts into the project unless directed to. If a persistent script would significantly help with a repetitive task, ask first. Running one-off scripts in the terminal is always fine.

Code style

Minimalism and defensiveness

  • Write the minimum obvious code that matches how the feature is actually used. Treat every line and branch as a liability.
  • Do not write defensive code. No extra guards, normalization, retries, fallbacks, or compatibility shims for hypothetical callers or edge cases the product does not care about. Do not add transformations or checks "for completeness", "just in case", or "to be safe" - only when omitting them would make real behavior wrong or unsafe.
  • If a minimal solution leaves an edge case or hole open, flag it to the user instead of coding around it.
  • Do not write backward-compatibility code unless the user explicitly asks for it.
  • Avoid "belt and suspenders" code that does something redundantly or unnecessarily.
  • Do not add defaults or fallbacks unless they are reasonably needed.
  • Avoid creating new modules unless it is clearly necessary.
  • Only create a helper function if the logic is non-trivial (more than a single operation or check) AND reused. If it is used in one place, inline it.

Naming

  • Use descriptive variable names. Prioritize clarity over brevity while staying concise. For example, use something descriptive like status_category_clean instead of something vague, like raw.
  • Avoid built-in negation in names (ignore_, not_, _exempt, etc.), though it is occasionally warranted.

Control flow and formatting

  • One-line if statements when possible.
  • In if/else branches, write the success path first.
  • Prefer explicit control flow over conditional/no-op helpers. When a conditional/no-op helper is necessary, name it maybe_.

Readability

  • Prioritize readability over succinctness. Save discrete steps to variables rather than chaining multiple operations on one line.
  • Still prefer the simplest path for simple things: a one-line normalization, not a multi-line case.

Elixir

Naming

  • When pattern matching (tuple results, function heads, etc.), name unused values (_var), never a bare _.
  • Never create a standalone binding just to discard it. Call my_function() directly, not _result = my_function().
  • Bind literal values in function heads to named unused variables: fun(var1, _var2 = true), not fun(var1, true).
  • Follow the get/fetch idiom: get returns the value or nil (or a provided default); fetch returns a tagged tuple.
  • In context files, name functions explicitly for the resource: get_audio_asset_for_platform, not get_for_platform.

Syntax

  • Avoid function guards (e.g. defp normalize(params) when is_map(params)). Use them only when absolutely necessary.
  • Prefer pipe (|>) over nesting function calls.
  • Prefer pattern matching to destructure multiple values, e.g. {:error, error} -> handle_twilio_error(job, source_sid, phone_number, error) over pulling fields out one at a time.
  • Use bracket syntax (params["key"]) to access non-assertive fields, not Map.get.
  • For simple normalization, params["key"] || %{} over a multi-line case.
  • Prefer ! over not.
  • Prefer || over or.
  • Prefer == nil over is_nil() (and != nil over !is_nil()).
  • Prefer in over multiple or checks on the same variable: my_var in [nil, ""], not my_var == nil or my_var == "".
  • Do not use else clauses in with expressions. Each function in the with should handle its own errors so failures bubble up. Wrap calls in small private functions to accomplish this if needed.
  • Never use as: in a module alias or in a form, unless absolutely necessary.
  • Declare aliases and imports at the top of the file, never mid-file.

Specs and docs

  • Always include @spec on new functions, public and private.
  • Only one @spec per function, no matter how many clauses it has.
  • Always include a @doc on new public functions
  • Keep module and function docs concise and high-level - describe the general purpose, not internal specifics.
  • Keep error returns minimal. If :not_found is the only error a function can return, :error already implies not found - keep the response as simple as possible.

By file category

Context modules

  • Default to ReadRepo for reads (queries, preloads, aggregates). It is a read replica with ~20ms lag, so this is safe only when the caller can tolerate slightly stale data.
  • Keep reads on the primary Repo when staleness would break correctness: read-after-write in the same flow, latest-row state checks, reads that guard a write (uniqueness/existence/balance/idempotency), auth/ownership checks, and anything inside a transaction or Ecto.Multi. If unsure, ask clarification from the user.
  • Write queries with from in keyword style:
query =
  from d in MessageDelivery,
    where: d.phone_number == "40708",
    where: d.failed_at > ^~U[2026-04-01 00:00:00Z],
    select: d.contact_phone_number,
    distinct: true

ReadRepo.aggregate(query, :count)

Schemas

  • Do the heavy lifting of validation in the schema, not the context, as much as possible.
  • When a specific field needs its own validations or transformations, give it a separate changeset function rather than piling conditionals into one changeset.

Oban workers

  • Do not put business logic into Oban workers. A job should be a thin wrapper around one or a few context functions plus error handling - mainly to translate non-retryable errors into :cancel so the job does not retry for them.
  • When a job fetches data it cannot proceed without, and that data is not just transiently absent, do not use an assertive get (no !). Use a plain get and return {:cancel, :<reason_atom>} when the data is missing.

API

  • In the OpenAPI spec, make examples reference Fast and Furious in a way that fits the field. For a phone number, use something that reads as a phone number, like "Letty's Main Line" - not a random "Toretto's Garage".

Tests

  • Do not define private functions in test files. Prefer fresh inline setup in each test. For heavily duplicated or tedious shared setup, use setup after the describe.
  • Do not use @tag :capture_log.

Scripts

  • Use Repo.cursor_stream/2 to stream through rows in scripts.