Skip to content

Commit 0936cb9

Browse files
committed
feat(hooks): add test-runner PostToolUse hook
After editing a .groovy/.java file under modules/ or plugins/, map it to its test class and run it via gradle (e.g. CacheDB.groovy -> ":nextflow:test --tests *CacheDBTest"); test files run themselves. On failure, block and feed the failure summary back to Claude, falling back to the tail of the output when no failure lines match. Written in Bash; parses the payload with jq. Wired as a PostToolUse hook because it keys off tool_input.file_path, which is only present on Edit/Write/MultiEdit events. Stacked on hooks-2-build-check. Signed-off-by: Edmund Miller <edmund.miller@seqera.io>
1 parent 098629c commit 0936cb9

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

.claude/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ understood on its own. This file documents the hooks that are currently wired up
3030
- **Failure mode**: blocking — feeds a parsed error summary back to Claude so it
3131
can fix the breakage before yielding
3232

33+
### 3. Test runner (PostToolUse)
34+
- **Script**: `hooks/run-tests.sh`
35+
- **Trigger**: after an `Edit`/`Write`/`MultiEdit` on a `.groovy`/`.java` file under
36+
`modules/` or `plugins/`
37+
- **Action**: maps the edited file to its test class and runs it, e.g.
38+
`modules/nextflow/.../cache/CacheDB.groovy``./gradlew :nextflow:test --tests "*CacheDBTest"`
39+
(test files run themselves)
40+
- **Failure mode**: blocking — feeds the test-failure summary back to Claude
41+
- **Note**: runs per edit, so the timeout is generous (300s); narrow the matcher if
42+
per-edit test runs are too heavy.
43+
3344
## Enabling / disabling
3445

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

.claude/hooks/run-tests.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Test runner hook for Nextflow development.
4+
#
5+
# After an Edit/Write/MultiEdit on a .groovy/.java file under modules/ or
6+
# plugins/, map it to its test class and run it via gradle. A source file runs
7+
# its corresponding *Test class; a test file runs itself. Block on failure.
8+
#
9+
# Reads the Claude Code hook payload as JSON on stdin (requires `jq`).
10+
11+
set -uo pipefail
12+
13+
command -v jq >/dev/null 2>&1 || exit 0
14+
15+
input=$(cat)
16+
tool_name=$(jq -r '.tool_name // ""' <<<"$input")
17+
file_path=$(jq -r '.tool_input.file_path // ""' <<<"$input")
18+
19+
case "$tool_name" in
20+
Edit|Write|MultiEdit) ;;
21+
*) exit 0 ;;
22+
esac
23+
24+
[[ -n "$file_path" ]] || exit 0
25+
case "$file_path" in *.groovy|*.java) ;; *) exit 0 ;; esac
26+
case "$file_path" in *modules/*|*plugins/*) ;; *) exit 0 ;; esac
27+
28+
# Resolve the gradle module path.
29+
module=""
30+
case "$file_path" in
31+
*plugins/nf-*)
32+
plugin=$(sed -E 's#.*plugins/(nf-[^/]+)/.*#\1#' <<<"$file_path")
33+
module="plugins:${plugin}" ;;
34+
*modules/nextflow/*) module="nextflow" ;;
35+
*modules/nf-commons/*) module="nf-commons" ;;
36+
*modules/nf-httpfs/*) module="nf-httpfs" ;;
37+
*modules/nf-lang/*) module="nf-lang" ;;
38+
*modules/nf-lineage/*) module="nf-lineage" ;;
39+
esac
40+
[[ -n "$module" ]] || exit 0
41+
42+
class=$(basename "$file_path"); class="${class%.*}"
43+
case "$class" in
44+
*Test) test_class="$class" ;;
45+
*) test_class="${class}Test" ;;
46+
esac
47+
48+
if out=$(./gradlew ":${module}:test" --tests "*${test_class}" 2>&1); then
49+
jq -n --arg f "$(basename "$file_path")" \
50+
'{suppressOutput: true, systemMessage: ("✓ Tests passed for " + $f)}'
51+
else
52+
summary=$(printf '%s\n' "$out" | grep -iE 'failed|error|exception|assertion' | tail -n 5)
53+
[[ -n "$summary" ]] || summary=$(printf '%s\n' "$out" | tail -n 15)
54+
jq -n --arg f "$(basename "$file_path")" --arg s "$summary" \
55+
'{decision: "block", reason: ("Tests failed for " + $f + ":\n" + $s)}'
56+
fi

.claude/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
"type": "command",
99
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-editorconfig.sh",
1010
"timeout": 30
11+
},
12+
{
13+
"type": "command",
14+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests.sh",
15+
"timeout": 300
1116
}
1217
]
1318
}

0 commit comments

Comments
 (0)