jevhooks.git / crates / README.md

Chapter 6: crates, three Rust packages and why there are three

Everything the plugin does that is not plumbing is Rust, in three crates (a crate is Rust's package: one Cargo.toml, the way an npm package has one package.json). Each exists because of where it has to run.

  • jevhooks-events runs everywhere. It is the vocabulary: the hook events, what is done with each, the verdict and the decision. It does no I/O at all, so it compiles both into the mod and into the daemon.
  • jevhooks-mod runs inside Claude Code. It is compiled to WebAssembly and then to JavaScript, and decides in well under a millisecond what to do with each event.
  • jevhooks-daemon runs as its own process. It is the jevhooks binary: the daemon that asks Jev, the per-session MCP server, and the command-line tools that read them.
flowchart BT
  events["jevhooks-events<br/>the vocabulary, no I/O"]
  modc["jevhooks-mod<br/>routing, as JavaScript"]
  daemon["jevhooks-daemon<br/>the jevhooks binary"]
  jevcrates["jev-http, jev-protocol<br/>(third-party/jevcrates)"]
  rmcp["rmcp<br/>(the deizel/rust-sdk fork)"]
  modc --> events
  daemon --> events
  daemon --> jevcrates
  daemon --> rmcp

(An arrow means "depends on".)

The point of the split is the line down the middle. The mod and the daemon live in different processes and speak JSON over a socket; the only way to be sure they agree on what "PreToolUse" or "ask" means is for both to be compiled from the same Rust enum. That enum is in jevhooks-events, and it is the only thing the two sides share.

Aside: the fork everyone trips over. The root Cargo.toml asks for rmcp 3.5.0, the Rust MCP library, and then patches it with a commit on deizel/rust-sdk, a private fork. rmcp 3.5.0 refuses every request after the way Claude Code 2.1.287 opens a stdio server (server/discover, then initialize), so without the patch the MCP server connects and offers no tools. The fork carries one fix on top of the rmcp-v3.5.0 tag. It is fetched over SSH and is not public yet, and Cargo resolves the whole workspace before building any part of it: without access, even cargo test -p jevhooks-events, which does not use rmcp, stops at fetching it.

Try it. With access to the fork, cargo test from the repository root runs every crate's tests. None of them needs the network, a Jev key or a daemon: the verdicts are tested on made-up probabilities, and the routing on made-up events.

The chapters that follow take them in the order an event meets them: the vocabulary (7 and 8), the routing in the mod (9 and 10), the daemon (11 and 12).

For the people who maintain it

All three are version 0.1.0, edition 2024, publish = false, and take every dependency's version from the root Cargo.toml's [workspace.dependencies]. The release profile there (opt-level = "s", lto, panic = "abort", one codegen unit) applies to the wasm build and to the daemon alike.

In this folder

CrateWhat
jevhooks-events/Chapters 7 and 8: every hook event, its role, the verdict and the decision. Depends on serde only.
jevhooks-mod/Chapters 9 and 10: the routing and the read-only filter, built as a cdylib for wasm32 and an rlib for native tests.
jevhooks-daemon/Chapters 11 and 12: the jevhooks binary.

← Previous: Chapter 5, tests/ · Up: jevhooks · Next: Chapter 7, jevhooks-events/ →