Files
2025-11-30 09:02:16 +08:00

8.9 KiB

Quality Hooks

Automated quality enforcement and context injection via Claude Code's hook system. A self-referential TypeScript application that uses its own configuration format.

💡 CONTEXT INJECTION IS AUTOMATIC

Just create .claude/context/{name}-{stage}.md files - they auto-inject at the right time. No configuration files needed. No gates.json. No setup.

The gates.json file is ONLY for optional quality enforcement (lint, test, build checks).

Quick Start

Just create context files - they auto-inject automatically:

# Create context directory
mkdir -p .claude/context

# Add context for /code-review command
cat > .claude/context/code-review-start.md << 'EOF'
## Security Requirements
- Authentication on all endpoints
- Input validation for user data
- No secrets in logs
- HTTPS only
EOF

# That's it! When /code-review runs, requirements auto-inject!

Works with ANY command, skill, or agent. Follow the naming pattern: .claude/context/{name}-{stage}.md

Advanced: Quality Gates (Optional)

Need to enforce quality checks? Add gates.json configuration:

mkdir -p .claude
cat > .claude/gates.json << 'EOF'
{
  "gates": {
    "check": {"command": "npm run lint", "on_fail": "BLOCK"},
    "test": {"command": "npm test", "on_fail": "BLOCK"}
  },
  "hooks": {
    "PostToolUse": {
      "enabled_tools": ["Edit", "Write"],
      "gates": ["check"]
    }
  }
}
EOF

See SETUP.md for detailed gate configuration.

How It Works

Hook Event → Context Injection (AUTOMATIC) → [OPTIONAL: gates.json Gates] → Action
                 ↓                                        ↓
          .claude/context/                         Quality checks
          plugin/context/                          Custom commands
          (zero config!)                           (requires gates.json)
  1. Context Injection (AUTOMATIC): Always runs first, discovers .claude/context/{name}-{stage}.md files
  2. Gate Execution (OPTIONAL): If gates.json configured, runs quality checks/custom commands
  3. Action Handling: CONTINUE, BLOCK, STOP, or chain to another gate

Context injection works standalone - gates.json is only for optional quality enforcement.

See ARCHITECTURE.md for detailed system design.

Supported Hook Events

All 12 Claude Code hook types are supported:

Event Context Pattern Default Behavior
SessionStart session-start.md Plugin injects agent selection guide
SessionEnd session-end.md -
UserPromptSubmit prompt-submit.md Keyword-triggered gates (check, test, build)
SlashCommandStart {command}-start.md -
SlashCommandEnd {command}-end.md -
SkillStart {skill}-start.md -
SkillEnd {skill}-end.md -
SubagentStop {agent}-end.md -
PreToolUse {tool}-pre.md -
PostToolUse {tool}-post.md -
Stop agent-stop.md -
Notification notification-receive.md -

Context Injection

Zero-config content injection via file naming convention.

Naming Convention

Pattern: .claude/context/{name}-{stage}.md

Examples:
  /code-review starts  → .claude/context/code-review-start.md
  /plan starts         → .claude/context/plan-start.md
  TDD skill loads      → .claude/context/test-driven-development-start.md
  SessionStart fires   → .claude/context/session-start.md

Priority Order

  1. Project context (.claude/context/) - highest priority
  2. Plugin context (${CLAUDE_PLUGIN_ROOT}/context/) - fallback defaults

Projects can override any plugin-provided context by creating their own file.

Complete Zero-Config Example

Step 1: Create context file

mkdir -p .claude/context
cat > .claude/context/code-review-start.md << 'EOF'
## Security Checklist

- [ ] Authentication on all endpoints
- [ ] Input validation for user data
- [ ] No secrets in logs
- [ ] HTTPS only
- [ ] Rate limiting configured
EOF

Step 2: Run the command

/code-review src/api/users.ts

Step 3: Context auto-injects

The security checklist appears in the conversation automatically. No configuration files needed!

This works with ANY slash command, skill, or agent. Just follow the naming pattern: .claude/context/{name}-{stage}.md

Hook-to-File Mapping

Hook Type File Pattern Example
SessionStart session-start.md Session begins
UserPromptSubmit prompt-submit.md User sends message
SlashCommandStart {command}-start.md /code-review-start.md
SkillStart {skill}-start.md test-driven-development-start.md
SubagentStop {agent}-end.md rust-agent-end.md
PreToolUse {tool}-pre.md Edit-pre.md

See CONVENTIONS.md for full documentation.

Gate Configuration (Optional)

Most users only need context files. Gates are for optional quality enforcement and custom commands.

Gates are defined in gates.json and can be:

Plugin Gate References

Reference gates defined in other plugins:

{
  "gates": {
    "plan-compliance": {
      "plugin": "cipherpowers",
      "gate": "plan-compliance"
    },
    "check": {
      "command": "npm run lint"
    }
  },
  "hooks": {
    "SubagentStop": {
      "gates": ["plan-compliance", "check"]
    }
  }
}

The plugin field uses sibling convention - assumes plugins are installed in the same directory (e.g., ~/.claude/plugins/). The gate's command runs in the plugin's directory context.

Shell Command Gates

{
  "gates": {
    "check": {
      "command": "npm run lint",
      "on_pass": "CONTINUE",
      "on_fail": "BLOCK"
    }
  }
}

TypeScript Gates

Gates without command field are TypeScript modules in src/gates/:

{
  "gates": {
    "plugin-path": {
      "description": "Verify plugin path resolution in subagents",
      "on_pass": "CONTINUE",
      "on_fail": "CONTINUE"
    }
  }
}

See TYPESCRIPT.md for creating TypeScript gates.

Keyword-Triggered Gates

Gates can define keywords to only run when the user message contains matching terms:

{
  "gates": {
    "test": {
      "description": "Run project test suite",
      "keywords": ["test", "testing", "spec", "verify"],
      "command": "npm test",
      "on_pass": "CONTINUE",
      "on_fail": "BLOCK"
    }
  },
  "hooks": {
    "UserPromptSubmit": {
      "gates": ["test"]
    }
  }
}

Behavior:

  • Gates with keywords only run if any keyword is found in the user message
  • Gates without keywords always run (backwards compatible)
  • Keyword matching is case-insensitive

Agent Filtering for SubagentStop

Important: Without enabled_agents, SubagentStop triggers for ALL agents - including verification-only agents that don't modify code.

{
  "hooks": {
    "SubagentStop": {
      "enabled_agents": ["rust-agent", "code-agent", "commit-agent"],
      "gates": ["check", "test"]
    }
  }
}

Why this matters:

  • Verification agents (technical-writer in VERIFICATION mode, research-agent) only read files
  • Running check and test gates after read-only verification is unnecessary
  • Gate failures for verification agents confuse the workflow (false positives)

Recommended pattern: Only include agents that modify code:

  • rust-agent, code-agent - write/edit code
  • commit-agent - makes git commits
  • Exclude: technical-writer (verification mode), research-agent, plan-review-agent

Note: enabled_tools works the same way for PostToolUse hooks.

Configuration Merging

The system merges plugin and project configurations:

plugin/hooks/gates.json     (defaults)
        ↓ merged with
.claude/gates.json          (project overrides)
        ↓
Merged Configuration        (project takes precedence)

Plugin provides defaults. Projects override what they need.

Debugging

Logs are written to $TMPDIR/turboshovel/hooks-YYYY-MM-DD.log:

# View logs in real-time
tail -f $(node ${CLAUDE_PLUGIN_ROOT}/hooks/hooks-app/dist/cli.js log-path)

# Or find the log file
ls $TMPDIR/turboshovel/hooks-*.log

What gets logged:

  • Hook event received
  • Config files loaded
  • Context files discovered
  • Gates executed
  • Actions taken

Documentation

Examples

See examples/ for ready-to-use configurations:

  • strict.json - Block on all failures
  • permissive.json - Warn only
  • pipeline.json - Gate chaining
  • context/ - Example context files