jevhooks: a guide in thirteen chapters

Jev judges what Claude Code is about to do. Claude Code is an AI coding assistant that runs in your terminal. It runs shell commands for you and decides for itself when it has finished. jevhooks is a plugin that, at those two moments, stops to ask a second opinion of something that does not write, cannot be talked round, and costs a few thousandths of a cent to ask.

That something is Jev, TypeSafe AI's "System One" model. Jev does not write text. You hand it a situation and a question whose answers are already written down (one of these eight kinds of act; somewhere on this four-step scale), and it says how likely each answer is, in about a quarter of a second, with numbers that mean what they say. jevhooks asks it two things:

  • Before a Bash command runs: what kind of act is this (one of eight, from "only reads" to "runs code nobody has read"), how hard would it be to put back (four levels), and how much of the machine will it take (four levels)? A confident consequential act, or anything hard to undo, is put to you with the reason. A heavy command is put to you when too little memory is free for it. Ordinary work runs without a prompt.
  • Before a turn ends: which of five endings is this (finished, waiting on you, blocked, still running, stopped early)? A confident "stopped early" is sent back to the assistant with the reason, and the turn goes on.

And one promise underneath both: Jev being slow, absent, unsure or broken never blocks anything. Then the verdict is "pass", and whatever would have happened without the plugin happens.

Jev's own documentation is at https://docs.typesafe.ai.

Try it. You do not need Rust, a Jev key or a GitHub account to see the plugin work. With Claude Code installed:

git clone --recurse-submodules https://lmjtfy.fun/jevhooks.git
cd jevhooks
claude plugin test plugin

Eight tests run against a cardboard stand-in for the daemon (chapter 5) and pass in about five seconds. One of them sends git status and checks that nothing left the process at all; chapter 10 says why.

How to read this

Every folder in this repository is a chapter, and each one ends with a link to the next. Read them in order and you will know how all of it works; jump in anywhere and the chapter tells you what it is and what is beside it. Each chapter starts with the idea, in plain words, and ends with the parts a maintainer needs (files, invariants, commands). The CLAUDE.md beside each README is what an AI agent working in that folder is told on top of it.

The guide follows an event from where Claude Code raises it to where Jev answers it: first the plugin Claude Code loads, then the Rust behind it.

ChapterFolderWhat you learn
1plugin/What a Claude Code plugin is, and what this one brings.
2plugin/.claude-plugin/The manifest: a name, a version, and a promise about state.
3plugin/hooks/The mod: hooks with a next, like middleware, and the band above the prompt.
4plugin/types/The one piece of state the plugin keeps, typed.
5plugin/tests/Testing a mod against a fake daemon and a fake host.
6crates/Three Rust packages, and why there are three.
7crates/jevhooks-events/One vocabulary, compiled into both sides of a socket.
8crates/jevhooks-events/src/Thirty-three events and the three things that can happen to one.
9crates/jevhooks-mod/Rust that becomes WebAssembly that becomes JavaScript.
10crates/jevhooks-mod/src/Routing, and a read-only filter that is wrong in only one direction.
11crates/jevhooks-daemon/One daemon for the machine, and how sessions find it.
12crates/jevhooks-daemon/src/The questions Jev is asked, and how its numbers become a verdict.
13third-party/The shared Jev client, borrowed as a submodule.

If you know web frameworks

None of this needs Rust or Claude Code experience to follow. Most of the parts have a counterpart you already know.

HereWhat it isIn web terms
A hookClaude Code raises an event (a tool is about to run, a turn is ending) and hands it to every plugin that registered for it.A request reaching your middleware.
next(e)Runs whatever is beneath this hook: other plugins, the user's own settings hooks.Express's next(), except you get its answer back and can combine it with yours.
The modTypeScript that Claude Code runs inside its own process (plugin/hooks/).A middleware module.
The daemonOne long-running local server for the whole machine, on a Unix socket.A small API server on localhost.
A Unix socketA file that behaves like a port: only processes on this machine can connect.localhost:3000, but private to the machine.
The MCP serverA process Claude Code starts per session, offering tools the assistant can call.A per-tab service worker that also boots the API server.
wasm2jsTranslates compiled WebAssembly into plain JavaScript.A transpiler, run on machine code instead of source.
Cargo, cratesRust's package manager, and its packages.npm and npm packages; a Cargo workspace is a monorepo workspace.
NixEvery tool at an exact version, from nix develop.package.json and a Node version manager, for every tool at once.

The whole story in one picture

Here is what happens between "the assistant wants to run a command" and the command running. Every arrow is a chapter somewhere in this guide.

sequenceDiagram
  participant C as Claude Code
  participant M as Mod (plugin/hooks)
  participant R as Routing (Rust, as JS)
  participant D as Daemon (jevhooks serve)
  participant J as Jev
  C->>M: classic.PreToolUse (Bash: cargo build)
  M->>R: route(event, tool, command)
  R-->>M: decide (not plainly read-only)
  par the mod asks the daemon
    M->>D: POST /decide, over the Unix socket
    D->>J: one request, three questions
    J-->>D: a probability for every option
    D-->>M: Decision: verdict, line, ms, cost
  and the hooks beneath run too
    M->>C: next(e): the user's own hooks
  end
  M-->>C: the stricter answer: deny, then ask, then allow
  C->>M: classic.PostToolUse
  M-)D: POST /observe (not waited on): it ran

In words:

  1. The mod hears every hook event (chapter 3) and asks a few lines of Rust, compiled into JavaScript and running in the same process, what to do with it (chapters 9 and 10). A plainly read-only command, a tool that is not Bash, or an event nobody needs goes no further: no round trip, no question, no cost.
  2. Anything worth judging goes to the daemon (chapter 11), one process for every session on the machine, which keeps the connection to Jev warm and the key in one place.
  3. The daemon asks Jev, all of a judgment's questions in one request, and turns the typed answers into a verdict with rules written in code, where a test can pin them (chapter 12).
  4. The mod combines that verdict with the hooks beneath it, and the stricter one wins. Then it reports what became of the command, so the log can show whether the judgment was any good.

Aside. Why not let the assistant police itself? Because it is the thing being checked. A model that writes can be argued with, by the very text it is about to act on; Jev can only pick from options written in judge.rs, so the worst a cleverly worded command can do is move a probability. And it is cheap enough to ask every time: a command's three questions come to about 1,700 input tokens, which at Jev's published price of $0.042 per million is under a hundredth of a cent.

Running it yourself

Anyone who clones it can read every line and run the plugin's tests (the Try it above). Building the Rust needs one thing an outside reader cannot fetch yet: the daemon depends on a private fork of rmcp, the Rust MCP library, at ssh://git@github.com/deizel/rust-sdk.git (the [patch.crates-io] entry in Cargo.toml, and chapter 6 says why). Cargo resolves the whole workspace before building any of it, so without that fork every cargo command stops at failed to load source for dependency rmcp, even for a crate that does not use it.

With access to the fork:

nix develop                   # stable Rust with the wasm32 target, binaryen, node, just
just check                    # Rust tests, the mod's JavaScript, the daemon, plugin validation
claude --plugin-dir ./plugin  # a session with jevhooks loaded

In a session, a band above the prompt shows the last decision, how long it took and what the session's questions have cost; /jev prints the daemon's status and the latest decisions. Outside one: plugin/bin/jevhooks status, recent [count] and stop.

The daemon needs a TypeSafe API key in TYPESAFE_API_KEY. Without one it runs, judges nothing, and every verdict is "pass" (chapter 11 has where the key comes from, the files the daemon keeps, and its one setting).

Files at the top

FileWhat
Cargo.tomlThe Cargo workspace: the three crates, every dependency's version, the release profile, and the rmcp patch.
Cargo.lockEvery dependency at the exact version built.
flake.nixThe devshell, for x86_64-linux: stable Rust with the wasm32 target, binaryen (wasm-opt, wasm2js), node and just.
flake.lockThe flake's inputs, pinned.
justfilejust test, just mod (chapter 9), just daemon (chapter 11), just check (all three, then claude plugin validate plugin).
.gitmodulesThe jevcrates submodule, by a relative URL (chapter 13).
.gitignoreBuild output, the built daemon in plugin/bin/, and the type files Claude Code lays in plugin/.claude-plugin/types/.

Next: Chapter 1, plugin/ →