Skip to content

Hooks Reference

Z-M-Huang edited this page Apr 10, 2026 · 13 revisions

Hooks Reference

VCP registers 4 hooks. Dev Buddy hooks were removed in v0.5.0 (see Dev Buddy Hooks below). All hooks are TypeScript files executed by Bun.

security-context.ts

Layer 1: Proactive context injection at session start.

Property Value
Event SessionStart
Trigger Every session start (new, resume, clear, compact)
Timeout 15 seconds
Exit code Always 0 (never blocks)
Command bun ${CLAUDE_PLUGIN_ROOT}/hooks/security-context.ts

Behavior

  1. Reads CLAUDE_PROJECT_DIR environment variable (falls back to cwd())
  2. Calls generateContext(projectRoot) from vcp-context-core.ts, which loads global config (~/.vcp/config.json) to resolve the standards URL and applies global defaults
  3. Outputs formatted VCP rule summaries to stdout
  4. On any error (network failure, missing config), outputs a fallback message

Fallback Message

If the manifest cannot be fetched or any error occurs:

VCP active but not fully initialized. Run /vcp-init to configure, then /vcp-audit before committing.

If no global config exists, outputs:

VCP active but not initialized. Run /vcp-init to configure.

Known Issues

Upstream Claude Code bugs may prevent the hook's output from being injected into the model context. See #10373, #11509, #12151. Use /vcp-context as a manual fallback.


security-gate.ts

Layer 3: Real-time blocking of dangerous code patterns.

Property Value
Event PreToolUse
Trigger Write|Edit and Bash (two separate matchers)
Timeout 10 seconds
Exit code 0 = allow, 2 = block
Command bun ${CLAUDE_PLUGIN_ROOT}/hooks/security-gate.ts

Input

Reads JSON from stdin with the structure:

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.ts",
    "content": "..."
  }
}

For Edit: checks old_string and new_string fields. For Bash: checks the command field. Bash-specific patterns (CWE-95 shell eval, CWE-116 obfuscation) only apply to Bash tool calls.

Output

When blocking (exit 2): Writes to stderr (fed to Claude as error message):

VCP Security Gate — BLOCKED:
  CWE-798: Hardcoded secret detected. Use environment variables or a secret manager.

When suppressing (exit 0): Outputs JSON to stdout (shown to user via systemMessage):

{"systemMessage":"VCP Security Gate — WARNING: Suppressed 1 finding(s) via .vcp/config.json ignore (CWE-798)."}

CWE Ignore

Reads both ~/.vcp/config.json (global) and .vcp/config.json (project) from CLAUDE_PROJECT_DIR. Merges ignore arrays from both configs (union). Filters patterns by CWE entries in the merged ignore list. For example, "CWE-798" in either config's ignore list disables all hardcoded secret patterns. Suppressed findings emit a warning via stdout JSON so the user is aware.

The hook gracefully handles missing global config — if ~/.vcp/config.json doesn't exist, only project ignores are used.

Patterns

See Security Gate Patterns for the complete reference of all 21 patterns.


test-quality-warning.ts

Warns when AI-generated test code contains mock-abuse patterns.

Property Value
Event PostToolUse
Trigger Write|Edit (test files only)
Timeout 10 seconds
Exit code Always 0 (never blocks — informational only)
Command bun ${CLAUDE_PLUGIN_ROOT}/hooks/test-quality-warning.ts

Input

Reads JSON from stdin with the structure:

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/test_file.py",
    "content": "..."
  }
}

Only processes Write and Edit tool calls where the file path matches test file patterns (*.test.*, *.spec.*, test_*.*, __tests__/, *_test.go, *Test.java, *_test.rb, *_spec.rb, *_test.rs). Non-test files are silently skipped.

Patterns

The hook detects 3 mock-abuse patterns:

  1. Excessive mocking — More than 3 mock/stub setup calls (mock(), jest.fn(), vi.fn(), patch(), stub(), spyOn(), .mockReturnValue(), .mockResolvedValue(), .mockImplementation(), .return_value =, sinon.stub/mock/fake). Maps to core-testing Rule 4.

  2. Mock-only assertions — Test contains mock assertions (.assert_called, .toHaveBeenCalled, .calledWith, etc.) but zero value assertions (.toEqual, .toBe, assert, assertEqual, etc.). Maps to core-testing Rule 5.

  3. Tautological mock assertions — Test asserts that a mock returns exactly what it was configured to return (e.g., .mockReturnValue(42) followed by .toEqual(42)). Maps to core-testing Rule 3.

Output

Outputs warnings as JSON to stdout (shown to user via systemMessage):

{"systemMessage":"VCP Test Quality Warning — 2 issue(s) in tests/test_payments.py:\n  - Excessive mocking: 7 mock setup calls detected. VCP standard core-testing Rule 4: \"Mock external services, not internal logic.\" Consider reducing mocks to external boundaries only.\n  - Mock-only assertions: 3 mock assertion(s) found but no value assertions. VCP standard core-testing Rule 5: \"When you mock, verify the contract — not the call.\" Add assertions on return values or state changes."}

Notes

  • PostToolUse hooks signal issues via JSON stdout with systemMessage — this is how Claude Code surfaces hook output to the user
  • Warnings are informational; the user can choose to address them or not
  • The hook runs quickly (regex-based, no I/O beyond stdin)

stop-reminder.ts

Reminder to run VCP checks before committing.

Property Value
Event Stop
Trigger When the AI finishes a task
Timeout None (fast, no I/O)
Exit code Always 0
Command bun ${CLAUDE_PLUGIN_ROOT}/hooks/stop-reminder.ts

Output

Outputs JSON to stdout (shown to user via systemMessage):

{"systemMessage":"Reminder: Run /vcp-audit, /vcp-review-tests, or /vcp-pre-commit-review before committing."}


Dev Buddy Hooks (v0.5.0+)

As of v0.5.0, Dev Buddy's hooks.json is empty ({"hooks": {}}). The three hooks previously registered — ralph-stage-gate.ts, dispatch-gate.ts, and uat-completion-gate.ts — have been removed.

What replaced them:

  • Stage sequencing — Enforced by the ralph state machine (ralph-state-machine.ts). The computeNextAction() function returns the correct skill for the current status. Review-gated statuses (discover-review, requirements-review, decompose-review) emit user_checkpoint actions that require user approval before advancing.
  • Dispatch proof — No longer needed. Stage skills now call stage-runner.ts which handles executor dispatch as a subprocess pipeline, not prompt-level instructions.
  • UAT completion — Enforced by checkPreconditions() in the state machine, which verifies UAT results before allowing the done transition.

If migrating from v0.4.x, no action is needed — the hooks are simply gone. The state machine provides equivalent enforcement with fewer moving parts.


Diagnostic Log

When debug is enabled in the global config (~/.vcp/config.json), all hooks write diagnostic entries to .vcp/vcp.log in the project root. This log records what each hook checked and decided, useful for verifying hooks are running and diagnosing issues. By default, debug is false and no log file is generated.

To enable: /vcp-config global set debug true or set "debug": true in ~/.vcp/config.json.

Log Format

2026-02-16T10:30:45.123Z [SessionStart] security-context: info — Generated context (9876 chars)\n--- BEGIN CONTEXT ---\n## VCP Standards Context\n...\n--- END CONTEXT ---
2026-02-16T10:31:02.456Z [PreToolUse] security-gate: allow — No findings
2026-02-16T10:31:05.789Z [PreToolUse] security-gate: block — CWE-798
2026-02-16T10:31:10.012Z [PreToolUse] security-gate: warn — Suppressed 1 finding(s) (CWE-798)
2026-02-16T10:32:00.345Z [PostToolUse] test-quality-warning: warn — 2 issue(s) in /src/foo.test.ts
2026-02-16T10:35:00.890Z [Stop] stop-reminder: info — Reminder shown

Each line: ISO_TIMESTAMP [event] source: decision — details

Notes

  • .vcp/*.log should be added to .gitignore (VCP's own .gitignore already includes it)
  • Logging is best-effort — if the project root is unavailable or not an absolute path, logging is silently skipped
  • The logger is shared by hooks and lib modules via vcp-logger.ts

hooks.json Structure (VCP)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": "bun ${CLAUDE_PLUGIN_ROOT}/hooks/security-gate.ts", "timeout": 10 }]
      },
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "bun ${CLAUDE_PLUGIN_ROOT}/hooks/security-gate.ts", "timeout": 10 }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": "bun ${CLAUDE_PLUGIN_ROOT}/hooks/test-quality-warning.ts", "timeout": 10 }]
      }
    ],
    "SessionStart": [
      {
        "hooks": [{ "type": "command", "command": "bun ${CLAUDE_PLUGIN_ROOT}/hooks/security-context.ts", "timeout": 15 }]
      }
    ],
    "Stop": [
      {
        "hooks": [{ "type": "command", "command": "bun ${CLAUDE_PLUGIN_ROOT}/hooks/stop-reminder.ts" }]
      }
    ]
  }
}

Environment Variables

Variable Available To Description
CLAUDE_PLUGIN_ROOT hooks.json command templates Resolves to the plugin directory at registration time
CLAUDE_PROJECT_DIR Hook scripts at runtime The user's project root directory

Note: CLAUDE_PLUGIN_ROOT is a template variable expanded in hooks.json, not an environment variable available to scripts at runtime. Skills use pluginRoot from .vcp/config.json instead.

Clone this wiki locally