# hookwise
Intelligent permission gating for AI coding assistants. A Rust binary that works as both a Claude Code plugin and Gemini CLI extension, providing learned permission decisions across multi-session and multi-agent environments.
## Architecture
6-tier decision cascade for every tool call:
```
Tool call -> Sanitize -> Path Policy -> Cache -> Token Sim -> Embed Sim -> Supervisor -> Human
~5us ~1us ~100ns ~500ns ~1-5ms ~1-2s interrupt
```
- **Tier 0**: Path policy — deterministic globset matching per role (~1us)
- **Tier 1**: Exact cache match (HashMap, ~100ns) — auto-resolves allow/deny, escalates ask
- **Tier 2a**: Token-level Jaccard similarity (~500ns) — fast approximate matching
- **Tier 2b**: Embedding similarity via fastembed + instant-distance HNSW (~1-5ms)
- **Tier 3**: LLM supervisor agent via Unix domain socket or Anthropic API (~1-2s)
- **Tier 4**: Human-in-the-loop with file-backed decision queue (variable)
Similarity only auto-approves, never auto-denies. Similarity propagates `ask`. Timeout defaults to deny.
Design document for more details: docs/hookwise-design.md
## Tri-State Decision Model
Three decision states, not two:
- **allow** — permit, cached, auto-resolves on future matches
- **deny** — block, cached, auto-resolves on future matches
- **ask** — always prompt a human, cached as `ask` so it never auto-resolves
`ask` is for operations the user wants to stay aware of regardless of frequency (writing `.claude/`, `.env`, settings files, etc.). Sensitive paths default to `ask`.
Precedence: DENY > ASK > ALLOW > silent
## Project Structure
```
src/
main.rs # CLI entry point (clap)
lib.rs # Library root, re-exports
error.rs # HookwiseError enum (thiserror)
decision.rs # Decision, DecisionRecord, CacheKey, DecisionTier
hook_io.rs # Hook input/output JSON types (stdin/stdout)
config/
mod.rs # Config loading orchestration
policy.rs # PolicyConfig, sensitive paths, YAML deserialization
roles.rs # RoleDefinition, PathPolicy with GlobSet compilation
sanitize/
mod.rs # SanitizePipeline (chains all 4 layers)
aho.rs # Layer 1: aho-corasick literal prefix matching
regex_san.rs # Layer 2: RegexSet positional patterns
entropy.rs # Layer 3: Shannon entropy detector
encoding.rs # Layer 4: encoding-aware (base64, URL-decode)
storage/
mod.rs # StorageBackend trait
jsonl.rs # JSONL read/write for decision records
index.rs # instant-distance HNSW index wrapper
scope/
mod.rs # ScopeResolver
hierarchy.rs # ScopeLevel enum + file loading
merge.rs # DENY > ASK > ALLOW merge logic
session/
mod.rs # SessionManager
context.rs # SessionContext + DashMap cache
registration.rs # Registration file read/write/poll
cascade/
mod.rs # CascadeRunner orchestrator (runs all tiers in sequence)
path_policy.rs # Tier 0: globset path matching
cache.rs # Tier 1: exact HashMap cache (tri-state)
token_sim.rs # Tier 2a: token-level Jaccard similarity
embed_sim.rs # Tier 2b: fastembed + instant-distance
supervisor.rs # Tier 3: SupervisorBackend trait + implementations
human.rs # Tier 4: file-backed pending queue
cli/
mod.rs # Subcommand dispatch
check.rs # `hookwise check`: reads JSON from stdin
session_check.rs # `hookwise session-check`: registration prompt
register.rs # register/disable/enable subcommands
queue.rs # queue/approve/deny subcommands
monitor.rs # monitor/stats subcommands
build.rs # build/invalidate subcommands
override_cmd.rs # override subcommand
init.rs # init subcommand (creates .hookwise/)
scan.rs # scan --staged subcommand
ipc/
mod.rs # IPC types
socket_server.rs # tokio Unix domain socket server
socket_client.rs # Sync client (hook binary -> supervisor)
pending_queue.rs # File-backed pending decision queue
tests/
sanitize_tests.rs # 3-layer sanitization pipeline tests
path_policy_tests.rs # Globset path matching tests
cache_tests.rs # Tri-state cache behavior tests
token_sim_tests.rs # Jaccard similarity tests
session_tests.rs # Session registration tests
cascade_integration.rs # Full cascade integration tests
cli_integration.rs # CLI binary invocation tests
ipc_integration.rs # Unix socket round-trip tests
.hookwise/ # Project-level config (checked into git)
policy.yml # Project policy, sensitive paths, thresholds
roles.yml # Role definitions with path policies
rules/ # Cached decisions (sanitized JSONL)
.gitignore # Ignores .index/ and .user/
.claude-plugin/
plugin.json # Claude Code plugin manifest
hooks/
hooks.json # Hook definitions (PreToolUse, user_prompt_submit)
skills/ # Slash command skill definitions
register/SKILL.md
disable/SKILL.md
enable/SKILL.md
switch/SKILL.md
status/SKILL.md
agents/
supervisor.md # Supervisor agent instructions
docs/
hookwise-design.md # Full design specification
adr/ # Architecture decision records
architecture/ # Module interfaces spec
research/ # Gitleaks patterns, bash path extraction
reviews/ # Code and security review reports
```
Global config lives at `~/.config/hookwise/`.
## Roles
Twelve built-in roles in three categories. Deterministic path globs (tier 0) + natural language descriptions (tier 3 LLM).
**Implementation roles** — write to specific code/config directories:
| coder | src/, lib/, project config (Cargo.toml, package.json, etc.) | tests/, docs/, .github/, *.tf |
| tester | tests/, test-fixtures/, *.test.*, *_test.go, test configs | src/, lib/, docs/, .github/ |
| integrator | *.tf, *.tfvars, terraform/, infra/, pulumi/, helm/, ansible/ | src/, lib/, tests/, docs/ |
| devops | .github/, Dockerfile*, docker-compose*, .*rc, tool version files | src/, lib/, tests/, docs/ |
**Knowledge roles** — read codebase, write artifacts to docs/ subdirectories:
| researcher | docs/research/ | src/, lib/, tests/, .github/ |
| architect | docs/architecture/, docs/adr/ | src/, lib/, tests/, .github/ |
| planner | docs/plans/ | src/, lib/, tests/, .github/ |
| reviewer | docs/reviews/ (not security/) | src/, lib/, tests/, .github/ |
| security-reviewer | docs/reviews/security/ | src/, lib/, tests/, .github/ |
| docs | docs/, *.md, *.aisp | src/, lib/, tests/, .github/ |
**Full-access roles** — unrestricted:
| maintainer | ** | (none) |
| troubleshooter | ** | (none) |
Knowledge roles produce artifacts that implementation roles consume:
researcher -> architect -> planner -> coder/tester -> reviewer -> maintainer.
## Session Registration
Every session must register a role before tool calls are permitted. Three mechanisms:
1. **Interactive** — `user_prompt_submit` hook prompts user via AskUserQuestion on first prompt
2. **CLI** — `hookwise register --session-id <id> --role <role>`
3. **Env var fallback** — `HOOKWISE_ROLE=coder` for CI/scripted use
Unregistered sessions: hook waits 5s for registration, then blocks with instructions.
## Key Components
### Secret Sanitization
Four layers, compiled at startup:
1. **aho-corasick** — literal prefix matching (sk-ant-, sk-proj-, ghp_, AKIA, etc.)
2. **RegexSet** — positional/contextual patterns (bearer tokens, api keys, connection strings)
3. **Shannon entropy** — catch unknown formats (20+ char tokens with entropy > 4.0, also scans bare tokens)
4. **Encoding-aware** — decodes base64 and URL-encoded values before re-scanning
All tool input is sanitized before any cache/vector/storage operation.
### Hook I/O
The `hookwise check` command reads JSON from **stdin** (matching Claude Code's hook protocol) and outputs JSON to stdout with `hookSpecificOutput` containing the `permissionDecision`.
### Path Policy (Tier 0)
Deterministic globset matching per role. Runs before cache/vector/LLM. Hard gate — cannot be overridden by cached decisions or LLM. Sensitive paths (`.claude/**`, `.env*`, etc.) default to `ask` regardless of role.
### Token Similarity (Tier 2a)
Token-level Jaccard similarity provides fast approximate matching (~500ns). Splits commands into tokens, computes set intersection/union ratio. Minimum 3-token threshold to avoid false matches on short commands.
### Embedding Similarity (Tier 2b)
fastembed generates embeddings, instant-distance provides HNSW-indexed nearest neighbor search. Rebuilds full index via `hookwise build`.
### Supervisor (Tier 3)
Pluggable supervisor with two backends:
- **Unix socket** — communicates with Claude Code subagent via `/tmp/hookwise-<team-id>.sock`
- **Anthropic API** — standalone mode using `ANTHROPIC_API_KEY` env var
### Human-in-the-Loop (Tier 4)
File-backed decision queue at `/tmp/hookwise-pending.json` (or `$XDG_RUNTIME_DIR/hookwise-pending.json`). Enables cross-process communication between the hook binary and CLI approve/deny commands.
### Scope Hierarchy
Deny > Ask > Allow. Precedence: Org > Project > User > Role.
### IPC
Unix domain socket at `/tmp/hookwise-<team-id>.sock` for supervisor agent communication.
## Slash Commands
- `/hookwise register` — pick a role interactively
- `/hookwise disable` — opt out for this session
- `/hookwise enable` — re-enable after disable
- `/hookwise switch` — change role mid-session
- `/hookwise status` — show current role, path policy, cache stats
## Dependencies
| `clap` | CLI argument parsing with derive macros |
| `serde` / `serde_json` / `serde_yaml` | JSONL and YAML serialization |
| `aho-corasick` | Literal prefix matching for secret detection |
| `regex` | RegexSet for positional secret patterns |
| `globset` | Path policy glob pattern matching |
| `dashmap` | Concurrent session context cache |
| `fastembed` | Embedding generation for semantic similarity |
| `instant-distance` | HNSW-indexed vector similarity search |
| `tokio` | Async runtime for socket server and polling |
| `thiserror` / `anyhow` | Error handling |
| `sha2` | Hashing for cache keys |
| `chrono` | Timestamps for decision records |
| `tracing` / `tracing-subscriber` | Structured logging |
| `async-trait` | Async trait support for cascade tiers |
| `reqwest` | HTTP client for Anthropic API supervisor backend |
| `libc` | Unix socket permission management |
## CLI Modes
- **Hook mode**: `hookwise check` — reads JSON from stdin, outputs permissionDecision JSON
- **Session check**: `hookwise session-check` — registration prompt for `user_prompt_submit` hook
- **Queue mode**: `hookwise queue/approve/deny` — human interface, supports `--always-ask`
- **Registration**: `hookwise register/disable/enable` — session management
- **Monitor mode**: `hookwise monitor/stats` — observe decisions, ask frequency
- **Cache management**: `hookwise build/invalidate` — rebuild indexes or clear decisions
- **Overrides**: `hookwise override --allow|--deny|--ask` — explicit per-role overrides
- **Init**: `hookwise init` — creates `.hookwise/` directory in a repo
- **Scan**: `hookwise scan --staged` — pre-commit secret detection
## Building
```bash
cargo build --release
cargo test
cargo clippy -- -D warnings
cargo fmt --check
```
## Conventions
- Binary name: `hookwise`
- Config directory: `.hookwise/` in repos, `~/.config/hookwise/` globally
- Socket path: `/tmp/hookwise-<team-id>.sock`
- Pending queue: `/tmp/hookwise-pending.json` or `$XDG_RUNTIME_DIR/hookwise-pending.json`
- Rules are sanitized JSONL, checked into git, reviewable in PRs
- Vector indexes and user preferences are gitignored (derived/local artifacts)
- Pre-commit hook runs `hookwise scan --staged` to prevent accidental secret commits
- Ships as a Claude Code plugin (binary + hooks + agent instructions + slash commands)