@bobobowis/jev-kit 0.0.0-stage → 0.2.0

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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -2
  3. package/dist/capabilities.d.ts +33 -0
  4. package/dist/capabilities.js +81 -0
  5. package/dist/cli/ask.d.ts +9 -0
  6. package/dist/cli/ask.js +210 -0
  7. package/dist/cli/common.d.ts +38 -0
  8. package/dist/cli/common.js +40 -0
  9. package/dist/cli/hook.d.ts +3 -0
  10. package/dist/cli/hook.js +85 -0
  11. package/dist/cli/index.d.ts +2 -0
  12. package/dist/cli/index.js +21 -0
  13. package/dist/cli/main.d.ts +8 -0
  14. package/dist/cli/main.js +41 -0
  15. package/dist/cli/mcp.d.ts +3 -0
  16. package/dist/cli/mcp.js +47 -0
  17. package/dist/cli/setup.d.ts +3 -0
  18. package/dist/cli/setup.js +84 -0
  19. package/dist/compact/compact-on-demand.d.ts +22 -0
  20. package/dist/compact/compact-on-demand.js +29 -0
  21. package/dist/compact/evaluate.d.ts +36 -0
  22. package/dist/compact/evaluate.js +31 -0
  23. package/dist/compact/index.d.ts +7 -0
  24. package/dist/compact/index.js +7 -0
  25. package/dist/compact/pick-cut-point.d.ts +18 -0
  26. package/dist/compact/pick-cut-point.js +29 -0
  27. package/dist/compact/should-compact.d.ts +57 -0
  28. package/dist/compact/should-compact.js +87 -0
  29. package/dist/core/client.d.ts +93 -0
  30. package/dist/core/client.js +295 -0
  31. package/dist/core/helpers.d.ts +14 -0
  32. package/dist/core/helpers.js +9 -0
  33. package/dist/core/mock.d.ts +20 -0
  34. package/dist/core/mock.js +132 -0
  35. package/dist/core/types.d.ts +102 -0
  36. package/dist/core/types.js +82 -0
  37. package/dist/events/index.d.ts +44 -0
  38. package/dist/events/index.js +77 -0
  39. package/dist/files/ask-file-bool.d.ts +12 -0
  40. package/dist/files/ask-file-bool.js +15 -0
  41. package/dist/files/ask-file-choice.d.ts +10 -0
  42. package/dist/files/ask-file-choice.js +17 -0
  43. package/dist/files/ask-file-score.d.ts +12 -0
  44. package/dist/files/ask-file-score.js +15 -0
  45. package/dist/files/ask-files.d.ts +23 -0
  46. package/dist/files/ask-files.js +56 -0
  47. package/dist/files/ask.d.ts +12 -0
  48. package/dist/files/ask.js +28 -0
  49. package/dist/files/assemble.d.ts +52 -0
  50. package/dist/files/assemble.js +129 -0
  51. package/dist/files/decide.d.ts +10 -0
  52. package/dist/files/decide.js +1 -0
  53. package/dist/files/index.d.ts +14 -0
  54. package/dist/files/index.js +14 -0
  55. package/dist/files/pick-first.d.ts +16 -0
  56. package/dist/files/pick-first.js +24 -0
  57. package/dist/files/prune.d.ts +13 -0
  58. package/dist/files/prune.js +87 -0
  59. package/dist/files/read-state.d.ts +17 -0
  60. package/dist/files/read-state.js +47 -0
  61. package/dist/guards/bash-gate.d.ts +65 -0
  62. package/dist/guards/bash-gate.js +78 -0
  63. package/dist/guards/index.d.ts +7 -0
  64. package/dist/guards/index.js +7 -0
  65. package/dist/guards/mcp-gate.d.ts +23 -0
  66. package/dist/guards/mcp-gate.js +52 -0
  67. package/dist/guards/result-screen.d.ts +20 -0
  68. package/dist/guards/result-screen.js +31 -0
  69. package/dist/guards/write-gate.d.ts +18 -0
  70. package/dist/guards/write-gate.js +44 -0
  71. package/dist/hooks/handle.d.ts +76 -0
  72. package/dist/hooks/handle.js +61 -0
  73. package/dist/hooks/harness.d.ts +45 -0
  74. package/dist/hooks/harness.js +124 -0
  75. package/dist/hooks/index.d.ts +7 -0
  76. package/dist/hooks/index.js +7 -0
  77. package/dist/hooks/state.d.ts +8 -0
  78. package/dist/hooks/state.js +25 -0
  79. package/dist/hooks/transcript.d.ts +8 -0
  80. package/dist/hooks/transcript.js +52 -0
  81. package/dist/index.d.ts +8 -0
  82. package/dist/index.js +8 -0
  83. package/dist/ledger.d.ts +22 -0
  84. package/dist/ledger.js +26 -0
  85. package/dist/mcp/server.d.ts +28 -0
  86. package/dist/mcp/server.js +124 -0
  87. package/dist/policy/index.d.ts +17 -0
  88. package/dist/policy/index.js +30 -0
  89. package/dist/setup/index.d.ts +76 -0
  90. package/dist/setup/index.js +221 -0
  91. package/dist/tools/ask-jev.d.ts +7 -0
  92. package/dist/tools/ask-jev.js +42 -0
  93. package/dist/tools/compact-tool.d.ts +2 -0
  94. package/dist/tools/compact-tool.js +33 -0
  95. package/dist/tools/description.d.ts +5 -0
  96. package/dist/tools/description.js +20 -0
  97. package/dist/tools/file-tools.d.ts +7 -0
  98. package/dist/tools/file-tools.js +127 -0
  99. package/dist/tools/index.d.ts +10 -0
  100. package/dist/tools/index.js +10 -0
  101. package/dist/tools/run-command.d.ts +7 -0
  102. package/dist/tools/run-command.js +27 -0
  103. package/dist/tools/sets.d.ts +9 -0
  104. package/dist/tools/sets.js +32 -0
  105. package/dist/tools/types.d.ts +51 -0
  106. package/dist/tools/types.js +4 -0
  107. package/package.json +96 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IndyDevDan / AgenticEngineer.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,312 @@
1
- # Temporary Holding Version
1
+ # @bobobowis/jev-kit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Jev, TypeSafe AI's System One decision model, as a typed client, guards, file reads, agent tools,
4
+ hooks, an MCP server, and a CLI. Zero runtime dependencies. Node 22.18 or later.
5
+
6
+ Jev answers typed questions about a state (yes or no, pick one of your options, a position on your
7
+ scale) in about 300 ms for a fraction of a cent. This package is the reusable part of
8
+ [10 Levels of Jev](https://github.com/bobobowis/ten-levels-of-jev): the client and the decisions the lab teaches, packaged so a
9
+ project or a coding agent can use them directly.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @bobobowis/jev-kit # or: pnpm add @bobobowis/jev-kit
15
+ npx jev --help
16
+ ```
17
+
18
+ Or run the command without installing anything:
19
+
20
+ ```bash
21
+ npx @bobobowis/jev-kit gate "Is this ticket about billing?" --state "I was charged twice this month"
22
+ npx @bobobowis/jev-kit setup --harness claude --guards bash,write --tools ask_jev
23
+ ```
24
+
25
+ Run through `npx` or `pnpm dlx`, `jev setup` writes `npx -y @bobobowis/jev-kit@<version>` into the
26
+ hooks and the MCP entry, not a path into the npx cache, so the config keeps working after the cache is cleared.
27
+ Installed in the project, it writes node and the installed bin.
28
+
29
+ ## Keys
30
+
31
+ Everything reads `TYPESAFE_API_KEY`, or else `OPENROUTER_API_KEY`, from the environment. Without one,
32
+ calls fail with an error; nothing quietly falls back to the mock. `--mock` (CLI) or
33
+ `new JevClient({ provider: "mock" })` selects the offline mock explicitly. The mock is a
34
+ deterministic stand-in for tests and wiring, not a model: its answers are not judgments.
35
+
36
+ ## The library
37
+
38
+ ```ts
39
+ import { JevClient, noul, choice, score } from "@bobobowis/jev-kit";
40
+
41
+ const jev = new JevClient(); // TypeSafe or OpenRouter from the environment; { provider: "mock" } offline
42
+ const { answers } = await jev.systemOne(
43
+ { ticket: "The export button has done nothing since Tuesday's release." },
44
+ {
45
+ bug: noul("Does the ticket report something broken?"),
46
+ area: choice("Which area is it about?", { billing: "Payments and invoices", export: "Exporting data", other: "None of these" }),
47
+ urgency: score("How urgent is it?", ["Whenever", "This week", "Today, people are blocked"]),
48
+ },
49
+ );
50
+ console.log(answers.bug, answers.area, answers.urgency);
51
+ ```
52
+
53
+ Each entry point is one layer of the lab. The functions that call Jev take an optional `decide`
54
+ function last, so tests and other clients plug in without a key.
55
+
56
+ | Import | What it has |
57
+ |---|---|
58
+ | `@bobobowis/jev-kit` | `JevClient`, `jev` (a shared client), the `noul`, `choice`, and `score` builders, `MockJev`, the wire types, `validateRequest`, `LIMITS` |
59
+ | `/policy` | `normalized`, `weighted` (scores to numbers), `CONFIDENCE`, `confidenceBand` (floor, middle, bar) |
60
+ | `/guards` | `gateBashCommand(command, cwd)`, `gateWriteCall(path, content, repo)`, `screenToolResult(tool, content)`: the Level 6 gates, with their questions and thresholds |
61
+ | `/compact` | `evaluateCompaction`, `decideTier`, `tierMessage`, `compactVerdict`, `cutPointQuestion`: the Level 7 advisor |
62
+ | `/files` | `readFileState` (a file as state, never outside `cwd`), `askFileBool`, `askFileChoice`, `askFileScore`, `askFiles` (one call per file, in parallel), `pickFirstFile`, `askJev`, `assembleState` |
63
+ | `/tools` | the agent tools as harness-neutral `JevTool` objects (`ALL_TOOLS`, `LEVEL_TOOLS`, `selectTools`), `ASK_JEV_DESCRIPTION`, `runCommand`, `gatedRunner` |
64
+ | `/hooks` | `handleHook` (normalized events), `runHarnessHook` (a Claude Code or Codex payload in, the harness's answer out), transcript usage readers |
65
+ | `/setup` | `plan` and `apply`, behind `jev setup` |
66
+ | `/events` | `createReporter`, `createDecider`, `parseEventLine`: the event channel below |
67
+ | `/ledger` | `emptyLedger`, `record`, `summarize`: Jev spend per session |
68
+ | `/capabilities` | `CAPABILITIES`, `capability(level, option, harness)`: the support table below |
69
+
70
+ ```ts
71
+ import { gateBashCommand } from "@bobobowis/jev-kit/guards";
72
+ import { askFileBool } from "@bobobowis/jev-kit/files";
73
+
74
+ const verdict = await gateBashCommand("git push --force origin main", process.cwd());
75
+ if (verdict.block) console.error(verdict.reason);
76
+
77
+ const r = await askFileBool("src/billing.ts", "Does this file round money anywhere?", process.cwd());
78
+ console.log(r.answer, r.noul); // your script, or the agent, never loads the file
79
+ ```
80
+
81
+ ## Recipes: the lab's levels 1 to 5
82
+
83
+ The package doesn't export the use cases from levels 1 to 5: they are examples with the lab's own
84
+ categories built in, so copy the one you need. Each one is a single short file that uses only what
85
+ the package exports. Copy it into your project, point its imports at the package, and change the
86
+ options and criteria to your own:
87
+
88
+ ```ts
89
+ // In the lab
90
+ import { jev } from "../../core/client.ts";
91
+ import { noul } from "../../core/helpers.ts";
92
+ import type { NoulAnswer } from "../../core/types.ts";
93
+ import { CONFIDENCE } from "./confidence.ts"; // or { normalized } from "./normalize.ts"
94
+
95
+ // In your project
96
+ import { jev, noul, type NoulAnswer } from "@bobobowis/jev-kit";
97
+ import { CONFIDENCE, normalized } from "@bobobowis/jev-kit/policy";
98
+ ```
99
+
100
+ | Level | Recipe | Use it for |
101
+ |---|---|---|
102
+ | 1 Single decisions | [`injectionGate`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level01/injection-gate.ts) | A prompt injection gate in front of anything that sends user text to an LLM |
103
+ | | [`urgentGate`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level01/urgency-gate.ts) | Pinging a human only when a message is urgent, with your threshold |
104
+ | | [`classifyTicket`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level01/ticket-classifier.ts) | Routing a ticket to one department, with its confidence |
105
+ | 2 Multiple choice | [`triageTicket`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level02/support-triage.ts) | Route, priority, and a human handoff for a support ticket in one call |
106
+ | | [`screenResume`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level02/resume-screening.ts) | Seniority and fit of a resume against a job description |
107
+ | | [`qualifySponsorForm`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level02/sponsor-qualification.ts) | Qualifying inbound forms or leads by intent and product |
108
+ | 3 Composite scoring | [`ticketPriority`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level03/ticket-priority.ts) | A weighted priority from several scores |
109
+ | | [`codeReviewRisk`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level03/code-review-risk.ts) | The risk of a diff in CI, before a human reviews it |
110
+ | | [`ideaVerdict`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level03/idea-verdict.ts) | Kill, fix, or ship on a pitch or proposal |
111
+ | 4 Confidence gating | [`gateShellCommand`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level04/shell-command-gate.ts) | Allowing a shell command, or asking a human when Jev is unsure |
112
+ | | [`routeAccountAction`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level04/account-actions.ts) | Running an account action on its own, asking for a confirmation before a transfer, and handing off to a human when unsure |
113
+ | | [`checkCitation`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level04/citation-check.ts) | Checking that a quote supports a claim, for RAG answers |
114
+ | 5 Routing | [`routeIntent`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level05/intent-router.ts) | Sending a message to the right handler, and to reasoning only when it needs it |
115
+ | | [`routeModel`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level05/model-router.ts) | The cheapest model and the effort a task needs |
116
+ | | [`routeAgent`](https://github.com/bobobowis/ten-levels-of-jev/blob/main/apps/ten-levels/src/levels/level05/agent-router.ts) | Picking which coding agent takes a task |
117
+
118
+ Levels 6 to 10 need no copying: they ship as `/guards`, `/compact`, `/files`, and `/tools`, and
119
+ behind `jev guard`, `jev setup`, and `jev mcp`.
120
+
121
+ ## The jev command
122
+
123
+ Results go to stdout as JSON, diagnostics to stderr, so stdout pipes straight into `jq`. Every
124
+ command takes `--help`, and the ones that call Jev take `--mock`.
125
+
126
+ **`jev ask`**: one question block about your own state, files code reads for you, and a command's
127
+ output.
128
+
129
+ ```bash
130
+ jev ask --questions '{"risky":{"type":"score","instructions":"How risky is the change in output?","criteria":["Docs only","Some callers","Auth or billing, no tests"]}}' \
131
+ --command "git diff HEAD~1" | jq .answers.risky.score
132
+ jev ask --questions @questions.json --state "customer says export fails" --paths "src/**/*.ts"
133
+ ```
134
+
135
+ **`jev files`**: the same block once per file, in parallel.
136
+
137
+ ```bash
138
+ jev files "src/**/*.ts" --questions '{"todo":{"type":"noul","instructions":"Does this file leave work unfinished?"}}' | jq '.results[] | {path, answers}'
139
+ ```
140
+
141
+ **`jev gate`**: the smart if. The exit code is the answer.
142
+
143
+ ```bash
144
+ jev gate "Is this ticket about billing?" --state "I was charged twice this month" && echo billing
145
+ ```
146
+
147
+ **`jev guard`**: the Level 6 gates on a command or a file write.
148
+
149
+ ```bash
150
+ jev guard bash "git push --force origin main" # block: ... exit 1
151
+ echo 'API_KEY=sk-live-4f9a...' | jev guard write .env # block: ... exit 1
152
+ ```
153
+
154
+ **`jev setup`**, **`jev hook`**, and **`jev mcp`** are below.
155
+
156
+ | Command | Exit codes |
157
+ |---|---|
158
+ | `ask`, `files` | 0 answered, 1 refused or failed (the reason is on stderr), 2 bad options |
159
+ | `gate` | 0 yes (at or above `--threshold`, default 0.5), 1 no, 2 bad options or a state that cannot be sent, 3 Jev could not answer |
160
+ | `guard` | 0 allow, 1 block, 2 bad options, 3 Jev could not answer |
161
+ | `setup` | 0 written (or printed with `--dry-run`), 1 a config it can't safely merge, 2 bad options |
162
+ | `hook` | 0 always, 1 bad options; never 2, which both harnesses read as "block" |
163
+
164
+ `gate` and `guard` exit 3 instead of guessing when Jev cannot answer, so the script decides whether
165
+ that fails open or closed. A pre-commit hook that blocks commits adding a live credential, and lets
166
+ commits through when Jev is unreachable:
167
+
168
+ ```sh
169
+ #!/bin/sh
170
+ # .git/hooks/pre-commit
171
+ jev gate "Does output add a real credential (an API key, token, password, or private key with a real-looking value), not a placeholder?" \
172
+ --command "git diff --cached" --threshold 0.7
173
+ case $? in
174
+ 0) echo "pre-commit: this commit seems to add a credential; remove it or commit with --no-verify." >&2; exit 1 ;;
175
+ 3) echo "pre-commit: Jev unavailable, not checked." >&2 ;;
176
+ esac
177
+ exit 0
178
+ ```
179
+
180
+ ## jev setup
181
+
182
+ Writes or merges the MCP entry and the hooks into a project's agent config, and leaves everything
183
+ else alone:
184
+
185
+ ```bash
186
+ jev setup --harness claude --guards bash,write,result --tools ask_jev # .claude/settings.json, .mcp.json
187
+ jev setup --harness codex --guards bash,write --compact # a marked block in .codex/config.toml
188
+ jev setup --harness claude --level 6 --dry-run # print, write nothing
189
+ jev setup --harness claude --guards bash,write,mcp --ask --allow-read-only # the Level 4 ladder, below
190
+ ```
191
+
192
+ Each run sets the whole selection and replaces only what an earlier run wrote. A settings file that
193
+ is not valid JSON is refused, never overwritten. Codex asks you to trust the project and its hooks
194
+ the first time.
195
+
196
+ ## Use the Jev tools in any MCP client
197
+
198
+ `jev mcp` serves the agent tools over stdio MCP:
199
+
200
+ | Tool | What it answers |
201
+ |---|---|
202
+ | `should_i_compact` | whether now is a good moment to compact (needs the Level 7 hooks' notes) |
203
+ | `ask_jev_file_bool`, `_choice`, `_score` | a yes or no, a pick, or a score about one file the agent never loads |
204
+ | `ask_jev_files`, `pick_first_file` | the same questions over many files in parallel, then which to open first |
205
+ | `ask_jev` | any question block over the agent's own state, file paths, and a command's output |
206
+
207
+ ```bash
208
+ jev mcp --level 8 # the lab's levels 7 to 10
209
+ jev mcp --tools ask_jev,ask_jev_files
210
+ ```
211
+
212
+ Claude Code, in `.mcp.json`:
213
+
214
+ ```json
215
+ { "mcpServers": { "jev": { "command": "jev", "args": ["mcp", "--tools", "ask_jev"] } } }
216
+ ```
217
+
218
+ Codex, in `~/.codex/config.toml` or a trusted project's `.codex/config.toml`:
219
+
220
+ ```toml
221
+ [mcp_servers.jev]
222
+ command = "jev"
223
+ args = ["mcp", "--tools", "ask_jev"]
224
+ default_tools_approval_mode = "approve"
225
+ env_vars = ["TYPESAFE_API_KEY", "OPENROUTER_API_KEY"] # Codex starts MCP servers without your environment
226
+ ```
227
+
228
+ Or let `jev setup` write either one.
229
+
230
+ ## Hooks: guards and the compaction advisor
231
+
232
+ `jev hook` runs as a Claude Code or Codex hook. It reads the hook payload on stdin and answers in the
233
+ harness's own format.
234
+
235
+ - **Guards.** `PreToolUse` runs the bash gate on shell commands and the write gate on file writes
236
+ (`Write`, `Edit`, `MultiEdit`, `NotebookEdit`, Codex's `apply_patch`). A block denies the call.
237
+ `PostToolUse` screens `Read` and `Bash` output for instructions aimed at the agent and attaches a
238
+ banner.
239
+ - **Compaction advisor.** `UserPromptSubmit` and `Stop` ask four questions after each turn and, when
240
+ the work has moved on and context is high, tell the agent on the next prompt to ask you to run
241
+ `/compact`.
242
+
243
+ ```bash
244
+ echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"git push --force origin main"},"cwd":"."}' \
245
+ | jev hook --harness claude --event PreToolUse --guards bash
246
+ ```
247
+
248
+ ### Ask, don't guess: the Level 4 ladder (Claude Code)
249
+
250
+ A block or a pass is two answers. Level 4 adds the third: when Jev is unsure, a human decides. Two
251
+ flags put it on the bash gate and the MCP gate, and both work on their own:
252
+
253
+ - **`--ask`**: short of a block, the bash gate asks you instead of passing when Jev is unsure (effect
254
+ confidence under 0.5), picks irreversible below the block line, leans destructive (0.4 or more),
255
+ or sees the command reach outside the machine (publish, deploy, push, `curl … | sh`).
256
+ - **`--allow-read-only`**: a command or MCP call Jev is sure only reads (above 0.9, no destructive
257
+ or outward lean) is approved, so Claude Code doesn't prompt you for `git status` or a `SELECT`.
258
+
259
+ The **`mcp` guard** runs the same ladder before any tool from another MCP server (`mcp__<server>__*`;
260
+ Jev's own tools are skipped). Destructive, outward, and unsure calls ask you; it never denies,
261
+ because you connected the server, so you decide. It is opt-in: `--level 6` and `jev hook` without
262
+ `--guards` leave it off.
263
+
264
+ What it did on live Jev, with both flags:
265
+
266
+ | Call | Verdict |
267
+ |---|---|
268
+ | `git status`, `grep -rn TODO`, `Neon run_sql SELECT count(*)`, `clerk list_users` | approved: read only (1.00) |
269
+ | `pnpm test`, `git commit`, `mv`, `Neon create_branch`, `Neon run_sql UPDATE ...` | your usual permissions |
270
+ | `npm publish`, `kubectl apply`, `git push`, `curl … \| sh`, `slack send_message` | asks: reaches outside |
271
+ | `git rebase main`, `psql -f migrate.sql` | asks: Jev is unsure (0.27, 0.48) |
272
+ | `Neon run_sql DROP TABLE`, `Neon delete_branch` | asks: destructive |
273
+ | `git push --force`, `git reset --hard`, `rm -rf` | denied, as without the flags |
274
+
275
+ Codex hooks read only deny, so there the flags and the `mcp` guard would do nothing; `jev setup`
276
+ refuses them for Codex rather than write hooks that don't act.
277
+
278
+ Hooks fail open: a missing key, a Jev error, or a timeout (8 s by default) allows the action and
279
+ reports an error. They never exit 2. The contracts they rely on are in
280
+ [docs/NOTES-claude.md](docs/NOTES-claude.md) and [docs/NOTES-codex.md](docs/NOTES-codex.md).
281
+
282
+ ## Support by harness
283
+
284
+ The same table is exported as `CAPABILITIES` from `@bobobowis/jev-kit/capabilities`.
285
+
286
+ <!-- jev:matrix (generated by `pnpm run docs` from packages/jev-kit/src/capabilities.ts) -->
287
+
288
+ | Level | pi | Claude Code | Codex | Generic MCP |
289
+ |---|---|---|---|---|
290
+ | 6 A Bash gate | full | full | full | unavailable (an MCP client has no hooks) |
291
+ | 6 B Write gate | full | full | full (apply_patch is read from the patch text) | unavailable (an MCP client has no hooks) |
292
+ | 6 C Result screen | full | degraded (the banner is added as context, not above the output) | degraded (the banner is added as context, not above the output) | unavailable (an MCP client has no hooks) |
293
+ | 7 A Turn end hook | full | degraded (advisory after each turn, delivered on the next prompt; no compact_now) | degraded (advisory after each turn, delivered on the next prompt; no compact_now) | unavailable (an MCP client has no turn end hook) |
294
+ | 7 B On demand tool | full | degraded (no compact_now; the agent asks you to run /compact) | degraded (no compact_now; Codex can't compact from a tool) | degraded (no compact_now) |
295
+ | 7 C Pick the cut point | full | unavailable (Claude Code doesn't let a hook choose where to cut) | unavailable (Codex doesn't let a hook choose where to cut) | unavailable (an MCP client has no compaction hook) |
296
+ | 8 A ask_jev_file_bool | full | full | full (the built-in shell can't be removed) | full |
297
+ | 8 B ask_jev_file_choice | full | full | full (the built-in shell can't be removed) | full |
298
+ | 8 C ask_jev_file_score | full | full | full (the built-in shell can't be removed) | full |
299
+ | 9 A Several paths, one block | full | full | full (the built-in shell can't be removed) | full |
300
+ | 9 B A glob over a directory | full | full | full (the built-in shell can't be removed) | full |
301
+ | 9 C Recursive, then pick first | full | full | full (the built-in shell can't be removed) | full |
302
+ | 10 A Triage a failure | full | full | full (the built-in shell can't be removed) | full |
303
+ | 10 B Judge a diff before shipping | full | full | full (the built-in shell can't be removed) | full |
304
+ | 10 C Classify a request, gate a plan | full | full | full (the built-in shell can't be removed) | full |
305
+
306
+ <!-- /jev:matrix -->
307
+
308
+ ## Events
309
+
310
+ Every Jev call and hook decision is reported as one JSON line: to `$JEV_EVENT_DIR/<harness>-<pid>.jsonl`
311
+ when that is set, otherwise to stderr prefixed `JEV_EVENT `. The lab uses this to show hooks and
312
+ tool calls live. `ask`, `files`, `gate`, and `guard` keep stderr quiet unless `JEV_EVENT_DIR` is set.
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What each agent level and option can do in each harness. The lab serves it with every level and
3
+ * the READMEs' table is generated from it, so the docs and the UI can't disagree.
4
+ * full: behaves as the level teaches it.
5
+ * degraded: runs, with the difference in the note.
6
+ * unavailable: the harness has no place for it; the lab won't start it.
7
+ */
8
+ export type Harness = "pi" | "claude" | "codex" | "mcp";
9
+ export type Status = "full" | "degraded" | "unavailable";
10
+ export interface Support {
11
+ status: Status;
12
+ note?: string;
13
+ }
14
+ export type OptionKey = "A" | "B" | "C";
15
+ export declare const HARNESS_LABELS: Record<Harness, string>;
16
+ export declare const HARNESS_ORDER: Harness[];
17
+ export declare const CAPABILITIES: Record<number, {
18
+ name: string;
19
+ options: Record<OptionKey, {
20
+ name: string;
21
+ support: Record<Harness, Support>;
22
+ }>;
23
+ }>;
24
+ /** One option's support in one harness; levels without hooks or tools (1 to 5) are full everywhere. */
25
+ export declare function capability(level: number, option: string, harness: Harness): Support;
26
+ /** Every option's support for a level, or null for a level that doesn't run in an agent. */
27
+ export declare function levelCapabilities(level: number): Record<OptionKey, Record<Harness, Support>> | null;
28
+ /** The matrix as a Markdown table, the one the READMEs carry between their jev:matrix markers. */
29
+ export declare function matrixMarkdown(): string;
30
+ export declare const MATRIX_START = "<!-- jev:matrix (generated by `pnpm run docs` from packages/jev-kit/src/capabilities.ts) -->";
31
+ export declare const MATRIX_END = "<!-- /jev:matrix -->";
32
+ /** Replace the table between the markers, in the text's own line endings; unchanged without markers. */
33
+ export declare function withMatrix(text: string): string;
@@ -0,0 +1,81 @@
1
+ export const HARNESS_LABELS = {
2
+ pi: "pi", claude: "Claude Code", codex: "Codex", mcp: "Generic MCP",
3
+ };
4
+ export const HARNESS_ORDER = ["pi", "claude", "codex", "mcp"];
5
+ const full = (note) => (note ? { status: "full", note } : { status: "full" });
6
+ const degraded = (note) => ({ status: "degraded", note });
7
+ const unavailable = (note) => ({ status: "unavailable", note });
8
+ const NO_HOOKS = unavailable("an MCP client has no hooks");
9
+ const ADVISORY = "advisory after each turn, delivered on the next prompt; no compact_now";
10
+ const TOOLS_FULL = {
11
+ pi: full(), claude: full(), codex: full("the built-in shell can't be removed"), mcp: full(),
12
+ };
13
+ export const CAPABILITIES = {
14
+ 6: { name: "Guardrail hooks", options: {
15
+ A: { name: "Bash gate", support: { pi: full(), claude: full(), codex: full(), mcp: NO_HOOKS } },
16
+ B: { name: "Write gate", support: { pi: full(), claude: full(), codex: full("apply_patch is read from the patch text"), mcp: NO_HOOKS } },
17
+ C: { name: "Result screen", support: {
18
+ pi: full(), claude: degraded("the banner is added as context, not above the output"),
19
+ codex: degraded("the banner is added as context, not above the output"), mcp: NO_HOOKS,
20
+ } },
21
+ } },
22
+ 7: { name: "Should I compact", options: {
23
+ A: { name: "Turn end hook", support: { pi: full(), claude: degraded(ADVISORY), codex: degraded(ADVISORY), mcp: unavailable("an MCP client has no turn end hook") } },
24
+ B: { name: "On demand tool", support: { pi: full(), claude: degraded("no compact_now; the agent asks you to run /compact"), codex: degraded("no compact_now; Codex can't compact from a tool"), mcp: degraded("no compact_now") } },
25
+ C: { name: "Pick the cut point", support: {
26
+ pi: full(), claude: unavailable("Claude Code doesn't let a hook choose where to cut"),
27
+ codex: unavailable("Codex doesn't let a hook choose where to cut"), mcp: unavailable("an MCP client has no compaction hook"),
28
+ } },
29
+ } },
30
+ 8: { name: "Cheap reads", options: {
31
+ A: { name: "ask_jev_file_bool", support: TOOLS_FULL },
32
+ B: { name: "ask_jev_file_choice", support: TOOLS_FULL },
33
+ C: { name: "ask_jev_file_score", support: TOOLS_FULL },
34
+ } },
35
+ 9: { name: "Files at scale", options: {
36
+ A: { name: "Several paths, one block", support: TOOLS_FULL },
37
+ B: { name: "A glob over a directory", support: TOOLS_FULL },
38
+ C: { name: "Recursive, then pick first", support: TOOLS_FULL },
39
+ } },
40
+ 10: { name: "Agentic Jev", options: {
41
+ A: { name: "Triage a failure", support: TOOLS_FULL },
42
+ B: { name: "Judge a diff before shipping", support: TOOLS_FULL },
43
+ C: { name: "Classify a request, gate a plan", support: TOOLS_FULL },
44
+ } },
45
+ };
46
+ /** One option's support in one harness; levels without hooks or tools (1 to 5) are full everywhere. */
47
+ export function capability(level, option, harness) {
48
+ return CAPABILITIES[level]?.options[option]?.support[harness] ?? full();
49
+ }
50
+ /** Every option's support for a level, or null for a level that doesn't run in an agent. */
51
+ export function levelCapabilities(level) {
52
+ const l = CAPABILITIES[level];
53
+ if (!l)
54
+ return null;
55
+ return { A: l.options.A.support, B: l.options.B.support, C: l.options.C.support };
56
+ }
57
+ const cell = (s) => (s.note ? `${s.status} (${s.note})` : s.status);
58
+ /** The matrix as a Markdown table, the one the READMEs carry between their jev:matrix markers. */
59
+ export function matrixMarkdown() {
60
+ const rows = [
61
+ `| Level | ${HARNESS_ORDER.map((h) => HARNESS_LABELS[h]).join(" | ")} |`,
62
+ `|---|${HARNESS_ORDER.map(() => "---").join("|")}|`,
63
+ ];
64
+ for (const [level, l] of Object.entries(CAPABILITIES)) {
65
+ for (const [key, o] of Object.entries(l.options)) {
66
+ rows.push(`| ${level} ${key} ${o.name} | ${HARNESS_ORDER.map((h) => cell(o.support[h])).join(" | ")} |`);
67
+ }
68
+ }
69
+ return rows.join("\n");
70
+ }
71
+ export const MATRIX_START = "<!-- jev:matrix (generated by `pnpm run docs` from packages/jev-kit/src/capabilities.ts) -->";
72
+ export const MATRIX_END = "<!-- /jev:matrix -->";
73
+ /** Replace the table between the markers, in the text's own line endings; unchanged without markers. */
74
+ export function withMatrix(text) {
75
+ const a = text.indexOf(MATRIX_START), b = text.indexOf(MATRIX_END);
76
+ if (a < 0 || b < a)
77
+ return text;
78
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
79
+ const table = matrixMarkdown().split("\n").join(eol);
80
+ return `${text.slice(0, a + MATRIX_START.length)}${eol}${eol}${table}${eol}${eol}${text.slice(b)}`;
81
+ }
@@ -0,0 +1,9 @@
1
+ import { type Io } from "./common.ts";
2
+ export declare const ASK_HELP = "Usage: jev ask --questions <json|@file> [state options]\n\nPrints { answers, state_summary, usage, model } as JSON. Exit 0, 1 when the call is refused or fails, 2 for bad options.\n\n --questions <json|@file> the question block, keyed by question id\n --state <text|json> your own state, plain text or a JSON object\n --state-file <file> the same, read from a file\n --paths <glob> files for code to read into files[\"path\"]; repeat for more (up to 20 files)\n --command <cmd> a command for code to run; its result goes into output (passes the bash gate)\n --cwd <dir> where paths resolve and the command runs (default: here)\n --mock the offline mock instead of a Jev key\n";
3
+ export declare const FILES_HELP = "Usage: jev files <glob|dir|file>... --questions <json|@file> [--recursive] [--cwd dir] [--mock]\n\nAsks the question block once per file, in parallel. Prints { results, skipped, calls } as JSON.\n";
4
+ export declare const GATE_HELP = "Usage: jev gate \"<yes or no question>\" [state options] [--threshold 0.5] [--yes <text>] [--no <text>] [--json]\n\nExit 0 when the probability of yes is at or above the threshold, 1 below it, 2 for bad options or a\nstate that cannot be sent, 3 when Jev could not answer. The script chooses whether 3 fails open or closed.\n\n --threshold <0..1> default 0.5\n --yes <text> what counts as yes\n --no <text> what counts as no\n --json also print { noul, threshold, yes } on stdout\n --state <text|json> your own state, plain text or a JSON object\n --state-file <file> the same, read from a file\n --paths <glob> files for code to read into files[\"path\"]; repeat for more (up to 20 files)\n --command <cmd> a command for code to run; its result goes into output (passes the bash gate)\n --cwd <dir> where paths resolve and the command runs (default: here)\n --mock the offline mock instead of a Jev key\n";
5
+ export declare const GUARD_HELP = "Usage: jev guard bash \"<command>\" | jev guard write <path> < content [--cwd dir] [--json] [--mock]\n\nThe Level 6 gates. Prints allow or block with the reason. Exit 0 allow, 1 block, 2 bad options, 3 Jev error.\n";
6
+ export declare function ask(argv: string[], io: Io): Promise<number>;
7
+ export declare function files(argv: string[], io: Io): Promise<number>;
8
+ export declare function gate(argv: string[], io: Io): Promise<number>;
9
+ export declare function guard(argv: string[], io: Io): Promise<number>;