@mauribadnights/clooks 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,8 @@ Persistent hook runtime for Claude Code. Eliminate cold starts. Get observabilit
5
5
  [![npm](https://img.shields.io/npm/v/@mauribadnights/clooks)](https://www.npmjs.com/package/@mauribadnights/clooks)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
 
8
+ **[Documentation](https://mauribadnights.github.io/clooks/)**
9
+
8
10
  ## Performance
9
11
 
10
12
  | Metric | Without clooks | With clooks | Improvement |
package/dist/cli.js CHANGED
File without changes
@@ -1,4 +1,6 @@
1
1
  import type { HandlerConfig, HandlerResult, HandlerState, HookEvent, HookInput, PrefetchContext } from './types.js';
2
+ /** Reset cached shell env (for testing) */
3
+ export declare function resetShellEnv(): void;
2
4
  /** Match handler agent field against current session agent (case-insensitive, comma-separated) */
3
5
  declare function matchAgent(pattern: string, currentAgent: string): boolean;
4
6
  /** Match handler project field against cwd path */
package/dist/handlers.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  // clooks hook handlers — execution engine
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.resetShellEnv = resetShellEnv;
4
5
  exports.matchAgent = matchAgent;
5
6
  exports.matchProject = matchProject;
6
7
  exports.resetHandlerStates = resetHandlerStates;
@@ -17,6 +18,47 @@ const constants_js_1 = require("./constants.js");
17
18
  const filter_js_1 = require("./filter.js");
18
19
  const llm_js_1 = require("./llm.js");
19
20
  const deps_js_1 = require("./deps.js");
21
+ /**
22
+ * Resolve the user's login shell PATH once at startup.
23
+ * When the daemon runs under launchd/systemd, it inherits a minimal PATH
24
+ * that may not include /opt/homebrew/bin, pyenv shims, nvm dirs, etc.
25
+ * This ensures script handlers see the same PATH as the user's terminal.
26
+ */
27
+ let _shellEnv = null;
28
+ function getShellEnv() {
29
+ if (_shellEnv)
30
+ return _shellEnv;
31
+ try {
32
+ const shell = process.env.SHELL || '/bin/sh';
33
+ const output = (0, child_process_1.execSync)(`${shell} -ilc 'env'`, {
34
+ timeout: 5000,
35
+ encoding: 'utf8',
36
+ stdio: ['pipe', 'pipe', 'pipe'],
37
+ });
38
+ const env = {};
39
+ for (const line of output.split('\n')) {
40
+ const idx = line.indexOf('=');
41
+ if (idx > 0) {
42
+ env[line.slice(0, idx)] = line.slice(idx + 1);
43
+ }
44
+ }
45
+ // Merge: shell env as base, but keep daemon-specific vars (like ANTHROPIC_API_KEY)
46
+ _shellEnv = { ...env, ...process.env };
47
+ // PATH specifically: prefer the shell's PATH (has homebrew, pyenv, nvm, etc.)
48
+ if (env.PATH) {
49
+ _shellEnv.PATH = env.PATH;
50
+ }
51
+ }
52
+ catch {
53
+ // Fallback: just use process.env as-is
54
+ _shellEnv = process.env;
55
+ }
56
+ return _shellEnv;
57
+ }
58
+ /** Reset cached shell env (for testing) */
59
+ function resetShellEnv() {
60
+ _shellEnv = null;
61
+ }
20
62
  /** Match handler agent field against current session agent (case-insensitive, comma-separated) */
21
63
  function matchAgent(pattern, currentAgent) {
22
64
  const agents = pattern.split(',').map(a => a.trim().toLowerCase());
@@ -313,6 +355,7 @@ function executeScriptHandler(handler, input) {
313
355
  const child = (0, child_process_1.spawn)('sh', ['-c', h.command], {
314
356
  stdio: ['pipe', 'pipe', 'pipe'],
315
357
  timeout,
358
+ env: getShellEnv(),
316
359
  });
317
360
  let stdout = '';
318
361
  let stderr = '';
package/docs/.nojekyll ADDED
File without changes
@@ -0,0 +1,32 @@
1
+ - **Getting Started**
2
+ - [Installation](getting-started/installation.md)
3
+ - [Quickstart](getting-started/quickstart.md)
4
+ - [Migration](getting-started/migration.md)
5
+
6
+ - **Guides**
7
+ - [Manifest](guides/manifest.md)
8
+ - [Handlers](guides/handlers.md)
9
+ - [LLM Handlers](guides/llm-handlers.md)
10
+ - [Filtering](guides/filtering.md)
11
+ - [Dependencies](guides/dependencies.md)
12
+ - [Async Handlers](guides/async-handlers.md)
13
+ - [Short-Circuit](guides/short-circuit.md)
14
+ - [System Service](guides/system-service.md)
15
+
16
+ - **Plugins**
17
+ - [Using Plugins](plugins/using-plugins.md)
18
+ - [Creating Plugins](plugins/creating-plugins.md)
19
+ - [CC Plugin Import](plugins/cc-plugin-import.md)
20
+
21
+ - **Reference**
22
+ - [CLI](reference/cli.md)
23
+ - [Hook Events](reference/hook-events.md)
24
+ - [HTTP API](reference/http-api.md)
25
+ - [Config Files](reference/config-files.md)
26
+ - [Types](reference/types.md)
27
+
28
+ - **Operations**
29
+ - [Monitoring](operations/monitoring.md)
30
+ - [Security](operations/security.md)
31
+ - [Troubleshooting](operations/troubleshooting.md)
32
+ - [Architecture](operations/architecture.md)
@@ -0,0 +1,52 @@
1
+ # Installation
2
+
3
+ ## Prerequisites
4
+
5
+ - **Node.js 18+** -- check with `node --version`
6
+ - **Claude Code** -- installed and working (`claude --version`)
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install -g @mauribadnights/clooks
12
+ ```
13
+
14
+ ## Initialize
15
+
16
+ ```bash
17
+ clooks init
18
+ ```
19
+
20
+ This creates your configuration directory and everything clooks needs to run. No existing hooks are modified -- use `clooks migrate` for that (see [Migration](migration.md)).
21
+
22
+ ## Verify
23
+
24
+ ```bash
25
+ clooks doctor
26
+ ```
27
+
28
+ Doctor runs health checks on the daemon, port, manifest, settings, and handler state. A passing report means clooks is ready.
29
+
30
+ ## What `clooks init` creates
31
+
32
+ | Item | Path | Description |
33
+ |------|------|-------------|
34
+ | Manifest | `~/.clooks/manifest.yaml` | Handler definitions, settings, and prefetch config |
35
+ | Hooks directory | `~/.clooks/hooks/` | Built-in hook scripts |
36
+ | Auth token | Stored in manifest | Generated once and shown at init. Used to authenticate requests to the daemon. |
37
+ | System service | launchd (macOS) / systemd (Linux) | Auto-starts the daemon on login, restarts on crash |
38
+ | Expert agent | `~/.claude/agents/clooks.md` | Invoke with `claude --agent clooks` for clooks-specific help |
39
+
40
+ > **Note:** The auth token is displayed once during init. It is stored in your manifest under `settings.authToken`. If you lose it, run `clooks rotate-token` to generate a new one.
41
+
42
+ ## Next steps
43
+
44
+ Start the daemon and add your first handler:
45
+
46
+ ```bash
47
+ clooks start
48
+ ```
49
+
50
+ ---
51
+
52
+ [Home](../index.md) | Next: [Quickstart](quickstart.md)
@@ -0,0 +1,68 @@
1
+ # Migration
2
+
3
+ Migrate existing Claude Code command hooks to clooks in one command.
4
+
5
+ ## What migration does
6
+
7
+ `clooks migrate` converts your `settings.json` command hooks into clooks HTTP hooks backed by the daemon, with equivalent handlers defined in the manifest. Your original command hooks continue to work -- they just route through clooks instead of spawning processes directly.
8
+
9
+ ## Run the migration
10
+
11
+ ```bash
12
+ clooks migrate
13
+ ```
14
+
15
+ ## Step-by-step breakdown
16
+
17
+ When you run `clooks migrate`, the following happens in order:
18
+
19
+ 1. **Reads settings** -- Loads `~/.claude/settings.json` (or `settings.local.json` if present).
20
+ 2. **Backs up original** -- Saves a copy to `~/.clooks/settings.backup.json`.
21
+ 3. **Extracts command hooks** -- Parses all command hooks from the settings file and creates corresponding handlers in `~/.clooks/manifest.yaml`.
22
+ 4. **Rewrites settings** -- Replaces command hooks with HTTP hooks pointing to `http://localhost:7890`.
23
+ 5. **Adds ensure-running** -- Injects a `clooks ensure-running` command into `SessionStart` so the daemon auto-starts when Claude Code launches.
24
+ 6. **Imports plugin hooks** -- Detects and imports any Claude Code plugin hooks into the manifest.
25
+ 7. **Installs system service** -- Sets up launchd (macOS) or systemd (Linux) for auto-start on login and crash recovery.
26
+
27
+ ## Verify
28
+
29
+ ```bash
30
+ clooks doctor
31
+ ```
32
+
33
+ A passing report confirms that the daemon is running, the manifest is valid, and `settings.json` points to clooks.
34
+
35
+ ## Rollback
36
+
37
+ If anything goes wrong, restore your original settings:
38
+
39
+ ```bash
40
+ clooks restore
41
+ ```
42
+
43
+ This replaces `settings.json` with the backup created during migration. Your command hooks return to their original state.
44
+
45
+ ## Keeping settings in sync
46
+
47
+ After migration, if you add new handlers to the manifest, run:
48
+
49
+ ```bash
50
+ clooks sync
51
+ ```
52
+
53
+ This updates `settings.json` to include HTTP hook entries for any new events in your manifest. You do not need to edit `settings.json` manually.
54
+
55
+ > **Note:** `clooks sync` only adds missing entries. It never removes or modifies existing hooks in `settings.json`.
56
+
57
+ ## Summary
58
+
59
+ | Command | What it does |
60
+ |---------|-------------|
61
+ | `clooks migrate` | Full migration: backup, convert, rewrite, install service |
62
+ | `clooks restore` | Rollback to pre-migration settings.json |
63
+ | `clooks sync` | Add missing HTTP hook entries for new manifest events |
64
+ | `clooks doctor` | Verify everything is wired up correctly |
65
+
66
+ ---
67
+
68
+ [Home](../index.md) | Prev: [Quickstart](quickstart.md) | Next: [Manifest Guide](../guides/manifest.md)
@@ -0,0 +1,76 @@
1
+ # Quickstart
2
+
3
+ Get your first clooks handler running in 5 minutes.
4
+
5
+ ## 1. Install
6
+
7
+ ```bash
8
+ npm install -g @mauribadnights/clooks
9
+ ```
10
+
11
+ ## 2. Initialize
12
+
13
+ ```bash
14
+ clooks init
15
+ ```
16
+
17
+ ## 3. Start the daemon
18
+
19
+ ```bash
20
+ clooks start
21
+ ```
22
+
23
+ ## 4. Check status
24
+
25
+ ```bash
26
+ clooks status
27
+ ```
28
+
29
+ You should see the daemon running on port 7890 with zero handlers loaded.
30
+
31
+ ## 5. Add a handler
32
+
33
+ Open `~/.clooks/manifest.yaml` in your editor and add a handler under `PreToolUse`:
34
+
35
+ ```yaml
36
+ handlers:
37
+ PreToolUse:
38
+ - id: bash-logger
39
+ type: script
40
+ command: "echo '{\"additionalContext\": \"Reviewed by clooks\"}'"
41
+ filter: "Bash"
42
+ ```
43
+
44
+ This handler fires before every Bash tool call and injects a note into Claude's context.
45
+
46
+ ## 6. Hot-reload
47
+
48
+ Save the file. The daemon watches `manifest.yaml` and hot-reloads on change -- no restart needed.
49
+
50
+ You can confirm the reload in the daemon log:
51
+
52
+ ```bash
53
+ tail -1 ~/.clooks/daemon.log
54
+ ```
55
+
56
+ ## 7. Try it
57
+
58
+ Open Claude Code and run any Bash command. The handler fires automatically. You will see "Reviewed by clooks" appear in the context.
59
+
60
+ ## 8. Check metrics
61
+
62
+ ```bash
63
+ clooks stats
64
+ ```
65
+
66
+ This launches an interactive TUI showing execution counts, latency, and errors per event. Use `-t` for plain text output.
67
+
68
+ ## What to try next
69
+
70
+ - Add a `filter` to scope handlers to specific tools
71
+ - Try an `llm` handler for AI-powered review
72
+ - Run `clooks migrate` to convert existing command hooks
73
+
74
+ ---
75
+
76
+ [Home](../index.md) | Prev: [Installation](installation.md) | Next: [Migration](migration.md)
@@ -0,0 +1,42 @@
1
+ # Async Handlers
2
+
3
+ ## Overview
4
+
5
+ Set `async: true` on any handler to execute it without blocking Claude Code's response. The handler runs in the background; its output is NOT included in the hook response.
6
+
7
+ ## Use Cases
8
+
9
+ - Logging and analytics
10
+ - Session tracking
11
+ - Background notifications
12
+ - Non-critical metric collection
13
+
14
+ ## Configuration
15
+
16
+ ```yaml
17
+ handlers:
18
+ UserPromptSubmit:
19
+ - id: prompt-analytics
20
+ type: inline
21
+ module: ~/hooks/analytics.js
22
+ async: true
23
+ ```
24
+
25
+ ## Behavior
26
+
27
+ - Fires immediately, does not await completion
28
+ - Errors are swallowed (logged to `daemon.log` but don't affect response)
29
+ - Results delivered via internal `onAsyncResult` callback
30
+ - Metrics still recorded for async handlers
31
+
32
+ ## Limitations
33
+
34
+ - Output NOT included in hook response to Claude Code
35
+ - If `depends` is set on an async handler, it is forced synchronous (with warning)
36
+ - Cannot be depended upon by other handlers
37
+
38
+ > **Note:** Async handlers are ideal for side effects that should never slow down the user experience. If you need the handler's output to influence Claude's behavior, use a synchronous handler instead.
39
+
40
+ ---
41
+
42
+ [Home](../index.md) | [Prev: Dependencies](dependencies.md) | [Next: Short-Circuit](short-circuit.md)
@@ -0,0 +1,153 @@
1
+ # Dependencies
2
+
3
+ Handlers can declare dependencies on other handlers using the `depends` field. clooks resolves dependencies into execution "waves" using topological sort (Kahn's algorithm), running independent handlers in parallel while respecting ordering constraints.
4
+
5
+ ## Overview
6
+
7
+ Without dependencies, all handlers for an event run in parallel. With dependencies, handlers are grouped into sequential waves:
8
+
9
+ - **Wave 0:** Handlers with no dependencies.
10
+ - **Wave 1:** Handlers whose dependencies are all in wave 0.
11
+ - **Wave N:** Handlers whose dependencies are all in waves 0 through N-1.
12
+
13
+ Handlers within the same wave run in parallel. Waves execute sequentially.
14
+
15
+ ## How It Works
16
+
17
+ 1. The dependency graph is built from `depends` fields across all eligible handlers for the event.
18
+ 2. Handlers are sorted into waves using Kahn's algorithm (BFS topological sort).
19
+ 3. Wave 0 executes first. All handlers in wave 0 run in parallel.
20
+ 4. When wave 0 completes, wave 1 starts. Its handlers can access outputs from wave 0.
21
+ 5. This continues until all waves have executed.
22
+
23
+ ## Example
24
+
25
+ ```yaml
26
+ handlers:
27
+ PreToolUse:
28
+ - id: context-loader
29
+ type: inline
30
+ module: ~/hooks/context.js
31
+ # No depends -- Wave 0
32
+
33
+ - id: security-check
34
+ type: llm
35
+ model: claude-haiku-4-5
36
+ prompt: "Check security of $TOOL_NAME with args: $ARGUMENTS"
37
+ # No depends -- Wave 0 (parallel with context-loader)
38
+
39
+ - id: deep-review
40
+ type: llm
41
+ model: claude-sonnet-4-6
42
+ prompt: "Perform deep review with full context: $ARGUMENTS"
43
+ depends: [context-loader, security-check]
44
+ # Both deps in Wave 0 -- this runs in Wave 1
45
+ ```
46
+
47
+ Execution order:
48
+
49
+ ```
50
+ Wave 0: context-loader + security-check (parallel)
51
+ |
52
+ v
53
+ Wave 1: deep-review (after both wave 0 handlers complete)
54
+ ```
55
+
56
+ ## Accessing Dependency Outputs
57
+
58
+ Handlers in wave N receive outputs from all previous waves via the `_handlerOutputs` field injected into their input:
59
+
60
+ ```json
61
+ {
62
+ "session_id": "...",
63
+ "cwd": "...",
64
+ "hook_event_name": "PreToolUse",
65
+ "tool_name": "Write",
66
+ "_handlerOutputs": {
67
+ "context-loader": {
68
+ "additionalContext": "Loaded project context..."
69
+ },
70
+ "security-check": {
71
+ "additionalContext": "No security issues found."
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ For inline handlers, access it directly from the input object:
78
+
79
+ ```javascript
80
+ export default async function(input) {
81
+ const priorResults = input._handlerOutputs || {};
82
+ const securityResult = priorResults['security-check'];
83
+
84
+ // Use prior results to inform this handler's logic
85
+ if (securityResult?.additionalContext?.includes('issue')) {
86
+ return { decision: 'block', reason: 'Security issue detected upstream' };
87
+ }
88
+
89
+ return { additionalContext: 'All clear after deep review.' };
90
+ }
91
+ ```
92
+
93
+ For LLM handlers, dependency outputs are available in the input but not directly interpolable into prompt templates. Use an inline handler as a dependency to prepare context that downstream LLM handlers can consume.
94
+
95
+ ## Cycle Detection
96
+
97
+ Circular dependencies are detected at execution time. If a cycle exists, clooks throws an error identifying the affected handler IDs:
98
+
99
+ ```
100
+ Error: Dependency cycle detected among handlers: handler-a, handler-b
101
+ ```
102
+
103
+ The daemon logs the error and skips all handlers involved in the cycle. Other handlers in the same event that are not part of the cycle execute normally.
104
+
105
+ ## Cross-Plugin Dependencies
106
+
107
+ Plugin handlers are namespaced as `pluginName/handlerId`. Dependency references follow these rules:
108
+
109
+ | Reference Style | Resolves To |
110
+ |-----------------|-------------|
111
+ | `depends: [other-handler]` | Same-plugin handler (auto-namespaced) |
112
+ | `depends: [other-plugin/handler-id]` | Handler from a different plugin |
113
+ | `depends: [user-handler-id]` | Handler defined in the user manifest |
114
+
115
+ Example with a plugin handler depending on a user-defined handler:
116
+
117
+ ```yaml
118
+ # In clooks-plugin.yaml (plugin: my-plugin)
119
+ handlers:
120
+ PreToolUse:
121
+ - id: plugin-review
122
+ type: llm
123
+ model: claude-haiku-4-5
124
+ prompt: "Review after context load: $ARGUMENTS"
125
+ depends: [context-loader] # References user manifest handler
126
+ ```
127
+
128
+ The fully qualified ID of this handler is `my-plugin/plugin-review`. Other plugins or user handlers can depend on it using that full name.
129
+
130
+ ## Async and Dependencies
131
+
132
+ Async handlers (`async: true`) that participate in dependency relationships are forced to run synchronously. This applies when:
133
+
134
+ - An async handler has `depends` referencing other handlers in the same event.
135
+ - Other handlers in the same event list an async handler in their `depends`.
136
+
137
+ In both cases, clooks logs a warning and runs the handler synchronously:
138
+
139
+ ```
140
+ [clooks] Warning: async handler "my-handler" has dependency relationships, running synchronously
141
+ ```
142
+
143
+ This is because fire-and-forget execution cannot guarantee dependency ordering. If you need a handler to be truly async, remove it from all dependency chains.
144
+
145
+ ## Dependencies and Filtering
146
+
147
+ Dependencies are resolved after filtering. If a handler's dependency is filtered out (by keyword filter, agent, or project scope), the dependency is treated as satisfied. The dependent handler will not find that dependency's output in `_handlerOutputs`, but it will not be blocked waiting for it.
148
+
149
+ Only dependencies referencing handlers within the current event's eligible set are considered. References to unknown handler IDs are silently ignored.
150
+
151
+ ---
152
+
153
+ [Home](../index.md) | [Prev: Filtering](filtering.md) | [Next: Async Handlers](async-handlers.md)
@@ -0,0 +1,153 @@
1
+ # Filtering
2
+
3
+ Handlers can be scoped to fire only under specific conditions. clooks provides three filtering mechanisms: keyword filters, agent scoping, and project scoping. All filters are AND'd together -- a handler only fires if every applicable filter passes.
4
+
5
+ ## Keyword Filters
6
+
7
+ The `filter` field applies a keyword match against the JSON-stringified hook input. Matching is case-insensitive.
8
+
9
+ ### Syntax
10
+
11
+ | Pattern | Meaning |
12
+ |---------|---------|
13
+ | `"word1\|word2"` | Match if ANY keyword is found (OR) |
14
+ | `"!word"` | Exclude if keyword is found (NOT) |
15
+ | `"word1\|!word2"` | Match if word1 is present AND word2 is absent |
16
+
17
+ The filter string is split on `|`. Each term is classified as positive (no prefix) or negative (`!` prefix). The rules are:
18
+
19
+ 1. If ANY negative term is found in the input, the handler is **blocked**.
20
+ 2. If there are positive terms, at least ONE must be found for the handler to **fire**.
21
+ 3. If there are only negative terms and none matched, the handler **fires**.
22
+
23
+ ### Examples
24
+
25
+ **Fire only for Bash or Execute tools:**
26
+
27
+ ```yaml
28
+ - id: bash-guard
29
+ type: script
30
+ command: "node ~/hooks/guard.js"
31
+ filter: "Bash|Execute"
32
+ ```
33
+
34
+ **Fire for everything except Read and Glob:**
35
+
36
+ ```yaml
37
+ - id: write-logger
38
+ type: inline
39
+ module: ~/hooks/logger.js
40
+ filter: "!Read|!Glob"
41
+ ```
42
+
43
+ This works because both `Read` and `Glob` are negative terms. The handler fires whenever neither term appears in the input.
44
+
45
+ **Fire for Write unless "test" appears in the input:**
46
+
47
+ ```yaml
48
+ - id: write-review
49
+ type: llm
50
+ model: claude-haiku-4-5
51
+ prompt: "Review: $ARGUMENTS"
52
+ filter: "Write|!test"
53
+ ```
54
+
55
+ Here `Write` is a positive term and `test` is negative. The handler fires when the input contains "Write" but does not contain "test".
56
+
57
+ > **Note:** The filter matches against the entire JSON-stringified hook input, not just the tool name. This means field values, file paths, and argument content are all searchable.
58
+
59
+ ## Agent Scoping
60
+
61
+ The `agent` field restricts a handler to specific Claude Code agent sessions.
62
+
63
+ ### Syntax
64
+
65
+ A comma-separated list of agent names (case-insensitive). The handler only fires when the current session's agent matches one of the listed names.
66
+
67
+ ```yaml
68
+ - id: builder-guard
69
+ type: script
70
+ command: "node ~/hooks/builder-guard.js"
71
+ agent: "builder"
72
+ ```
73
+
74
+ ```yaml
75
+ - id: multi-agent-hook
76
+ type: inline
77
+ module: ~/hooks/shared.js
78
+ agent: "builder,coo"
79
+ ```
80
+
81
+ ### How Agent Detection Works
82
+
83
+ The agent name is extracted from the `agent_type` field of the `SessionStart` event payload. clooks caches this per `session_id`. Subsequent events in the same session use the cached value.
84
+
85
+ If `agent` is omitted from a handler, it fires in all sessions regardless of agent type.
86
+
87
+ ## Project Scoping
88
+
89
+ The `project` field restricts a handler to sessions running in specific directories. It is matched against the `cwd` field of the hook input.
90
+
91
+ ### Matching Rules
92
+
93
+ - **With wildcards (`*`):** The pattern is split on `*` and each literal segment must appear in the cwd path. Order does not matter.
94
+ - **Without wildcards:** The cwd must start with the pattern (prefix match) or equal it exactly.
95
+
96
+ ### Examples
97
+
98
+ **Only fire in Driffusion projects:**
99
+
100
+ ```yaml
101
+ - id: driffusion-lint
102
+ type: script
103
+ command: "node ~/hooks/driffusion-lint.js"
104
+ project: "*/Driffusion/*"
105
+ ```
106
+
107
+ This matches any cwd containing `/Driffusion/` anywhere in the path.
108
+
109
+ **Only fire in a specific directory:**
110
+
111
+ ```yaml
112
+ - id: work-hook
113
+ type: inline
114
+ module: ~/hooks/work.js
115
+ project: "/Users/me/work"
116
+ ```
117
+
118
+ This matches any cwd that starts with `/Users/me/work`.
119
+
120
+ ## Combining Filters
121
+
122
+ All filters are evaluated in order. A handler fires only if every condition passes:
123
+
124
+ 1. `enabled` is not `false`.
125
+ 2. The handler is not auto-disabled (consecutive failures < 3).
126
+ 3. `agent` matches the current session agent (if specified).
127
+ 4. `project` matches the session cwd (if specified).
128
+ 5. `filter` keyword match passes (if specified).
129
+
130
+ If any condition fails, the handler is skipped. Skipped handlers are recorded in metrics with `filtered: true` and zero execution time.
131
+
132
+ ### Full Example
133
+
134
+ ```yaml
135
+ handlers:
136
+ PreToolUse:
137
+ - id: targeted-review
138
+ type: llm
139
+ model: claude-haiku-4-5
140
+ prompt: "Review: $ARGUMENTS"
141
+ filter: "Write|Edit"
142
+ agent: "builder"
143
+ project: "*/Driffusion/*"
144
+ ```
145
+
146
+ This handler fires only when:
147
+ - The tool call input contains "Write" or "Edit".
148
+ - The session is running the `builder` agent.
149
+ - The working directory contains `/Driffusion/` in its path.
150
+
151
+ ---
152
+
153
+ [Home](../index.md) | [Prev: LLM Handlers](llm-handlers.md) | [Next: Dependencies](dependencies.md)