jevhooks.git / README.md
README.mdpreviewREADME.mdsource179 lines · 10.1 KB · raw
1# jevhooks: a guide in thirteen chapters
2
3**Jev judges what Claude Code is about to do.** Claude Code is an AI coding
4assistant that runs in your terminal. It runs shell commands for you and
5decides for itself when it has finished. jevhooks is a plugin that, at those
6two moments, stops to ask a second opinion of something that does not
7write, cannot be talked round, and costs a few thousandths of a cent to ask.
8
9That something is Jev, TypeSafe AI's "System One" model. Jev does not write
10text. You hand it a situation and a question whose answers are already
11written down (one of these eight kinds of act; somewhere on this four-step
12scale), and it says how likely each answer is, in about a quarter of a
13second, with numbers that mean what they say. jevhooks asks it two things:
14
15- **Before a Bash command runs:** what kind of act is this (one of eight,
16  from "only reads" to "runs code nobody has read"), how hard would it be to
17  put back (four levels), and how much of the machine will it take (four
18  levels)? A confident consequential act, or anything hard to undo, is put
19  to you with the reason. A heavy command is put to you when too little
20  memory is free for it. Ordinary work runs without a prompt.
21- **Before a turn ends:** which of five endings is this (finished, waiting
22  on you, blocked, still running, stopped early)? A confident "stopped
23  early" is sent back to the assistant with the reason, and the turn goes
24  on.
25
26And one promise underneath both: **Jev being slow, absent, unsure or broken
27never blocks anything.** Then the verdict is "pass", and whatever would have
28happened without the plugin happens.
29
30Jev's own documentation is at <https://docs.typesafe.ai>.
31
32> **Try it.** You do not need Rust, a Jev key or a GitHub account to see
33> the plugin work. With Claude Code installed:
34>
35>     git clone --recurse-submodules https://lmjtfy.fun/jevhooks.git
36>     cd jevhooks
37>     claude plugin test plugin
38>
39> Eight tests run against a cardboard stand-in for the daemon (chapter 5)
40> and pass in about five seconds. One of them sends `git status` and checks
41> that nothing left the process at all; chapter 10 says why.
42
43## How to read this
44
45Every folder in this repository is a chapter, and each one ends with a link
46to the next. Read them in order and you will know how all of it works; jump
47in anywhere and the chapter tells you what it is and what is beside it. Each
48chapter starts with the idea, in plain words, and ends with the parts a
49maintainer needs (files, invariants, commands). The `CLAUDE.md` beside each
50README is what an AI agent working in that folder is told on top of it.
51
52The guide follows an event from where Claude Code raises it to where Jev
53answers it: first the plugin Claude Code loads, then the Rust behind it.
54
55| Chapter | Folder | What you learn |
56| --- | --- | --- |
57| 1 | [plugin/](plugin/) | What a Claude Code plugin is, and what this one brings. |
58| 2 | [plugin/.claude-plugin/](plugin/.claude-plugin/) | The manifest: a name, a version, and a promise about state. |
59| 3 | [plugin/hooks/](plugin/hooks/) | The mod: hooks with a `next`, like middleware, and the band above the prompt. |
60| 4 | [plugin/types/](plugin/types/) | The one piece of state the plugin keeps, typed. |
61| 5 | [plugin/tests/](plugin/tests/) | Testing a mod against a fake daemon and a fake host. |
62| 6 | [crates/](crates/) | Three Rust packages, and why there are three. |
63| 7 | [crates/jevhooks-events/](crates/jevhooks-events/) | One vocabulary, compiled into both sides of a socket. |
64| 8 | [crates/jevhooks-events/src/](crates/jevhooks-events/src/) | Thirty-three events and the three things that can happen to one. |
65| 9 | [crates/jevhooks-mod/](crates/jevhooks-mod/) | Rust that becomes WebAssembly that becomes JavaScript. |
66| 10 | [crates/jevhooks-mod/src/](crates/jevhooks-mod/src/) | Routing, and a read-only filter that is wrong in only one direction. |
67| 11 | [crates/jevhooks-daemon/](crates/jevhooks-daemon/) | One daemon for the machine, and how sessions find it. |
68| 12 | [crates/jevhooks-daemon/src/](crates/jevhooks-daemon/src/) | The questions Jev is asked, and how its numbers become a verdict. |
69| 13 | [third-party/](third-party/) | The shared Jev client, borrowed as a submodule. |
70
71## If you know web frameworks
72
73None of this needs Rust or Claude Code experience to follow. Most of the
74parts have a counterpart you already know.
75
76| Here | What it is | In web terms |
77| --- | --- | --- |
78| A hook | Claude 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. |
79| `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. |
80| The mod | TypeScript that Claude Code runs inside its own process (`plugin/hooks/`). | A middleware module. |
81| The daemon | One long-running local server for the whole machine, on a Unix socket. | A small API server on localhost. |
82| A Unix socket | A file that behaves like a port: only processes on this machine can connect. | `localhost:3000`, but private to the machine. |
83| The MCP server | A process Claude Code starts per session, offering tools the assistant can call. | A per-tab service worker that also boots the API server. |
84| wasm2js | Translates compiled WebAssembly into plain JavaScript. | A transpiler, run on machine code instead of source. |
85| Cargo, crates | Rust's package manager, and its packages. | npm and npm packages; a Cargo workspace is a monorepo workspace. |
86| Nix | Every tool at an exact version, from `nix develop`. | `package.json` and a Node version manager, for every tool at once. |
87
88## The whole story in one picture
89
90Here is what happens between "the assistant wants to run a command" and the
91command running. Every arrow is a chapter somewhere in this guide.
92
93```mermaid
94sequenceDiagram
95  participant C as Claude Code
96  participant M as Mod (plugin/hooks)
97  participant R as Routing (Rust, as JS)
98  participant D as Daemon (jevhooks serve)
99  participant J as Jev
100  C->>M: classic.PreToolUse (Bash: cargo build)
101  M->>R: route(event, tool, command)
102  R-->>M: decide (not plainly read-only)
103  par the mod asks the daemon
104    M->>D: POST /decide, over the Unix socket
105    D->>J: one request, three questions
106    J-->>D: a probability for every option
107    D-->>M: Decision: verdict, line, ms, cost
108  and the hooks beneath run too
109    M->>C: next(e): the user's own hooks
110  end
111  M-->>C: the stricter answer: deny, then ask, then allow
112  C->>M: classic.PostToolUse
113  M-)D: POST /observe (not waited on): it ran
114```
115
116In words:
117
1181. **The mod hears every hook event** (chapter 3) and asks a few lines of
119   Rust, compiled into JavaScript and running in the same process, what to
120   do with it (chapters 9 and 10). A plainly read-only command, a tool that
121   is not Bash, or an event nobody needs goes no further: no round trip, no
122   question, no cost.
1232. **Anything worth judging goes to the daemon** (chapter 11), one process
124   for every session on the machine, which keeps the connection to Jev warm
125   and the key in one place.
1263. **The daemon asks Jev**, all of a judgment's questions in one request,
127   and turns the typed answers into a verdict with rules written in code,
128   where a test can pin them (chapter 12).
1294. **The mod combines that verdict with the hooks beneath it**, and the
130   stricter one wins. Then it reports what became of the command, so the
131   log can show whether the judgment was any good.
132
133> **Aside.** Why not let the assistant police itself? Because it is the
134> thing being checked. A model that writes can be argued with, by the very
135> text it is about to act on; Jev can only pick from options written in
136> `judge.rs`, so the worst a cleverly worded command can do is move a
137> probability. And it is cheap enough to ask every time: a command's three
138> questions come to about 1,700 input tokens, which at Jev's published
139> price of $0.042 per million is under a hundredth of a cent.
140
141## Running it yourself
142
143Anyone who clones it can read every line and run the plugin's tests (the
144Try it above). Building the Rust needs one thing an outside reader cannot
145fetch yet: the daemon depends on a private fork of rmcp, the Rust MCP
146library, at `ssh://git@github.com/deizel/rust-sdk.git` (the
147`[patch.crates-io]` entry in [Cargo.toml](Cargo.toml), and chapter 6 says
148why). Cargo resolves the whole workspace before building any of it, so
149without that fork every `cargo` command stops at `failed to load source for
150dependency rmcp`, even for a crate that does not use it.
151
152With access to the fork:
153
154    nix develop                   # stable Rust with the wasm32 target, binaryen, node, just
155    just check                    # Rust tests, the mod's JavaScript, the daemon, plugin validation
156    claude --plugin-dir ./plugin  # a session with jevhooks loaded
157
158In a session, a band above the prompt shows the last decision, how long it
159took and what the session's questions have cost; `/jev` prints the daemon's
160status and the latest decisions. Outside one: `plugin/bin/jevhooks status`,
161`recent [count]` and `stop`.
162
163The daemon needs a TypeSafe API key in `TYPESAFE_API_KEY`. Without one it
164runs, judges nothing, and every verdict is "pass" (chapter 11 has where the
165key comes from, the files the daemon keeps, and its one setting).
166
167## Files at the top
168
169| File | What |
170| --- | --- |
171| [Cargo.toml](Cargo.toml) | The Cargo workspace: the three crates, every dependency's version, the release profile, and the rmcp patch. |
172| [Cargo.lock](Cargo.lock) | Every dependency at the exact version built. |
173| [flake.nix](flake.nix) | The devshell, for x86_64-linux: stable Rust with the wasm32 target, binaryen (`wasm-opt`, `wasm2js`), node and just. |
174| [flake.lock](flake.lock) | The flake's inputs, pinned. |
175| [justfile](justfile) | `just test`, `just mod` (chapter 9), `just daemon` (chapter 11), `just check` (all three, then `claude plugin validate plugin`). |
176| [.gitmodules](.gitmodules) | The jevcrates submodule, by a relative URL (chapter 13). |
177| [.gitignore](.gitignore) | Build output, the built daemon in `plugin/bin/`, and the type files Claude Code lays in `plugin/.claude-plugin/types/`. |
178
179Next: [Chapter 1, plugin/](plugin/) →