10 KiB
Quality Hooks Setup
Simple Setup (Context Files Only)
For most projects, context files are all you need.
Context injection is AUTOMATIC - no configuration files required. Just create .claude/context/ directory and add markdown files following the naming pattern.
Quick Setup
# 1. Create context directory
mkdir -p .claude/context
# 2. Add context files for your commands/skills
# For /code-review command
cat > .claude/context/code-review-start.md << 'EOF'
## Security Requirements
- Authentication on all endpoints
- Input validation
- No secrets in logs
EOF
# For test-driven-development skill
cat > .claude/context/test-driven-development-start.md << 'EOF'
## TDD Standards
- Write failing test first
- Implement minimal code to pass
- Refactor with tests passing
EOF
# For session start
cat > .claude/context/session-start.md << 'EOF'
## Project Context
- TypeScript project using Vitest
- Follow functional programming style
- Use strict type checking
EOF
That's It!
Context files auto-inject when commands/skills run. No gates.json needed.
Need quality gates or custom commands? Continue to "Advanced Setup" below.
Advanced Setup (gates.json Configuration)
Only needed for quality enforcement (lint, test, build checks) or custom commands.
Quality hooks support optional project-level gates.json configuration for running quality checks.
gates.json Search Priority
The hooks search for gates.json in this order:
.claude/gates.json- Project-specific configuration (recommended)gates.json- Project root configuration${CLAUDE_PLUGIN_ROOT}hooks/gates.json- Plugin default (fallback)
Quick gates.json Setup
Option 1: Recommended (.claude/gates.json)
# Create .claude directory
mkdir -p .claude
# Copy example configuration
cp ${CLAUDE_PLUGIN_ROOT}hooks/examples/strict.json .claude/gates.json
# Customize for your project
vim .claude/gates.json
Option 2: Project Root (gates.json)
# Copy example configuration
cp ${CLAUDE_PLUGIN_ROOT}hooks/examples/strict.json gates.json
# Customize for your project
vim gates.json
Customizing Gates
Edit your project's gates.json to match your build tooling:
{
"gates": {
"check": {
"description": "Run quality checks",
"command": "npm run lint", // ← Change to your command
"on_pass": "CONTINUE",
"on_fail": "BLOCK"
},
"test": {
"description": "Run tests",
"command": "npm test", // ← Change to your command
"on_pass": "CONTINUE",
"on_fail": "BLOCK"
}
},
"hooks": {
"PostToolUse": {
"enabled_tools": ["Edit", "Write"],
"gates": ["check"]
},
"SubagentStop": {
"enabled_agents": ["rust-agent"],
"gates": ["check", "test"]
}
}
}
Common Command Patterns
Node.js/TypeScript:
{
"gates": {
"check": {"command": "npm run lint"},
"test": {"command": "npm test"},
"build": {"command": "npm run build"}
}
}
Rust:
{
"gates": {
"check": {"command": "cargo clippy"},
"test": {"command": "cargo test"},
"build": {"command": "cargo build"}
}
}
Python:
{
"gates": {
"check": {"command": "ruff check ."},
"test": {"command": "pytest"},
"build": {"command": "python -m build"}
}
}
mise tasks:
{
"gates": {
"check": {"command": "mise run check"},
"test": {"command": "mise run test"},
"build": {"command": "mise run build"}
}
}
Make:
{
"gates": {
"check": {"command": "make lint"},
"test": {"command": "make test"},
"build": {"command": "make build"}
}
}
Example Configurations
The plugin provides three example configurations:
Strict Mode (Block on Failures)
cp ${CLAUDE_PLUGIN_ROOT}hooks/examples/strict.json .claude/gates.json
Best for: Production code, established projects
Permissive Mode (Warn Only)
cp ${CLAUDE_PLUGIN_ROOT}hooks/examples/permissive.json .claude/gates.json
Best for: Prototyping, learning, experimental work
Pipeline Mode (Chained Gates)
cp ${CLAUDE_PLUGIN_ROOT}hooks/examples/pipeline.json .claude/gates.json
Best for: Complex workflows, auto-formatting before checks
Enabling/Disabling Hooks
Disable Quality Hooks Entirely
Remove or rename your project's gates.json:
mv .claude/gates.json .claude/gates.json.disabled
Disable Specific Hooks
Edit gates.json to remove hooks:
{
"hooks": {
"PostToolUse": {
"enabled_tools": [], // ← Empty = disabled
"gates": []
},
"SubagentStop": {
"enabled_agents": ["rust-agent"], // ← Keep enabled
"gates": ["check", "test"]
}
}
}
Disable Specific Tools/Agents
Remove from enabled lists:
{
"hooks": {
"PostToolUse": {
"enabled_tools": ["Edit"], // ← Removed "Write"
"gates": ["check"]
}
}
}
Testing Your Configuration
# Test gate execution manually
source ${CLAUDE_PLUGIN_ROOT}hooks/shared-functions.sh
run_gate "check" ".claude/gates.json"
# Verify JSON is valid
jq . .claude/gates.json
# Test with mock hook input
export CLAUDE_PLUGIN_ROOT=/path/to/plugin
echo '{"tool_name": "Edit", "cwd": "'$(pwd)'"}' | ${CLAUDE_PLUGIN_ROOT}hooks/post-tool-use.sh
Version Control
Recommended: Commit gates.json
git add .claude/gates.json
git commit -m "chore: configure quality gates"
This ensures all team members use the same quality standards.
Optional: Per-Developer Override
Developers can override with local configuration:
# Team config
.claude/gates.json ← committed
# Personal override (gitignored)
gates.json ← takes priority, not committed
Add to .gitignore:
/gates.json
Troubleshooting
Hooks Not Running
-
Check configuration exists:
ls -la .claude/gates.json -
Verify plugin root is set:
echo $CLAUDE_PLUGIN_ROOT -
Check tool/agent is enabled:
jq '.hooks.PostToolUse.enabled_tools' .claude/gates.json
Gate Fails for Verification-Only Agents
Symptom: SubagentStop gates fail for agents that only read files (technical-writer in verification mode, research-agent).
Cause: Missing enabled_agents filter - gates run for ALL agents.
Solution: Add enabled_agents to only include code-modifying agents:
{
"hooks": {
"SubagentStop": {
"enabled_agents": ["rust-agent", "code-agent", "commit-agent"],
"gates": ["check", "test"]
}
}
}
Why: Verification agents don't modify code, so check/test gates are unnecessary and produce false positive failures.
Commands Failing
-
Test command manually:
npm run lint # or whatever your check command is -
Check command exists:
which npm -
Verify working directory:
- Commands run from project root (where gates.json lives)
- Use absolute paths if needed
JSON Syntax Errors
# Validate JSON
jq . .claude/gates.json
# Common errors:
# - Missing commas between items
# - Trailing commas in arrays/objects
# - Unescaped quotes in strings
Plugin Gate References
You can reference gates defined in other plugins to reuse quality checks across projects.
Configuration
Use the plugin and gate fields to reference external gates:
{
"gates": {
"plan-compliance": {
"plugin": "cipherpowers",
"gate": "plan-compliance",
"description": "Verify implementation matches plan"
},
"check": {
"command": "npm run lint",
"on_fail": "BLOCK"
}
},
"hooks": {
"SubagentStop": {
"gates": ["plan-compliance", "check"]
}
}
}
How Plugin Gates Work
Plugin Discovery:
- The
pluginfield uses sibling convention - Assumes plugins are installed in the same parent directory
- Example: If your plugin is in
~/.claude/plugins/turboshovel/, it looks for~/.claude/plugins/cipherpowers/
Execution Context:
- Plugin gate commands run in the plugin's directory
- This allows plugin gates to access their own tools and configurations
- Your project's working directory is still available via environment variables
Required Fields:
plugin: Name of the plugin containing the gategate: Name of the gate defined in the plugin'sgates.json
Optional Fields:
description: Override the plugin's gate description- Other gate fields (like
on_pass,on_fail) use the plugin's defaults
Mixing Local and Plugin Gates
You can combine local gates (with command field) and plugin gates (with plugin field) in the same configuration:
{
"gates": {
"plan-compliance": {
"plugin": "cipherpowers",
"gate": "plan-compliance"
},
"code-review": {
"plugin": "cipherpowers",
"gate": "code-review"
},
"check": {
"command": "npm run lint"
},
"test": {
"command": "npm test"
}
},
"hooks": {
"SubagentStop": {
"gates": ["plan-compliance", "check", "test"]
},
"PostToolUse": {
"enabled_tools": ["Edit", "Write"],
"gates": ["check"]
}
}
}
Troubleshooting Plugin Gates
Plugin not found:
- Verify plugin is installed in sibling directory
- Check plugin name matches directory name
- Example:
"plugin": "cipherpowers"requires../cipherpowers/directory
Gate not found in plugin:
- Verify gate name matches plugin's
gates.json - Check plugin's
gates.jsonfor available gates - Gate names are case-sensitive
Plugin gate fails:
- Plugin gates run in plugin's directory context
- Check plugin's own configuration and dependencies
- Review logs for plugin-specific error messages
Migration from Plugin Default
If you were using the plugin's default gates.json, migrate to project-level:
# Copy current config
cp ${CLAUDE_PLUGIN_ROOT}hooks/gates.json .claude/gates.json
# Customize for this project
vim .claude/gates.json
The plugin default now serves as a fallback template only.