diff --git a/AGENTS.md b/AGENTS.md index 9eb3dd87..e2ddce18 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,13 +29,15 @@ Before exploring the codebase for any task, read `docs/architecture.md`. Do this + ## usage_rules usage + _A config-driven dev tool for Elixir projects to manage AGENTS.md files and agent skills from dependencies_ ## Using Usage Rules -Many packages have usage rules, which you should *thoroughly* consult before taking any -action. These usage rules contain guidelines and rules *directly from the package authors*. +Many packages have usage rules, which you should _thoroughly_ consult before taking any +action. These usage rules contain guidelines and rules _directly from the package authors_. They are your best source of knowledge for making decisions. ## Modules & functions in the current app and dependencies @@ -54,10 +56,9 @@ mix usage_rules.docs Enum.zip mix usage_rules.docs Enum.zip/1 ``` - ## Searching Documentation -You should also consult the documentation of any tools you are using, early and often. The best +You should also consult the documentation of any tools you are using, early and often. The best way to accomplish this is to use the `usage_rules.search_docs` mix task. Once you have found what you are looking for, use the links in the search results to get more detail. For example: @@ -75,23 +76,27 @@ mix usage_rules.search_docs "making requests" -p req mix usage_rules.search_docs "Enum.zip" --query-by title ``` - + ## usage_rules:elixir usage + # Elixir Core Usage Rules ## Pattern Matching + - Use pattern matching over conditional logic when possible - Prefer to match on function heads instead of using `if`/`else` or `case` in function bodies - `%{}` matches ANY map, not just empty maps. Use `map_size(map) == 0` guard to check for truly empty maps ## Error Handling + - Use `{:ok, result}` and `{:error, reason}` tuples for operations that can fail - Avoid raising exceptions for control flow - Use `with` for chaining operations that return `{:ok, _}` or `{:error, _}` ## Common Mistakes to Avoid + - Elixir has no `return` statement, nor early returns. The last expression in a block is always returned. - Don't use `Enum` functions on large collections when `Stream` is more appropriate - Avoid nested `case` statements - refactor to a single `case`, `with` or separate functions @@ -104,6 +109,7 @@ mix usage_rules.search_docs "Enum.zip" --query-by title - There are many useful standard library functions, prefer to use them where possible ## Function Design + - Use guard clauses: `when is_binary(name) and byte_size(name) > 0` - Prefer multiple function clauses over complex conditional logic - Name functions descriptively: `calculate_total_price/2` not `calc/2` @@ -111,6 +117,7 @@ mix usage_rules.search_docs "Enum.zip" --query-by title - Names like `is_thing` should be reserved for guards ## Data Structures + - Use structs over maps when the shape is known: `defstruct [:name, :age]` - Prefer keyword lists for options: `[timeout: 5000, retries: 3]` - Use maps for dynamic key-value data @@ -123,6 +130,7 @@ mix usage_rules.search_docs "Enum.zip" --query-by title - Read the docs and options fully before using tasks ## Testing + - Run tests in a specific file with `mix test test/my_test.exs` and a specific test with the line number `mix test path/to/test.exs:123` - Limit the number of failed tests with `mix test --max-failures n` - Use `@tag` to tag specific tests, and `mix test --only tag` to run only those tests @@ -135,26 +143,32 @@ mix usage_rules.search_docs "Enum.zip" --query-by title + ## usage_rules:otp usage + # OTP Usage Rules ## GenServer Best Practices + - Keep state simple and serializable - Handle all expected messages explicitly - Use `handle_continue/2` for post-init work - Implement proper cleanup in `terminate/2` when necessary ## Process Communication + - Use `GenServer.call/3` for synchronous requests expecting replies - Use `GenServer.cast/2` for fire-and-forget messages. - When in doubt, use `call` over `cast`, to ensure back-pressure - Set appropriate timeouts for `call/3` operations ## Fault Tolerance + - Set up processes such that they can handle crashing and being restarted by supervisors - Use `:max_restarts` and `:max_seconds` to prevent restart loops ## Task and Async + - Use `Task.Supervisor` for better fault tolerance - Handle task failures with `Task.yield/2` or `Task.shutdown/2` - Set appropriate task timeouts @@ -162,13 +176,15 @@ mix usage_rules.search_docs "Enum.zip" --query-by title + ## usage_rules usage + _A config-driven dev tool for Elixir projects to manage AGENTS.md files and agent skills from dependencies_ ## Using Usage Rules -Many packages have usage rules, which you should *thoroughly* consult before taking any -action. These usage rules contain guidelines and rules *directly from the package authors*. +Many packages have usage rules, which you should _thoroughly_ consult before taking any +action. These usage rules contain guidelines and rules _directly from the package authors_. They are your best source of knowledge for making decisions. ## Modules & functions in the current app and dependencies @@ -187,10 +203,9 @@ mix usage_rules.docs Enum.zip mix usage_rules.docs Enum.zip/1 ``` - ## Searching Documentation -You should also consult the documentation of any tools you are using, early and often. The best +You should also consult the documentation of any tools you are using, early and often. The best way to accomplish this is to use the `usage_rules.search_docs` mix task. Once you have found what you are looking for, use the links in the search results to get more detail. For example: @@ -208,23 +223,27 @@ mix usage_rules.search_docs "making requests" -p req mix usage_rules.search_docs "Enum.zip" --query-by title ``` - + ## usage_rules:elixir usage + # Elixir Core Usage Rules ## Pattern Matching + - Use pattern matching over conditional logic when possible - Prefer to match on function heads instead of using `if`/`else` or `case` in function bodies - `%{}` matches ANY map, not just empty maps. Use `map_size(map) == 0` guard to check for truly empty maps ## Error Handling + - Use `{:ok, result}` and `{:error, reason}` tuples for operations that can fail - Avoid raising exceptions for control flow - Use `with` for chaining operations that return `{:ok, _}` or `{:error, _}` ## Common Mistakes to Avoid + - Elixir has no `return` statement, nor early returns. The last expression in a block is always returned. - Don't use `Enum` functions on large collections when `Stream` is more appropriate - Avoid nested `case` statements - refactor to a single `case`, `with` or separate functions @@ -237,6 +256,7 @@ mix usage_rules.search_docs "Enum.zip" --query-by title - There are many useful standard library functions, prefer to use them where possible ## Function Design + - Use guard clauses: `when is_binary(name) and byte_size(name) > 0` - Prefer multiple function clauses over complex conditional logic - Name functions descriptively: `calculate_total_price/2` not `calc/2` @@ -244,6 +264,7 @@ mix usage_rules.search_docs "Enum.zip" --query-by title - Names like `is_thing` should be reserved for guards ## Data Structures + - Use structs over maps when the shape is known: `defstruct [:name, :age]` - Prefer keyword lists for options: `[timeout: 5000, retries: 3]` - Use maps for dynamic key-value data @@ -256,6 +277,7 @@ mix usage_rules.search_docs "Enum.zip" --query-by title - Read the docs and options fully before using tasks ## Testing + - Run tests in a specific file with `mix test test/my_test.exs` and a specific test with the line number `mix test path/to/test.exs:123` - Limit the number of failed tests with `mix test --max-failures n` - Use `@tag` to tag specific tests, and `mix test --only tag` to run only those tests @@ -268,10 +290,13 @@ mix usage_rules.search_docs "Enum.zip" --query-by title + ## mdex usage + _Fast and extensible Markdown for Elixir_ @deps/mdex/usage-rules.md + @@ -293,6 +318,7 @@ This project uses Backlog.md MCP for all task and project management activities. - **When to read it**: BEFORE creating tasks, or when you're unsure whether to track work These guides cover: + - Decision framework for when to create tasks - Search-first workflow to avoid duplicates - Links to detailed guides for task creation, execution, and finalization diff --git a/docs/available-tasks.md b/docs/available-tasks.md index 36428cf2..09c29794 100644 --- a/docs/available-tasks.md +++ b/docs/available-tasks.md @@ -50,7 +50,7 @@ Run pi tests - **Usage**: `dev:precommit` -Run checks before a commit +Run checks before a commit (conditional on staged file types) ## `dev:readme` diff --git a/docs/project-conventions.md b/docs/project-conventions.md index 0c64f46e..5fb3ab9a 100644 --- a/docs/project-conventions.md +++ b/docs/project-conventions.md @@ -16,6 +16,24 @@ Key rules: imperative present tense, single-line under 60 characters, task ID pr - When working on a Backlog.md task, **make sure to read the implementation plan included in the task, and follow it**. If the plan is outdated compared to the conditions of the codebase, notify the user. - **GitHub issues are legacy and read-only for the agent.** Never create, comment on, close, or reopen GitHub issues — only the user does that. When asked about "the issue tracker" or "open issues", reach for Backlog.md, not `gh issue`. +### Pre-commit Hooks + +The pre-commit hook (`scripts/dev/precommit`) inspects staged files and runs only the checks relevant to the change, rather than always running the full verification suite. CI always runs the full suite unconditionally. + +| Category | File pattern | Checks (conditional) | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **elixir** | `lib/`, `test/`, `config/`, `mix.exs`, `mix.lock`, `priv/repo/migrations/`, `priv/gettext/`, `.credo.exs`, `.formatter.exs`, `.sobelow-conf` | `mix format --check-formatted`
`mix credo --strict`
`mix sobelow --compact --exit`
`mix gettext.extract --check-up-to-date`
`mix test`
`mix deps.unlock --unused` † | +| **shell** | `scripts/`, `.shellcheckrc` | `shellcheck` on all `scripts/` files (excluding `.hurl`) | +| **assets** | `assets/`, `.pi/extensions/.*\.(ts\|js\|json)$` | `prettier` on CSS, JS, and TS/JSON in `.pi/extensions/` (excluding `node_modules`) | +| **docs** | `docs/`, `README.md`, `AGENTS.md` | `prettier` on Markdown and Livebook files | +| **backlog** | `backlog/` | `prettier` on all backlog markdown files | +| **presto** | `presto/` | `(cd presto && mise run test)` — runs pytest | +| **docker** | `Dockerfile`, `.dockerignore`, `compose.yaml` | `mise run dev:validate-docker-image` (only if `Dockerfile` is staged) | + +> **† `mix deps.unlock --unused` sub-gate**: Runs only when `mix.exs` or `mix.lock` is in the staged files — not on every Elixir change. An unused dependency can only be introduced by changing the dependency specification, not by changing application code. + +If no staged files match any category (e.g., `git commit --allow-empty`), the script exits early without running any checks. + ## Architecture - **Context modules own all queries.** LiveViews never query the database directly -- they call context functions. diff --git a/scripts/dev/precommit b/scripts/dev/precommit index 5054124d..42167ed4 100755 --- a/scripts/dev/precommit +++ b/scripts/dev/precommit @@ -1,6 +1,6 @@ #!/usr/bin/env bash -#MISE description="Run checks before a commit" +#MISE description="Run checks before a commit (conditional on staged file types)" set -e @@ -8,27 +8,74 @@ source "$(git rev-parse --show-toplevel)/scripts/_helpers.sh" ensure_working_directory! -debug_msg "Running shellcheck..." -fd . 'scripts/' --exclude '*.hurl' -t file --exec shellcheck --color +# Convert space-separated STAGED list to newline-delimited for grep. +# $STAGED is exported by .git/hooks/pre-commit via git diff-index. +staged_lines() { + echo "$STAGED" | tr ' ' '\n' +} -debug_msg "Running credo..." -mix credo --strict +# Early exit if no staged files (e.g., --allow-empty) +if [ -z "$STAGED" ]; then + debug_msg "No staged files found, skipping pre-commit checks." + exit 0 +fi -debug_msg "Running sobelow..." -mix sobelow --compact --exit +# --- Shell scripts --- +if staged_lines | grep -qE '^scripts/|^\.shellcheckrc'; then + debug_msg "Running shellcheck..." + fd . 'scripts/' --exclude '*.hurl' -t file --exec shellcheck --color +fi -debug_msg "Checking translations..." -mix gettext.extract --check-up-to-date +# --- Elixir --- +if staged_lines | grep -qE '^(lib/|test/|config/|mix\.exs|mix\.lock|priv/repo/migrations/|priv/gettext/|\.credo\.exs|\.formatter\.exs|\.sobelow-conf)'; then + debug_msg "Running credo..." + mix credo --strict -debug_msg "Checking formatting..." -mix format --check-formatted -prettier --check '.pi/extensions/**/*.{ts,js,json}' -prettier --check 'assets/css/**/*.css' 'assets/js/**/*.js' -prettier --check 'docs/**/*.md' 'docs/**/*.livemd' 'README.md' -prettier --check 'backlog/archive/**/*.md' 'backlog/completed/**/*.md' 'backlog/tasks/**/*.md' 'backlog/docs/**/*.md' + debug_msg "Running sobelow..." + mix sobelow --compact --exit -debug_msg "Checking unused deps..." -mix deps.unlock --unused + debug_msg "Checking translations..." + mix gettext.extract --check-up-to-date -debug_msg "Running tests..." -mix test + debug_msg "Checking formatting..." + mix format --check-formatted + + debug_msg "Running tests..." + mix test +fi + +# deps.unlock sub-gate: only when mix.exs or mix.lock changed +if staged_lines | grep -qE '^mix\.exs$|^mix\.lock$'; then + debug_msg "Checking unused deps..." + mix deps.unlock --unused +fi + +# --- Assets (JS/TS/CSS) --- +if staged_lines | grep -qE '^assets/|^\.pi/extensions/.*\.(ts|js|json)$'; then + debug_msg "Checking assets formatting..." + prettier --check 'assets/css/**/*.css' 'assets/js/**/*.js' '.pi/extensions/**/*.{ts,js,json}' '!.pi/extensions/**/node_modules/**' +fi + +# --- Documentation --- +if staged_lines | grep -qE '^docs/|^README\.md$|^AGENTS\.md$'; then + debug_msg "Checking docs formatting..." + prettier --check 'docs/**/*.md' 'docs/**/*.livemd' 'README.md' 'AGENTS.md' +fi + +# --- Backlog --- +if staged_lines | grep -qE '^backlog/'; then + debug_msg "Checking backlog formatting..." + prettier --check 'backlog/archive/**/*.md' 'backlog/completed/**/*.md' 'backlog/tasks/**/*.md' 'backlog/docs/**/*.md' +fi + +# --- Presto --- +if staged_lines | grep -qE '^presto/'; then + debug_msg "Checking presto..." + (cd presto && mise run test) +fi + +# --- Docker --- +if staged_lines | grep -qE '^Dockerfile$|^\.dockerignore$|^compose\.yaml$'; then + debug_msg "Validating Docker image..." + mise run dev:validate-docker-image +fi