docs: add hook improvement tasks for better feedback cycle

6 new issues for Claude Code hook improvements:

- 012: Test output filtering (reduce noise, show failures only)
- 013: Stop verification (verify work complete before stopping)
- 014: Auto-test on edit (async tests after code changes)
- 015: Session context injection (git/issues/CI on startup)
- 016: Silent auto-formatting (suppress formatter output)
- 017: Expose/hide policy (define what to show vs suppress)

Based on Claude Code hooks documentation:
- PostToolUse with suppressOutput for noise reduction
- Stop hooks with agent verification
- Async hooks for background testing
- SessionStart for context injection
- additionalContext for exposing important info

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-02-01 18:57:16 +00:00
parent beb24f71d2
commit 8fed5bc6ef
7 changed files with 603 additions and 2 deletions

View file

@ -58,9 +58,21 @@ claude/
No shell scripts. Just JSON config + markdown docs + `core` CLI calls.
### Hook Improvements (feedback cycle)
| Issue | Feature | Purpose |
|-------|---------|---------|
| #012 | Test output filtering | Reduce noise, show only failures |
| #013 | Stop verification | Verify work complete before stopping |
| #014 | Auto-test on edit | Run tests async after code changes |
| #015 | Session context | Inject git/issues/CI context on start |
| #016 | Silent formatting | Auto-format without output noise |
| #017 | Expose/hide policy | Define what to show vs suppress |
## Implementation Order
1. **Phase 1**: `core ai session` + `core ai context` (enables hooks to work)
2. **Phase 2**: `core ai hook` + `core qa debug` (safety + quality)
3. **Phase 3**: `core collect github` + `core collect bitcointalk` (most used)
4. **Phase 4**: Remaining collection commands
3. **Phase 3**: Hook improvements (012-017) - better feedback cycle
4. **Phase 4**: `core collect github` + `core collect bitcointalk` (most used)
5. **Phase 5**: Remaining collection commands

View file

@ -0,0 +1,80 @@
# feat(hooks): Reduce test noise with smart output filtering
## Summary
Add PostToolUse hooks that filter test output to show only what matters - failures and summaries, not passing tests.
## Problem
When running `core go test` or `core php test`, the full output floods the context:
- Hundreds of "PASS" lines for passing tests
- Verbose coverage output
- Repetitive timing information
This wastes context window and makes failures harder to spot.
## Solution
PostToolUse hooks that:
1. Parse test output
2. Extract only failures, errors, and summary
3. Return filtered output as `additionalContext`
4. Use `suppressOutput: true` to hide the verbose original
## Hook Configuration
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "core ai filter test-output"
}
]
}
]
}
}
```
## Filter Logic
**Go tests:**
- Show: FAIL, ERROR, panic, --- FAIL
- Hide: PASS, RUN, coverage lines (unless requested)
- Summary: "47 passed, 2 failed, 1 skipped"
**PHP/Pest tests:**
- Show: ✗, FAIL, Error, Exception
- Hide: ✓, passing test names
- Summary: "Tests: 47 passed, 2 failed"
**Lint output:**
- Show: actual errors/warnings with file:line
- Hide: "no issues found" for each file
- Summary: "3 issues in 2 files"
## Output Format
```json
{
"suppressOutput": true,
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "## Test Results\n\n✗ 2 failed, 47 passed\n\n### Failures:\n- TestFoo: expected 5, got 3\n- TestBar: nil pointer"
}
}
```
## Command Detection
Only filter when command matches:
- `core go test*`
- `core php test*`
- `core go lint*`
- `core php stan*`
- `core qa*`

View file

@ -0,0 +1,98 @@
# feat(hooks): Stop hook to verify work is complete
## Summary
Add a Stop hook that verifies Claude has actually completed the task before stopping, preventing premature "I'm done" responses.
## Problem
Claude sometimes stops too early:
- "I've updated the file" (but didn't run tests)
- "The fix is complete" (but there are lint errors)
- "Done" (but left debug statements)
## Solution
A Stop hook (type: "agent") that spawns a verification subagent to check:
1. Were tests run? Did they pass?
2. Are there uncommitted changes that should be committed?
3. Are there debug statements left in code?
4. Does the output match what was requested?
## Hook Configuration
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify the work is complete. Check: 1) Tests passed if code was changed, 2) No debug statements (dd, dump, fmt.Println), 3) Code is formatted. Context: $ARGUMENTS",
"timeout": 60
}
]
}
]
}
}
```
## Alternative: Command-based
For faster verification without subagent overhead:
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "core ai verify-complete"
}
]
}
]
}
}
```
## Verification Checks
`core ai verify-complete` should check:
1. **If code was modified:**
- Run `core go test` or `core php test`
- Check for debug statements
- Verify formatting
2. **If commit was requested:**
- Check git status for uncommitted changes
3. **Return decision:**
```json
{
"decision": "block",
"reason": "Tests have not been run. Please run: core go test"
}
```
## Guard Against Loops
Check `stop_hook_active` to prevent infinite loops:
```bash
#!/bin/bash
INPUT=$(cat)
STOP_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active')
if [ "$STOP_ACTIVE" = "true" ]; then
# Already continued once, allow stop
exit 0
fi
# Run verification...
```

View file

@ -0,0 +1,97 @@
# feat(hooks): Auto-run tests after code changes (async)
## Summary
Add async PostToolUse hooks that automatically run tests after Write/Edit operations, reporting results without blocking.
## Problem
Claude often edits code but forgets to run tests, or runs tests manually each time which is slow and repetitive.
## Solution
Async PostToolUse hooks that:
1. Detect code file changes (*.go, *.php, *.ts)
2. Run relevant tests in background
3. Report results on next turn via `systemMessage`
## Hook Configuration
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "core ai auto-test",
"async": true,
"timeout": 120,
"statusMessage": "Running tests..."
}
]
}
]
}
}
```
## Test Detection Logic
`core ai auto-test` should:
1. **Read file path from stdin:**
```bash
FILE=$(jq -r '.tool_input.file_path')
```
2. **Detect test scope:**
- `*.go` in `pkg/foo/``core go test ./pkg/foo/...`
- `*.php` in `src/``core php test --filter=related`
- `*_test.go` → run that specific test file
- `*.spec.ts` → run that specific spec
3. **Run minimal test set:**
- Don't run full suite on every change
- Use `--filter` or package scope
- Cache recent test runs
4. **Report results:**
```json
{
"systemMessage": "Tests for pkg/foo: 12 passed, 0 failed (2.3s)"
}
```
## Smart Test Selection
For Go:
```bash
# Find related test file
TEST_FILE="${FILE%.go}_test.go"
if [ -f "$TEST_FILE" ]; then
core go test -run "$(basename $TEST_FILE .go)" ./...
else
# Run package tests
PKG_DIR=$(dirname "$FILE")
core go test "./$PKG_DIR/..."
fi
```
For PHP:
```bash
# Find related test
TEST_FILE=$(echo "$FILE" | sed 's/\.php$/Test.php/' | sed 's|src/|tests/|')
if [ -f "$TEST_FILE" ]; then
core php test "$TEST_FILE"
fi
```
## Debouncing
Avoid running tests multiple times for rapid edits:
- Track last test run per package
- Skip if <5 seconds since last run
- Queue and batch rapid changes

View file

@ -0,0 +1,101 @@
# feat(hooks): SessionStart context injection
## Summary
Add SessionStart hooks that inject relevant project context at the start of each session.
## Problem
Claude starts each session without knowing:
- Recent git changes
- Open issues assigned to the user
- CI status
- Current branch and uncommitted work
This leads to repeated context-gathering at the start of every session.
## Solution
SessionStart hook that runs `core` commands to gather context and inject it.
## Hook Configuration
```json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "core ai session-context",
"timeout": 30
}
]
}
]
}
}
```
## Context to Gather
`core ai session-context` should collect:
1. **Git status:**
```bash
BRANCH=$(git branch --show-current)
CHANGES=$(git status --short | head -10)
```
2. **Recent activity:**
```bash
COMMITS=$(git log --oneline -5)
```
3. **Open issues (if gh available):**
```bash
ISSUES=$(core dev issues --assignee @me --limit 5)
```
4. **CI status:**
```bash
CI=$(core qa health --brief)
```
5. **Restored session state:**
```bash
if [ -f ~/.claude/sessions/scratchpad.md ]; then
# Include previous session context
fi
```
## Output Format
```json
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "## Session Context\n\n**Branch:** feature/foo\n**Uncommitted:** 3 files\n\n**Recent commits:**\n- abc123 fix: resolve test flake\n- def456 feat: add new endpoint\n\n**Your issues:**\n- #42 Bug in login flow\n- #38 Add caching\n\n**CI:** All green ✓"
}
}
```
## Environment Variables
Also set useful env vars via `CLAUDE_ENV_FILE`:
```bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo "export PROJECT_ROOT=\"$(pwd)\"" >> "$CLAUDE_ENV_FILE"
echo "export GIT_BRANCH=\"$BRANCH\"" >> "$CLAUDE_ENV_FILE"
fi
```
## Conditional Context
Only inject what's relevant:
- Skip CI status if not in a repo with workflows
- Skip issues if gh not authenticated
- Skip session restore if >3 hours old

View file

@ -0,0 +1,89 @@
# feat(hooks): Auto-format code silently after edits
## Summary
Add PostToolUse hooks that silently auto-format code after Write/Edit, without polluting context with formatter output.
## Current State
Current hooks call formatters but output is visible:
- `php-format.sh` - runs Pint
- `go-format.sh` - runs gofmt
This creates noise in the conversation.
## Solution
Use `suppressOutput: true` to hide formatter output entirely.
## Hook Configuration
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "core ai format-file",
"statusMessage": "Formatting..."
}
]
}
]
}
}
```
## Implementation
`core ai format-file`:
```bash
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] || [ ! -f "$FILE" ] && exit 0
case "$FILE" in
*.go)
core go fmt "$FILE" 2>/dev/null
;;
*.php)
core php fmt "$FILE" 2>/dev/null
;;
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE" 2>/dev/null
;;
*.json)
jq '.' "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE" 2>/dev/null
;;
esac
# Suppress all output - formatting is silent
echo '{"suppressOutput": true}'
```
## Benefits
1. **No context pollution** - formatter output hidden
2. **Consistent formatting** - every file formatted on save
3. **No manual step** - Claude doesn't need to remember to format
4. **Fast** - formatters are quick, doesn't block significantly
## Error Handling
Only report if formatting fails badly:
```bash
if ! core go fmt "$FILE" 2>&1; then
# Only surface actual errors, not warnings
echo '{"systemMessage": "Format failed for '$FILE' - syntax error?"}'
exit 0
fi
echo '{"suppressOutput": true}'
```

View file

@ -0,0 +1,124 @@
# feat(hooks): Define what to expose vs hide in output
## Summary
Document and implement a consistent policy for what hook output should be exposed to Claude vs hidden.
## Principles
**Expose (additionalContext):**
- Errors that need fixing
- Failures that block progress
- Decisions that need to be made
- Security warnings
- Breaking changes
**Hide (suppressOutput):**
- Success confirmations ("formatted", "passed")
- Verbose progress output
- Repetitive status messages
- Debug information
- Intermediate results
## Output Categories
### Always Expose
| Category | Example | Reason |
|----------|---------|--------|
| Test failures | `FAIL: TestFoo` | Must be fixed |
| Build errors | `cannot find package` | Blocks progress |
| Lint errors | `undefined: foo` | Code quality |
| Security alerts | `HIGH vulnerability` | Critical |
| Type errors | `type mismatch` | Must be fixed |
### Always Hide
| Category | Example | Reason |
|----------|---------|--------|
| Pass confirmations | `PASS: TestFoo` | No action needed |
| Format success | `Formatted 3 files` | No action needed |
| Coverage numbers | `coverage: 84.2%` | Unless requested |
| Timing info | `(12.3s)` | Noise |
| Progress bars | `[=====> ]` | Noise |
### Conditional
| Category | Show When | Hide When |
|----------|-----------|-----------|
| Warnings | First occurrence | Repeated |
| Suggestions | Actionable | Informational |
| Diffs | Small (<10 lines) | Large |
| Stack traces | Unique error | Repeated |
## Implementation Pattern
```bash
#!/bin/bash
INPUT=$(cat)
OUTPUT=$(echo "$INPUT" | jq -r '.tool_response.output // empty')
# Extract what matters
ERRORS=$(echo "$OUTPUT" | grep -E "FAIL|ERROR|panic" | head -10)
SUMMARY=$(echo "$OUTPUT" | tail -1)
if [ -n "$ERRORS" ]; then
# Expose failures
cat << EOF
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "## Issues Found\n\n$ERRORS\n\n**Summary:** $SUMMARY"
}
}
EOF
else
# Hide success
echo '{"suppressOutput": true}'
fi
```
## Hook-Specific Policies
### PostToolUse (Bash)
```
core go test → Expose failures, hide passes
core go lint → Expose errors, hide "no issues"
core build → Expose errors, hide success
git status → Always expose (informational)
git commit → Expose result, hide diff
```
### PostToolUse (Write/Edit)
```
Format result → Always hide
Debug check → Expose if found, hide if clean
```
### PostToolUseFailure
```
All errors → Always expose with context
Interrupts → Hide (user initiated)
```
## Aggregation
When multiple issues, aggregate intelligently:
```
Instead of:
- FAIL: TestA
- FAIL: TestB
- FAIL: TestC
- (47 more)
Show:
"50 tests failed. Top failures:
- TestA: nil pointer
- TestB: timeout
- TestC: assertion failed
Run `core go test -v` for full output"
```