Skip to content

Commit 7948f91

Browse files
committed
feat: Add comprehensive Claude Code hooks for development workflow
This commit adds a complete set of Claude Code hooks to improve the development experience for Nextflow: **Immediate Formatting (PostToolUse):** - format-editorconfig.py: Applies editorconfig rules to all source files - format-idea.py: Formats Groovy files with IntelliJ IDEA code style **Build & Test Validation (Stop):** - check-build.py: Runs 'make compile' to catch compilation errors - run-tests.py: Runs corresponding test class for edited files **Subagent Validation (SubagentStop):** - check-build.py: Verifies subagent changes compile successfully **Benefits:** - Instant code formatting after each edit - Build errors caught before tests run - Tests only run once when agent finishes (not after each edit) - Subagents validate compilation but skip slower test runs - Non-blocking when optional tools (IDEA) not available **Configuration:** All hooks configured in .claude/settings.json with appropriate timeouts and proper event triggers for optimal development workflow.
1 parent deb8c52 commit 7948f91

File tree

4 files changed

+469
-25
lines changed

4 files changed

+469
-25
lines changed

.claude/README.md

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,25 @@ This directory contains Claude Code hooks configured to improve the Nextflow dev
44

55
## Features
66

7-
### 1. EditorConfig Enforcement
8-
- **Trigger**: After editing any source file (`.groovy`, `.java`, `.gradle`, `.md`, `.txt`, `.yml`, `.yaml`, `.json`)
7+
### 1. EditorConfig Enforcement (PostToolUse)
8+
- **Trigger**: Immediately after editing any source file (`.groovy`, `.java`, `.gradle`, `.md`, `.txt`, `.yml`, `.yaml`, `.json`)
99
- **Action**: Applies editorconfig formatting rules using `eclint`
1010
- **Files**: `hooks/format-editorconfig.py`
1111

12-
### 2. Automatic Test Running
13-
- **Trigger**: After editing source files or test files in modules or plugins
12+
### 2. IntelliJ IDEA Formatter (PostToolUse)
13+
- **Trigger**: Immediately after editing Groovy files
14+
- **Action**: Applies IntelliJ IDEA code formatting to match project style
15+
- **Files**: `hooks/format-idea.py`
16+
- **Requirements**: IntelliJ IDEA installed (Community or Ultimate Edition)
17+
18+
### 3. Build Check (Stop + SubagentStop)
19+
- **Trigger**: When Claude finishes responding or when a subagent completes
20+
- **Action**: Runs `make compile` to verify code compiles without errors
21+
- **Files**: `hooks/check-build.py`
22+
- **Purpose**: Catch syntax and compilation errors immediately
23+
24+
### 4. Automatic Test Running (Stop)
25+
- **Trigger**: When Claude finishes responding (main agent only)
1426
- **Action**:
1527
- For source files: Runs corresponding test class (e.g., `CacheDB.groovy` → runs `CacheDBTest`)
1628
- For test files: Runs the specific test class
@@ -19,10 +31,17 @@ This directory contains Claude Code hooks configured to improve the Nextflow dev
1931
## Hook Configuration
2032

2133
The hooks are configured in `.claude/settings.json` with:
22-
- **30-second timeout** for editorconfig formatting
23-
- **5-minute timeout** for test execution
24-
- **Smart file filtering** to only process relevant files
25-
- **Parallel execution** of both hooks after file edits
34+
35+
### PostToolUse (runs after each edit)
36+
- **format-editorconfig.py**: 30-second timeout
37+
- **format-idea.py**: 60-second timeout
38+
39+
### Stop (runs when main agent finishes)
40+
- **check-build.py**: 120-second timeout (runs first)
41+
- **run-tests.py**: 300-second timeout (runs after build succeeds)
42+
43+
### SubagentStop (runs when subagent finishes)
44+
- **check-build.py**: 120-second timeout
2645

2746
## Supported File Structure
2847

@@ -67,12 +86,40 @@ The hooks generate appropriate Gradle test commands:
6786
The hooks may automatically install:
6887
- `eclint` via npm for editorconfig enforcement
6988

89+
Optional dependencies:
90+
- IntelliJ IDEA (Community or Ultimate Edition) for Groovy formatting
91+
- Set `IDEA_SH` environment variable if not in standard location
92+
- Falls back gracefully if not found
93+
94+
## Hook Execution Flow
95+
96+
1. **During editing** (PostToolUse):
97+
```
98+
✓ EditorConfig formatting applied to CacheDB.groovy
99+
✓ IDEA formatter: applied to CacheDB.groovy (5.2s)
100+
```
101+
102+
2. **When finishing** (Stop):
103+
```
104+
✓ Build check passed (12.3s)
105+
✓ Tests passed for CacheDB.groovy
106+
```
107+
108+
3. **If errors occur**:
109+
```
110+
Build check failed:
111+
error: cannot find symbol
112+
symbol: variable foo
113+
location: class CacheDB
114+
```
115+
70116
## Customization
71117

72118
You can modify the hooks by:
73119
1. Editing the Python scripts in `hooks/`
74120
2. Adjusting timeouts in `settings.json`
75121
3. Adding or removing file extensions in the filter logic
122+
4. Disabling specific hooks by removing them from `settings.json`
76123

77124
## Troubleshooting
78125

@@ -81,20 +128,4 @@ If hooks aren't working:
81128
2. Verify Python 3 is available
82129
3. Check Claude Code's debug output with `claude --debug`
83130
4. Review hook execution in the transcript (Ctrl-R)
84-
85-
## Example Output
86-
87-
When editing a file, you'll see:
88-
```
89-
✓ EditorConfig formatting applied to CacheDB.groovy
90-
✓ Tests passed for CacheDB.groovy
91-
BUILD SUCCESSFUL in 2s
92-
```
93-
94-
If tests fail:
95-
```
96-
Tests failed for CacheDB.groovy:
97-
Error output:
98-
CacheDBTest > testCacheCreation FAILED
99-
AssertionError: Expected true but was false
100-
```
131+
5. For IDEA formatter: verify IDEA installation or set `IDEA_SH` environment variable

.claude/hooks/check-build.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Build check hook for Nextflow development.
4+
This hook runs a quick compilation check to catch syntax errors.
5+
"""
6+
7+
import json
8+
import os
9+
import subprocess
10+
import sys
11+
import time
12+
from pathlib import Path
13+
14+
15+
def run_build_check():
16+
"""Run a quick build check"""
17+
start_time = time.time()
18+
19+
try:
20+
# Run make compile for quick syntax checking
21+
result = subprocess.run(
22+
['make', 'compile'],
23+
capture_output=True,
24+
text=True,
25+
timeout=120 # 2 minute timeout
26+
)
27+
28+
execution_time = time.time() - start_time
29+
30+
if result.returncode == 0:
31+
return {
32+
'success': True,
33+
'execution_time': execution_time,
34+
'message': f"✓ Build check passed ({execution_time:.1f}s)"
35+
}
36+
else:
37+
# Extract error information
38+
error_lines = []
39+
for line in result.stdout.split('\n') + result.stderr.split('\n'):
40+
if any(keyword in line.lower() for keyword in ['error', 'failed', 'exception']):
41+
error_lines.append(line)
42+
43+
error_summary = '\n'.join(error_lines[-10:]) if error_lines else result.stderr[-500:]
44+
45+
return {
46+
'success': False,
47+
'execution_time': execution_time,
48+
'error': f"Build check failed:\n{error_summary}",
49+
'suggestions': [
50+
'Check for syntax errors in modified files',
51+
'Review compilation errors above',
52+
'Run `make compile` manually for full output'
53+
]
54+
}
55+
56+
except subprocess.TimeoutExpired:
57+
execution_time = time.time() - start_time
58+
return {
59+
'success': False,
60+
'execution_time': execution_time,
61+
'error': 'Build check timed out after 2 minutes',
62+
'suggestions': [
63+
'Check if build is hung',
64+
'Try running `make compile` manually'
65+
]
66+
}
67+
except Exception as e:
68+
execution_time = time.time() - start_time
69+
return {
70+
'success': False,
71+
'execution_time': execution_time,
72+
'error': f'Exception during build check: {str(e)}',
73+
'suggestions': [
74+
'Verify make is installed',
75+
'Check build system configuration'
76+
]
77+
}
78+
79+
80+
def main():
81+
try:
82+
input_data = json.load(sys.stdin)
83+
except json.JSONDecodeError as e:
84+
error_output = {
85+
"decision": "block",
86+
"reason": f"Build check hook received invalid JSON input: {e}",
87+
"suggestions": ["Check Claude Code hook configuration"]
88+
}
89+
print(json.dumps(error_output))
90+
sys.exit(0)
91+
92+
hook_event = input_data.get("hook_event_name", "")
93+
94+
# Only run on Stop and SubagentStop events
95+
if hook_event not in ["stop-hook", "subagent-stop-hook"]:
96+
sys.exit(0)
97+
98+
# Run build check
99+
result = run_build_check()
100+
101+
if result['success']:
102+
output = {
103+
"suppressOutput": True,
104+
"systemMessage": result['message']
105+
}
106+
print(json.dumps(output))
107+
sys.exit(0)
108+
else:
109+
# Block with error information
110+
output = {
111+
"decision": "block",
112+
"reason": result['error'],
113+
"stopReason": "Build compilation failed",
114+
"buildError": {
115+
"executionTime": result['execution_time'],
116+
"suggestions": result.get('suggestions', [])
117+
}
118+
}
119+
print(json.dumps(output))
120+
sys.exit(0)
121+
122+
123+
if __name__ == "__main__":
124+
main()

0 commit comments

Comments
 (0)