Add "RFC-Hook-Efficiency-Improvements"

2026-01-29 04:18:10 +00:00
parent 5097bbca0a
commit 8f06f95784

@@ -0,0 +1,371 @@
# RFC: Hook Efficiency Improvements
**Status:** Proposal
**Created:** 2026-01-28
**Author:** Claude Code Analysis
**Priority:** Medium
**Estimated Effort:** 2-3 sprints
---
## Executive Summary
Analysis of the 16 hooks across 12 plugins revealed significant inefficiencies in trigger timing, matcher precision, and redundant operations. This RFC proposes changes to reduce session startup latency, minimize per-edit overhead, and eliminate duplicate logic.
---
## Current State
### Hook Inventory
| Plugin | Event | Matcher | Script | Lines |
|--------|-------|---------|--------|-------|
| projman | SessionStart | - | startup-check.sh | 119 |
| pr-review | SessionStart | - | startup-check.sh | 31 |
| claude-config-maintainer | SessionStart | - | enforce-rules.sh | 69 |
| data-platform | SessionStart | - | startup-check.sh | 55 |
| viz-platform | SessionStart | - | inline echo | 1 |
| cmdb-assistant | SessionStart | - | startup-check.sh | 67 |
| contract-validator | SessionStart | - | auto-validate.sh | 196 |
| doc-guardian | PostToolUse | `Write\|Edit\|MultiEdit` | notify.sh | 75 |
| code-sentinel | PreToolUse | `Write\|Edit\|MultiEdit` | security-check.sh | 63 |
| data-platform | PostToolUse | `Edit\|Write` | schema-diff-check.sh | 139 |
| contract-validator | PostToolUse | `Edit\|Write` | breaking-change-check.sh | 175 |
| project-hygiene | PostToolUse | `Write\|Edit` | cleanup.sh | 29 |
| cmdb-assistant | PreToolUse | NetBox MCP tools | validate-input.sh | 80 |
| git-flow | PreToolUse | `Bash` | branch-check.sh | 103 |
| git-flow | PreToolUse | `Bash` | commit-msg-check.sh | 75 |
| clarity-assist | UserPromptSubmit | - | vagueness-check.sh | 217 |
**Total:** 16 hooks, ~1,493 lines of shell code
### Performance Impact
**Per session startup:**
- 7 bash processes spawned
- 3 Python invocations
- 4 HTTP calls (Gitea API, NetBox API)
- Estimated 200-500ms added latency
**Per file edit:**
- 4 bash processes spawned (PostToolUse hooks)
- 1 bash process spawned (PreToolUse - code-sentinel)
- Multiple `find` and `git` commands
- Each hook independently parses JSON stdin
---
## Issues Identified
### Issue 1: SessionStart Hook Overload
**Problem:** 7 hooks run on every session, regardless of whether the user will use that plugin.
**Affected plugins:**
- projman
- pr-review
- claude-config-maintainer
- data-platform
- viz-platform
- cmdb-assistant
- contract-validator
**Specific concerns:**
- `projman` makes HTTP calls to Gitea API to check open issues
- `cmdb-assistant` makes HTTP calls to NetBox API
- `data-platform` spawns Python and attempts PostgreSQL connection
- `contract-validator` computes MD5 hashes of all plugin files
- `viz-platform` just echoes "loaded" - zero value
### Issue 2: Duplicate Logic Across Hooks
**Problem:** Multiple hooks implement the same checks.
| Check | Implemented by |
|-------|----------------|
| MCP venv exists | projman, pr-review, data-platform |
| Git remote vs .env mismatch | projman, pr-review |
| JSON stdin parsing | 8 different hooks |
| File path extraction from JSON | 8 different hooks |
### Issue 3: Overly Broad Matchers
**Problem:** Some hooks trigger far more often than needed.
| Hook | Matcher | Problem |
|------|---------|---------|
| git-flow/branch-check.sh | `Bash` | Triggers on every bash command |
| git-flow/commit-msg-check.sh | `Bash` | Triggers on every bash command |
| project-hygiene/cleanup.sh | `Write\|Edit` | Runs `find` on entire project every edit |
| data-platform/schema-diff-check.sh | `Edit\|Write` | Checks every file, then pattern-matches |
### Issue 4: clarity-assist Always-On
**Problem:** `vagueness-check.sh` (217 lines) runs on every user prompt.
- Parses prompt
- Runs multiple regex matches
- Uses `bc` for floating-point calculations
- Adds latency to every message
**Current behavior:** Opt-out (runs unless `CLARITY_ASSIST_AUTO_SUGGEST=false`)
**Recommended:** Opt-in (disabled unless explicitly enabled)
### Issue 5: No Shared Utilities
**Problem:** Each hook implements its own:
- JSON parsing
- File path extraction
- Prefix output formatting
- Early exit conditions
---
## Proposed Changes
### Change 1: Consolidate SessionStart into Single Dispatcher
**Create:** `scripts/hooks/session-dispatcher.sh`
```bash
#!/bin/bash
# Single entry point for all SessionStart checks
# Runs common checks once, dispatches to plugins only when relevant
# Common checks (run once)
check_marketplace_health() { ... }
check_mcp_venvs() { ... }
check_git_remote() { ... }
# Plugin-specific checks (conditional)
if is_projman_project; then
source_plugin_check "projman"
fi
```
**Benefits:**
- Single process instead of 7
- Common checks run once
- Plugin checks only run if relevant
**Files to modify:**
- Create `scripts/hooks/session-dispatcher.sh`
- Create `scripts/hooks/lib/common.sh` (shared functions)
- Update all plugin `hooks.json` to remove SessionStart
- Add single SessionStart hook at marketplace level
### Change 2: Add Path-Based Filtering to PostToolUse Hooks
**Current:** Hooks spawn, parse stdin, check path, usually exit.
**Proposed:** Check path relevance before detailed processing.
```bash
# At top of each PostToolUse hook
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | ...)
# Immediate exit for irrelevant paths
case "$FILE_PATH" in
*/node_modules/*|*/.git/*|*/venv/*|*/__pycache__/*)
exit 0 ;;
esac
```
**Affected hooks:**
- doc-guardian/notify.sh
- data-platform/schema-diff-check.sh
- contract-validator/breaking-change-check.sh
- project-hygiene/cleanup.sh
### Change 3: Smarter git-flow Bash Hooks
**Current:** Both hooks trigger on every Bash command.
**Option A:** Early command check before full parsing
```bash
# Quick check - if not git command, exit immediately
if ! echo "$INPUT" | grep -q '"command".*git'; then
exit 0
fi
```
**Option B:** Combine into single hook
```bash
# Single git-flow hook that handles both branch and commit validation
```
**Files to modify:**
- `plugins/git-flow/hooks/branch-check.sh`
- `plugins/git-flow/hooks/commit-msg-check.sh`
- Optionally merge into `plugins/git-flow/hooks/git-validator.sh`
### Change 4: Remove viz-platform SessionStart Hook
**Current:**
```json
{
"event": "SessionStart",
"type": "command",
"command": "echo 'viz-platform plugin loaded'"
}
```
**Proposed:** Delete entirely. Provides zero value.
**Files to modify:**
- `plugins/viz-platform/hooks/hooks.json` - remove the hook
### Change 5: Make clarity-assist Opt-In
**Current:** Enabled by default, check `CLARITY_ASSIST_AUTO_SUGGEST=false` to disable.
**Proposed:** Disabled by default, check `CLARITY_ASSIST_AUTO_SUGGEST=true` to enable.
```bash
# Change from:
AUTO_SUGGEST="${CLARITY_ASSIST_AUTO_SUGGEST:-true}"
# To:
AUTO_SUGGEST="${CLARITY_ASSIST_AUTO_SUGGEST:-false}"
```
**Files to modify:**
- `plugins/clarity-assist/hooks/vagueness-check.sh` (line 11)
- `plugins/clarity-assist/README.md` (document opt-in)
### Change 6: Create Shared Hook Library
**Create:** `scripts/hooks/lib/common.sh`
```bash
#!/bin/bash
# Shared utilities for all hooks
# Parse file_path from JSON stdin
extract_file_path() {
echo "$1" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | \
head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/'
}
# Parse command from JSON stdin
extract_command() {
echo "$1" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('command',''))" 2>/dev/null
}
# Standard prefix output
hook_output() {
local prefix="$1"
shift
echo "[$prefix] $*"
}
# Check if path is in ignorable location
is_ignored_path() {
case "$1" in
*/node_modules/*|*/.git/*|*/venv/*|*/__pycache__/*|*/.venv/*)
return 0 ;;
esac
return 1
}
```
**Benefits:**
- Consistent behavior across hooks
- Single place to fix bugs
- Reduced code duplication
### Change 7: Optimize project-hygiene Cleanup
**Current:** Runs `find` across entire project on every edit.
**Proposed:**
1. Only run cleanup after significant operations (not every edit)
2. Use more targeted paths
3. Add cooldown period (don't run if ran in last 5 minutes)
```bash
# Add cooldown
LAST_RUN_FILE="/tmp/project-hygiene-last-run"
if [[ -f "$LAST_RUN_FILE" ]]; then
LAST_RUN=$(cat "$LAST_RUN_FILE")
NOW=$(date +%s)
if (( NOW - LAST_RUN < 300 )); then
exit 0 # Skip if ran in last 5 minutes
fi
fi
```
**Files to modify:**
- `plugins/project-hygiene/hooks/cleanup.sh`
---
## Implementation Order
### Phase 1: Quick Wins (Low effort, high impact)
1. Remove viz-platform SessionStart hook
2. Make clarity-assist opt-in
3. Add early exit to git-flow hooks
### Phase 2: Optimizations (Medium effort)
4. Add path filtering to PostToolUse hooks
5. Add cooldown to project-hygiene
6. Create shared hook library
### Phase 3: Architecture (Higher effort)
7. Consolidate SessionStart into dispatcher
8. Refactor all hooks to use shared library
---
## Testing Plan
### Before changes
- Measure session startup time (10 runs, average)
- Measure time to complete 10 file edits
- Count hook invocations via logging
### After each phase
- Re-measure all metrics
- Verify no functionality lost
- Test on both dev machine and Raspberry Pi (hotport)
### Expected improvements
- SessionStart: 200-500ms → 50-100ms (4-5x faster)
- Per-edit overhead: 100-200ms → 20-50ms (4x faster)
- Process spawns: 7 SessionStart + 5 per-edit → 1 SessionStart + 2-3 per-edit
---
## Risks and Mitigations
| Risk | Mitigation |
|------|------------|
| Breaking existing functionality | Comprehensive test plan, phase rollout |
| Shared library versioning | Keep library in marketplace root, plugins reference via relative path |
| User config migration (clarity-assist) | Document in CHANGELOG, mention in release notes |
---
## Open Questions
1. Should we support a `hooks.json` path matcher natively? (Would require Claude Code changes)
2. Should SessionStart hooks be lazy-loaded on first plugin command instead?
3. Is the shared library approach compatible with plugins being installed independently?
---
## References
- Analysis performed: 2026-01-28
- Total hooks analyzed: 16
- Total shell code: ~1,493 lines
- Plugins affected: 12
---
## Changelog
| Date | Change |
|------|--------|
| 2026-01-28 | Initial RFC created |