Skip to content

Commit e097194

Browse files
committed
feat(hooks): add IntelliJ IDEA formatter PostToolUse hook (async)
After editing a .groovy file, run IntelliJ IDEA's headless formatter to match project code style. IDEA is located via IDEA_SH or common macOS/Linux/JetBrains-Toolbox paths. Wired with `async: true` so it formats in the background without blocking the conversation, and is fully non-blocking: skips silently when IDEA is absent (optional dependency) and only warns — never blocks — if the formatter errors. Written in Bash; parses the payload with jq. Most environment-specific of the hooks, so it lands last in the stack and can be dropped without unwinding the others. Stacked on hooks-3-test-runner. Signed-off-by: Edmund Miller <edmund.miller@seqera.io>
1 parent 578641e commit e097194

3 files changed

Lines changed: 85 additions & 0 deletions

File tree

.claude/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ understood on its own. This file documents the hooks that are currently wired up
4343
without gaps, so no declarative `if` is used here. Runs per edit, so the timeout is
4444
generous (300s); narrow the matcher if per-edit test runs are too heavy.
4545

46+
### 4. IntelliJ IDEA formatter (PostToolUse, async)
47+
- **Script**: `hooks/format-idea.sh`
48+
- **Trigger**: after an `Edit`/`Write`/`MultiEdit` on a `.groovy` file
49+
- **Action**: runs IntelliJ IDEA's headless formatter to match project code style
50+
- **Dependencies**: IntelliJ IDEA (Community or Ultimate). Located via the `IDEA_SH`
51+
environment variable or common macOS/Linux/JetBrains-Toolbox install paths
52+
- **`async: true`**: runs in the background; non-blocking — skips silently if IDEA is not
53+
installed and only warns (never blocks) if the formatter errors
54+
4655
## Enabling / disabling
4756

4857
Hooks are configured in `.claude/settings.json`. Disable one by removing its entry;

.claude/hooks/format-idea.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env bash
2+
#
3+
# IntelliJ IDEA formatter hook for Nextflow development.
4+
#
5+
# After an Edit/Write/MultiEdit on a .groovy file, run IntelliJ IDEA's headless
6+
# formatter to match project code style. IDEA is located via the IDEA_SH
7+
# environment variable or common install paths.
8+
#
9+
# Non-blocking: skips silently if IDEA is not installed (optional), and only
10+
# warns (never blocks) if the formatter errors. Wired with `async: true`, so it
11+
# runs in the background and does not hold up the conversation.
12+
#
13+
# Reads the Claude Code hook payload as JSON on stdin (requires `jq`).
14+
15+
set -uo pipefail
16+
17+
command -v jq >/dev/null 2>&1 || exit 0
18+
19+
input=$(cat)
20+
tool_name=$(jq -r '.tool_name // ""' <<<"$input")
21+
file_path=$(jq -r '.tool_input.file_path // ""' <<<"$input")
22+
23+
case "$tool_name" in
24+
Edit|Write|MultiEdit) ;;
25+
*) exit 0 ;;
26+
esac
27+
28+
case "$file_path" in *.groovy) ;; *) exit 0 ;; esac
29+
[[ -f "$file_path" ]] || exit 0
30+
case "$file_path" in */build/*|*/.*/*) exit 0 ;; esac
31+
32+
find_idea() {
33+
if [[ -n "${IDEA_SH:-}" && -x "$IDEA_SH" ]]; then
34+
echo "$IDEA_SH"; return 0
35+
fi
36+
local candidates=(
37+
"/Applications/IntelliJ IDEA.app/Contents/MacOS/idea"
38+
"/Applications/IntelliJ IDEA CE.app/Contents/MacOS/idea"
39+
"/Applications/IntelliJ IDEA Ultimate.app/Contents/MacOS/idea"
40+
"/usr/local/bin/idea.sh"
41+
"/opt/idea/bin/idea.sh"
42+
"/snap/intellij-idea-community/current/bin/idea.sh"
43+
"/snap/intellij-idea-ultimate/current/bin/idea.sh"
44+
"$HOME/.local/share/JetBrains/Toolbox/apps/IDEA-U/bin/idea.sh"
45+
"$HOME/.local/share/JetBrains/Toolbox/apps/IDEA-C/bin/idea.sh"
46+
)
47+
local c
48+
for c in "${candidates[@]}"; do
49+
[[ -x "$c" ]] && { echo "$c"; return 0; }
50+
done
51+
command -v idea 2>/dev/null && return 0
52+
return 1
53+
}
54+
55+
# IDEA is an optional dependency: skip quietly if it isn't installed.
56+
idea=$(find_idea) || exit 0
57+
58+
# Prefer a sibling format.sh, else fall back to `idea format`.
59+
idea_dir=$(dirname "$idea")
60+
if [[ -x "$idea_dir/format.sh" ]]; then
61+
fmt=("$idea_dir/format.sh" -allowDefaults "$file_path")
62+
else
63+
fmt=("$idea" format -allowDefaults "$file_path")
64+
fi
65+
66+
if ! out=$("${fmt[@]}" 2>&1); then
67+
# Non-blocking: a formatting failure should never stop the workflow.
68+
echo "Warning: IDEA formatter failed for $file_path: ${out:-(no output)}" >&2
69+
exit 1
70+
fi

.claude/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"timeout": 30,
1111
"async": true
1212
},
13+
{
14+
"type": "command",
15+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-idea.sh",
16+
"timeout": 60,
17+
"async": true
18+
},
1319
{
1420
"type": "command",
1521
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests.sh",

0 commit comments

Comments
 (0)