From 9c7ab5b229d6905f2e794ea3bcd66eb798b0a56f Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Sun, 12 Apr 2026 09:54:32 +0100 Subject: [PATCH] Add Claude Code hooks, commands, and permissions High priority: - Auto-format on edit (.claude/hooks/format-elixir.sh): runs mix format synchronously after every Edit/Write on .ex, .exs, and .heex files. Ensures code is formatted before the next read or tool call. - mix test permissions: added Bash(mix test) (bare) and Bash(mix test *) (with path/flags) to the allow list. Previously only Bash(mix test:*) was allowed, which didn't match common invocations. - Slash commands: /precommit runs the full mix precommit pipeline and reports failures with fix suggestions. /oban-status queries Oban job state via Tidewave SQL, grouped by state and worker. Medium priority: - Chrome DevTools permissions: added 8 new tools (select_page, list_network_requests, get_network_request, list_console_messages, get_console_message, hover, press_key, wait_for) for debugging API traffic, JS hook errors, hover states, and keyboard navigation. - Gettext extraction hook (.claude/hooks/gettext-extract.sh): runs mix gettext.extract --merge asynchronously after editing .heex files. Async because it's project-wide and doesn't need to block the next edit. - Sobelow permission: added Bash(mix sobelow:*) to the allow list for independent security checks. --- .claude/commands/oban-status.md | 12 ++++++++++++ .claude/commands/precommit.md | 1 + .claude/hooks/format-elixir.sh | 12 ++++++++++++ .claude/hooks/gettext-extract.sh | 12 ++++++++++++ .claude/settings.local.json | 33 +++++++++++++++++++++++++++++++- mix.exs | 5 ++++- 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 .claude/commands/oban-status.md create mode 100644 .claude/commands/precommit.md create mode 100755 .claude/hooks/format-elixir.sh create mode 100755 .claude/hooks/gettext-extract.sh diff --git a/.claude/commands/oban-status.md b/.claude/commands/oban-status.md new file mode 100644 index 00000000..85587565 --- /dev/null +++ b/.claude/commands/oban-status.md @@ -0,0 +1,12 @@ +Query the Oban job queue status using Tidewave's `execute_sql_query` tool against the `MusicLibrary.BackgroundRepo`. + +Run this SQL query: + +```sql +SELECT state, worker, count(*) as count +FROM oban_jobs +GROUP BY state, worker +ORDER BY state, count DESC +``` + +Present the results as a formatted table grouped by state. If there are failed or retryable jobs, show their error details. diff --git a/.claude/commands/precommit.md b/.claude/commands/precommit.md new file mode 100644 index 00000000..446923f8 --- /dev/null +++ b/.claude/commands/precommit.md @@ -0,0 +1 @@ +Run the full pre-commit check pipeline (`mix precommit`) and report the results. If any check fails, identify the specific failure and suggest a fix. diff --git a/.claude/hooks/format-elixir.sh b/.claude/hooks/format-elixir.sh new file mode 100755 index 00000000..ee9a8dea --- /dev/null +++ b/.claude/hooks/format-elixir.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +INPUT=$(cat) +FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path') + +if [[ "$FILE_PATH" == *.ex ]] || [[ "$FILE_PATH" == *.exs ]] || [[ "$FILE_PATH" == *.heex ]]; then + cd "$CLAUDE_PROJECT_DIR" + mix format "$FILE_PATH" 2>/dev/null || true +fi + +exit 0 diff --git a/.claude/hooks/gettext-extract.sh b/.claude/hooks/gettext-extract.sh new file mode 100755 index 00000000..e184e53c --- /dev/null +++ b/.claude/hooks/gettext-extract.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +INPUT=$(cat) +FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path') + +if [[ "$FILE_PATH" == *.heex ]]; then + cd "$CLAUDE_PROJECT_DIR" + mix gettext.extract --merge 2>/dev/null || true +fi + +exit 0 diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ba98744a..ca36b001 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,6 +3,8 @@ "allow": [ "mcp__tidewave__get_ecto_schemas", "Bash(mix test:*)", + "Bash(mix test)", + "Bash(mix test *)", "mcp__tidewave__get_source_location", "WebFetch(domain:hexdocs.pm)", "mcp__tidewave__execute_sql_query", @@ -51,6 +53,7 @@ "Bash(gh issue:*)", "Bash(mise run:*)", "Bash(gh label:*)", + "Bash(mix sobelow:*)", "mcp__plugin_chrome-devtools-mcp_chrome-devtools__list_pages", "mcp__plugin_chrome-devtools-mcp_chrome-devtools__navigate_page", "mcp__plugin_chrome-devtools-mcp_chrome-devtools__take_screenshot", @@ -58,7 +61,35 @@ "mcp__plugin_chrome-devtools-mcp_chrome-devtools__fill", "mcp__plugin_chrome-devtools-mcp_chrome-devtools__click", "mcp__plugin_chrome-devtools-mcp_chrome-devtools__evaluate_script", - "mcp__plugin_chrome-devtools-mcp_chrome-devtools__new_page" + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__new_page", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__select_page", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__list_network_requests", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__get_network_request", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__list_console_messages", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__get_console_message", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__hover", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__press_key", + "mcp__plugin_chrome-devtools-mcp_chrome-devtools__wait_for" + ] + }, + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-elixir.sh", + "timeout": 30 + }, + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/gettext-extract.sh", + "timeout": 30, + "async": true + } + ] + } ] }, "enableAllProjectMcpServers": false diff --git a/mix.exs b/mix.exs index 60db7f5c..ea11c416 100644 --- a/mix.exs +++ b/mix.exs @@ -161,7 +161,10 @@ defmodule MusicLibrary.MixProject do ], "ecto.setup": ["ecto.create", "ecto.migrate"], "ecto.reset": ["ecto.drop", "ecto.setup"], - shellcheck: "cmd fd . 'scripts/' --exclude '*.hurl' -t file --exec shellcheck --color", + shellcheck: [ + "cmd fd . 'scripts/' --exclude '*.hurl' -t file --exec shellcheck --color", + "cmd fd . '.claude/hooks' -t file --exec shellcheck --color" + ], lint: [ "format", "credo",