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.
This commit is contained in:
Claudio Ortolina
2026-04-12 09:54:32 +01:00
parent e498dccde5
commit 9c7ab5b229
6 changed files with 73 additions and 2 deletions
+12
View File
@@ -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.
+1
View File
@@ -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.
+12
View File
@@ -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
+12
View File
@@ -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
+32 -1
View File
@@ -3,6 +3,8 @@
"allow": [ "allow": [
"mcp__tidewave__get_ecto_schemas", "mcp__tidewave__get_ecto_schemas",
"Bash(mix test:*)", "Bash(mix test:*)",
"Bash(mix test)",
"Bash(mix test *)",
"mcp__tidewave__get_source_location", "mcp__tidewave__get_source_location",
"WebFetch(domain:hexdocs.pm)", "WebFetch(domain:hexdocs.pm)",
"mcp__tidewave__execute_sql_query", "mcp__tidewave__execute_sql_query",
@@ -51,6 +53,7 @@
"Bash(gh issue:*)", "Bash(gh issue:*)",
"Bash(mise run:*)", "Bash(mise run:*)",
"Bash(gh label:*)", "Bash(gh label:*)",
"Bash(mix sobelow:*)",
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__list_pages", "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__navigate_page",
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__take_screenshot", "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__fill",
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__click", "mcp__plugin_chrome-devtools-mcp_chrome-devtools__click",
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__evaluate_script", "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 "enableAllProjectMcpServers": false
+4 -1
View File
@@ -161,7 +161,10 @@ defmodule MusicLibrary.MixProject do
], ],
"ecto.setup": ["ecto.create", "ecto.migrate"], "ecto.setup": ["ecto.create", "ecto.migrate"],
"ecto.reset": ["ecto.drop", "ecto.setup"], "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: [ lint: [
"format", "format",
"credo", "credo",