Skip to content

Commit 94fc995

Browse files
Joseph Edmondsclaude
andcommitted
feat: hooks-daemon integration
- Add daemon detection and config enforcement to deploy-skills.bash - Automatic classic hook removal when daemon detected - Update README.md with Claude Code Integration section - Seamless migration path for existing projects Eliminates double execution overhead, single source of truth. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 20e12b0 commit 94fc995

2 files changed

Lines changed: 264 additions & 4 deletions

File tree

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,56 @@ The script configures:
8080

8181
**Prerequisites**: Requires [GitHub CLI](https://cli.github.com/) (`gh`) installed and authenticated.
8282

83+
## Claude Code Integration
84+
85+
PHP-QA-CI now integrates seamlessly with [Claude Code Hooks Daemon](https://github.com/anthropics/claude-code-hooks-daemon) to provide enhanced development guardrails and automation when using Claude Code.
86+
87+
### What is hooks-daemon?
88+
89+
hooks-daemon is a high-performance daemon for Claude Code hooks that provides 20x faster execution than classic hooks after warmup. It enables intelligent workflow enforcement, destructive command prevention, and automated quality checks.
90+
91+
### Integration Features
92+
93+
When you deploy php-qa-ci skills and hooks to your project, the deployment script automatically:
94+
95+
- **Detects hooks-daemon** - Checks if `.claude/hooks-daemon.yaml` exists
96+
- **Configures required handlers** - Ensures the daemon has the necessary handlers enabled with correct settings
97+
- **Migrates from classic hooks** - Removes legacy `.claude/hooks/*.py` files and settings.json registrations
98+
- **Provides clear instructions** - If daemon not detected, displays installation guide
99+
100+
### Benefits
101+
102+
**No double execution overhead** - Single handler execution instead of running both classic hooks and daemon handlers
103+
**Superior performance** - 20x faster after warmup via Unix socket IPC
104+
**Better implementations** - Daemon handlers include enhancements and bug fixes
105+
**Single source of truth** - Daemon provides all hook functionality
106+
**Automatic configuration** - php-qa-ci enforces required daemon settings
107+
108+
### Required Daemon Handlers
109+
110+
When hooks-daemon is detected, php-qa-ci configures these handlers:
111+
112+
- `git_stash` (mode: deny) - Strict blocking of git stash operations
113+
- `plan_time_estimates` - Prevents time estimates in plan documents
114+
- `validate_instruction_content` - Ensures docs contain instructions, not logs
115+
- `markdown_organization` - Enforces documentation organization
116+
117+
### Installation
118+
119+
**Deploy skills and hooks** (will configure daemon if present):
120+
```bash
121+
vendor/lts/php-qa-ci/scripts/deploy-skills.bash vendor/lts/php-qa-ci .
122+
```
123+
124+
**Install hooks-daemon** (if not already installed):
125+
```bash
126+
git clone -b v2.2.0 https://github.com/anthropics/claude-code-hooks-daemon.git .claude/hooks-daemon
127+
cd .claude/hooks-daemon
128+
./scripts/install/install.bash
129+
```
130+
131+
**Documentation**: See `.claude/hooks/README.md` for detailed hook documentation and [hooks-daemon repository](https://github.com/anthropics/claude-code-hooks-daemon) for daemon documentation.
132+
83133
## Docs
84134

85135
Comprehensive documentation is available in the [./docs](./docs) folder:

scripts/deploy-skills.bash

Lines changed: 214 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,26 @@ AGENTS_SOURCE="$QACI_PATH/.claude/agents"
1616
AGENTS_TARGET="$PROJECT_ROOT/.claude/agents"
1717
HOOKS_SOURCE="$QACI_PATH/.claude/hooks"
1818
HOOKS_TARGET="$PROJECT_ROOT/.claude/hooks"
19+
DAEMON_CONFIG="$PROJECT_ROOT/.claude/hooks-daemon.yaml"
20+
21+
# Check if hooks-daemon is present
22+
DAEMON_DETECTED=false
23+
if [[ -f "$DAEMON_CONFIG" ]]; then
24+
DAEMON_DETECTED=true
25+
fi
1926

2027
echo "Deploying Skills from: $SKILLS_SOURCE"
2128
echo " to: $SKILLS_TARGET"
2229
echo "Deploying Agents from: $AGENTS_SOURCE"
2330
echo " to: $AGENTS_TARGET"
24-
echo "Deploying Hooks from: $HOOKS_SOURCE"
25-
echo " to: $HOOKS_TARGET"
31+
32+
if [[ "$DAEMON_DETECTED" == "true" ]]; then
33+
echo ""
34+
echo "📋 hooks-daemon detected - will configure daemon instead of deploying classic hooks"
35+
else
36+
echo "Deploying Hooks from: $HOOKS_SOURCE"
37+
echo " to: $HOOKS_TARGET"
38+
fi
2639

2740
# Create .claude directories
2841
mkdir -p "$SKILLS_TARGET"
@@ -61,8 +74,8 @@ if [[ -d "$AGENTS_SOURCE" ]]; then
6174
done
6275
fi
6376

64-
# Copy each hook (Python scripts)
65-
if [[ -d "$HOOKS_SOURCE" ]]; then
77+
# Copy each hook (Python scripts) - ONLY if daemon not detected
78+
if [[ "$DAEMON_DETECTED" == "false" ]] && [[ -d "$HOOKS_SOURCE" ]]; then
6679
for hook_file in "$HOOKS_SOURCE"/*.py; do
6780
if [[ -f "$hook_file" ]]; then
6881
hook_name=$(basename "$hook_file")
@@ -246,6 +259,203 @@ with open(settings_file, 'w') as f:
246259
PYTHON_SCRIPT
247260
fi
248261

262+
# ============================================================================
263+
# Phase 4: hooks-daemon Config Enforcement
264+
# ============================================================================
265+
# Ensure projects using php-qa-ci have required daemon handlers configured
266+
267+
DAEMON_CONFIG="$PROJECT_ROOT/.claude/hooks-daemon.yaml"
268+
269+
if [[ -f "$DAEMON_CONFIG" ]]; then
270+
echo ""
271+
echo "📋 hooks-daemon detected - enforcing required handler configuration..."
272+
273+
# Use Python with PyYAML to validate and update config
274+
python3 - "$DAEMON_CONFIG" << 'PYTHON_DAEMON_CONFIG'
275+
import sys
276+
from pathlib import Path
277+
278+
# Try to import yaml - gracefully handle if not available
279+
try:
280+
import yaml
281+
HAS_YAML = True
282+
except ImportError:
283+
HAS_YAML = False
284+
285+
config_file = Path(sys.argv[1])
286+
287+
if not HAS_YAML:
288+
print(" ⚠️ Warning: PyYAML not installed - cannot validate daemon config", file=sys.stderr)
289+
print(" Install with: pip install pyyaml", file=sys.stderr)
290+
print(" Continuing deployment without config validation...", file=sys.stderr)
291+
sys.exit(0)
292+
293+
# Required handlers for php-qa-ci projects
294+
REQUIRED_HANDLERS = {
295+
"git_stash": {
296+
"enabled": True,
297+
"mode": "deny" # Strict for php-qa-ci projects
298+
},
299+
"plan_time_estimates": {
300+
"enabled": True
301+
},
302+
"validate_instruction_content": {
303+
"enabled": True
304+
},
305+
"markdown_organization": {
306+
"enabled": True
307+
}
308+
}
309+
310+
try:
311+
# Load existing config
312+
with open(config_file, 'r') as f:
313+
config = yaml.safe_load(f) or {}
314+
315+
# Ensure structure exists
316+
if 'handlers' not in config:
317+
config['handlers'] = {}
318+
if 'pre_tool_use' not in config['handlers']:
319+
config['handlers']['pre_tool_use'] = {}
320+
321+
pre_tool_use = config['handlers']['pre_tool_use']
322+
changes_made = []
323+
324+
# Enforce each required handler
325+
for handler_name, required_config in REQUIRED_HANDLERS.items():
326+
if handler_name not in pre_tool_use:
327+
# Handler not configured at all - add it
328+
pre_tool_use[handler_name] = required_config
329+
changes_made.append(f" ✓ Added handler: {handler_name}")
330+
else:
331+
# Handler exists - verify configuration
332+
existing = pre_tool_use[handler_name]
333+
if not isinstance(existing, dict):
334+
# Handler is not a dict (maybe just enabled: true) - fix it
335+
pre_tool_use[handler_name] = required_config
336+
changes_made.append(f" ✓ Updated handler: {handler_name}")
337+
else:
338+
# Check each required key
339+
for key, value in required_config.items():
340+
if key not in existing:
341+
existing[key] = value
342+
changes_made.append(f" ✓ Added {handler_name}.{key}: {value}")
343+
elif existing[key] != value:
344+
old_value = existing[key]
345+
existing[key] = value
346+
changes_made.append(f" ✓ Updated {handler_name}.{key}: {old_value} → {value}")
347+
348+
if changes_made:
349+
# Write back updated config
350+
with open(config_file, 'w') as f:
351+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
352+
353+
print(" Configuration updated:")
354+
for change in changes_made:
355+
print(change)
356+
else:
357+
print(" ✓ All required handlers already configured correctly")
358+
359+
except Exception as e:
360+
print(f" ⚠️ Warning: Could not validate daemon config: {e}", file=sys.stderr)
361+
print(" Continuing deployment...", file=sys.stderr)
362+
363+
PYTHON_DAEMON_CONFIG
364+
365+
echo " ✓ hooks-daemon configuration enforced"
366+
367+
# ========================================================================
368+
# Remove classic hooks - daemon provides all functionality now
369+
# ========================================================================
370+
echo ""
371+
echo "🧹 Removing classic hooks (daemon provides functionality)..."
372+
373+
# Remove hook files
374+
HOOKS_REMOVED=()
375+
for hook_file in "$HOOKS_TARGET"/php-qa-ci__*.py; do
376+
if [[ -f "$hook_file" ]]; then
377+
hook_name=$(basename "$hook_file")
378+
rm -f "$hook_file"
379+
HOOKS_REMOVED+=("$hook_name")
380+
echo " ✓ Removed: $hook_name"
381+
fi
382+
done
383+
384+
# Update settings.json to remove hook registrations
385+
if [[ -f "$SETTINGS_FILE" ]] && [[ ${#HOOKS_REMOVED[@]} -gt 0 ]]; then
386+
echo ""
387+
echo " Updating settings.json to remove hook registrations..."
388+
389+
python3 - "$SETTINGS_FILE" "${HOOKS_REMOVED[@]}" << 'PYTHON_CLEANUP'
390+
import json
391+
import sys
392+
393+
settings_file = sys.argv[1]
394+
hooks_removed = sys.argv[2:]
395+
396+
try:
397+
with open(settings_file, 'r') as f:
398+
settings = json.load(f)
399+
400+
# Get hooks config
401+
hooks_config = settings.get('hooks', {})
402+
changes_made = False
403+
404+
# Check all hook sections
405+
for hook_type in ['PreToolUse', 'PostToolUse', 'Stop']:
406+
hooks_section = hooks_config.get(hook_type, [])
407+
if not hooks_section:
408+
continue
409+
410+
hooks_list = hooks_section[0].get('hooks', [])
411+
original_count = len(hooks_list)
412+
413+
# Remove hooks that match removed files
414+
hooks_list[:] = [
415+
h for h in hooks_list
416+
if not any(removed in h.get('command', '') for removed in hooks_removed)
417+
]
418+
419+
new_count = len(hooks_list)
420+
if new_count < original_count:
421+
removed_count = original_count - new_count
422+
print(f" Removed {removed_count} hook(s) from {hook_type}")
423+
changes_made = True
424+
425+
hooks_section[0]['hooks'] = hooks_list
426+
427+
# Write back if changes made
428+
if changes_made:
429+
with open(settings_file, 'w') as f:
430+
json.dump(settings, f, indent=2)
431+
print(" ✓ settings.json updated")
432+
else:
433+
print(" ✓ No hook registrations to remove")
434+
435+
except Exception as e:
436+
print(f" ⚠️ Warning: Could not update settings.json: {e}", file=sys.stderr)
437+
438+
PYTHON_CLEANUP
439+
fi
440+
441+
echo ""
442+
echo "✅ Classic hooks removed - hooks-daemon now provides all functionality"
443+
else
444+
echo ""
445+
echo "ℹ️ hooks-daemon not detected"
446+
echo ""
447+
echo " php-qa-ci hooks require hooks-daemon to function."
448+
echo " Classic .claude/hooks/*.py files have been deployed but won't run without daemon."
449+
echo ""
450+
echo " To install hooks-daemon:"
451+
echo " git clone -b v2.2.0 https://github.com/anthropics/claude-code-hooks-daemon.git .claude/hooks-daemon"
452+
echo " cd .claude/hooks-daemon"
453+
echo " ./scripts/install/install.bash"
454+
echo ""
455+
echo " Or see: https://github.com/anthropics/claude-code-hooks-daemon"
456+
echo ""
457+
fi
458+
249459
echo "✓ Skills, Agents & Hooks deployment complete"
250460
echo ""
251461
echo "Installed skills:"

0 commit comments

Comments
 (0)