@jkudish/jev-mcp 0.3.0 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ - New `jev_review` tool: score a proposed diff against the request before the task is called done. Four 0..2 Score rubrics (correctness, spec match, test gap, blast radius, the last two inverted in the composite) plus a safe-to-apply probability, combined into a weighted composite (0.4 / 0.3 / 0.15 / 0.15) and one `auto | review | escalate` action. Auto requires safe_to_apply and every rubric confidence at `auto_accept` (default 0.8) and the composite at `composite_floor` (default 0.7); below `review_at` escalates. Unknown confidence escalates and can never satisfy a threshold, even a zero one. Text fields capped at 50,000 characters; truncated or malformed input never returns auto. Question design adapted from [burnigtm/jev-mcp](https://github.com/burnigtm/jev-mcp) (MIT) via [PR #2](https://github.com/jkudish/jev-mcp/pull/2) by rimusz, with deliberate divergences: caps raised from 2,000 characters so auto is reachable on real diffs, thresholds parameterized instead of a hardcoded safe_to_apply escalation, strict per-answer validation under the package's invalid_response contract, and anti-injection framing (every field is evidence to evaluate, never instructions to follow).
6
+ - New `jev_gate` tool: the completion gate. The same patch review plus up to 16 completion claims verified against caller-supplied evidence in one request, each claim a Choice over verified / contradicted / unsupported, with claim questions instructed to use evidence only (the request, diff, and tests fields are assertions to check, never proof; all fields share one model state, so the isolation is instructional). Evidence is capped at 16 items and 200,000 characters in aggregate, rejected before any model call. The final action is the worst of the review and every claim; `reason_codes` records why (`incomplete_context`, `invalid_response`, `review_escalated`, `review_required`, `claims_contradicted`, `claims_unsupported`, `claim_confidence_low`, `claim_confidence_below_auto_accept`, `accepted`). Same credits and divergences as `jev_review`.
7
+ - Deliberately excluded from that PR: its `jev_coding_loop` tool, an agent loop that runs the review gate repeatedly. This package stays judgment primitives that a coding agent calls; it does not become one.
8
+ - Docs: README restructured around the ten tools (capability bullets, collapsible per-client install sections, a When to call which tool section), the `jev_rerank` example replaced with a live-captured semantic-ranking example that shows where candidate text comes from, and guidance added for ranking whole documents by chunking with distinct ids.
9
+ - New `jev_rerank` tool: one independent relevance probability per candidate in a single request, returned sorted with scores. Candidate ids preserved verbatim; any malformed answer marks the whole ranking `invalid_response` instead of sorting a missing score as zero. TypeSafe's rerank cookbook reports that on the CLERC benchmark this pattern lifted top-1 from 5% to 18% and top-10 from 38% to 62%. Bounded at 250 candidates and a 100,000-character aggregate budget.
10
+ - New `jev_compare` tool: overall same_fact / contradicts / different_facts relation between two passages, plus optional per-aspect judgments, each an independent Choice in the same request with aspect-specific wording for the third outcome, with distributions, confidence, an argmax guard, and an auto-versus-review decision.
11
+ - New `jev_extract` tool: your regex finds candidate substrings, Jev picks which candidate is each field's value, and the value returns verbatim. Regexes run in a sandboxed worker with a 1-second deadline, so pathological patterns cannot hang the server. Complete zero-match fields are omitted from the model request (`not_found`); if no field reaches the model, no API call is made, and a confident `none_of_them` is a model-judged negative. An incomplete candidate universe (matches beyond the 20-per-field cap, or matches skipped for exceeding 2,000 characters) forces every otherwise-valid positive or `none_of_them` result to `review` (malformed answers remain `invalid_response`), with explicit flags rather than silent truncation; invalid patterns and timeouts report `invalid_pattern` without failing the call. Bounded at 32 fields, a 50,000-character document, and a separate 50,000-character aggregate candidate-match budget.
12
+
13
+ ## 0.4.0
14
+
15
+ - New `jev_classify` tool: assign up to 64 items to a shared catalog of up to 250 classes in one batched request (catalog sent once, one Choice per item). Per-item distribution, confidence, winner margin, and an auto-versus-review decision gated on both top probability (default 0.85) and margin (default 0.5). Caller IDs are preserved verbatim (opaque internal keys on the wire); the catalog is sent once in shared state with each item in its own question; malformed responses surface as invalid_response rather than uncertainty; batches are capped at an 8,000 item-class budget.
16
+
3
17
  ## 0.3.0
4
18
 
5
19
  - Cloudflare Workers AI support: with `CLOUDFLARE_API_TOKEN` (or `JEV_CLOUDFLARE_API_TOKEN`) and `CLOUDFLARE_ACCOUNT_ID` set, judgments run through Cloudflare at the `typesafe/jev` alias; `JEV_PROVIDER=cloudflare` forces it. Usage tokens are reported on every call.
package/README.md CHANGED
@@ -5,13 +5,33 @@
5
5
 
6
6
  Fast, cheap, typed judgments from TypeSafe's Jev model, as MCP tools.
7
7
 
8
- Give your agent three judgment tools: `jev_verify` checks claims against evidence, `jev_screen` judges content before it enters context, `jev_find` ranks candidates by meaning with no embeddings. Each call returns verdicts with probability distributions and confidence in roughly 150 to 500 ms, for a fraction of a cent. The cheap mechanical checks agents otherwise skip, because a frontier model is too slow to run on every page, claim, or candidate list.
9
-
10
- Things it has done in real use:
11
-
12
- - Caught a contradicted claim at confidence 1.0 against a city ordinance.
13
- - Blocked a pricing page carrying a hidden "ignore your instructions" note at injection probability 0.99, while still reading it as a real page.
14
- - Ranked three files for "how caching affects infrastructure costs" and picked the right one at probability 1.0.
8
+ Give your agent ten judgment tools:
9
+
10
+ - `jev_verify` checks claims against evidence.
11
+ - `jev_screen` judges content before it enters context.
12
+ - `jev_find` picks the best candidate by meaning.
13
+ - `jev_rerank` scores and sorts every candidate.
14
+ - `jev_classify` batch-assigns items to classes.
15
+ - `jev_decide` settles bounded alternatives.
16
+ - `jev_compare` judges how two passages relate.
17
+ - `jev_extract` pulls field values with regex plus judgment.
18
+ - `jev_review` scores a proposed diff before the task is called done.
19
+ - `jev_gate` reviews a patch and verifies completion claims in one call.
20
+
21
+ Each judgment comes back typed: probabilities, and for most tools a confidence score, in roughly 150 to 500 ms, for a fraction of a cent. The cheap mechanical checks agents otherwise skip, because a frontier model is too slow to run on every page, claim, or candidate list.
22
+
23
+ What you can use it for (the use cases are endless; these are just examples):
24
+
25
+ - Fact-check a report, PR description, or agent brief against the sources it cites, claim by claim.
26
+ - Screen a fetched page for injected instructions before it enters context, and skip pages with nothing to say.
27
+ - Find which document, file, or note answers a question, across hundreds of candidates, with no embeddings and no index to maintain.
28
+ - Rerank retrieval results, triage near-duplicates, or order a feed by relevance.
29
+ - Route support messages, label issues, or sort an inbox against your own label set, in batches.
30
+ - Choose between a handful of options with evidence and priorities in view, with an explicit ask-the-user escape hatch when it cannot decide.
31
+ - Reconcile a changelog against its docs, a summary against its source, or catch two pages that disagree about a price or a date.
32
+ - Pull prices, dates, versions, and IDs out of a page or document as verbatim strings the model found but never wrote.
33
+ - Score a proposed diff for correctness, spec match, test gap, and blast radius before your agent declares the task done.
34
+ - Gate a merge or a ship on completion claims: the patch review and every "tests pass" claim checked against the evidence actually supplied.
15
35
 
16
36
  This is early software. Expect rough edges. Issues and pull requests are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).
17
37
 
@@ -38,19 +58,26 @@ From npm:
38
58
  npx -y @jkudish/jev-mcp
39
59
  ```
40
60
 
41
- ### Amp
61
+ <details>
62
+ <summary>Amp</summary>
42
63
 
43
64
  ```bash
44
65
  amp mcp add jev -- npx -y @jkudish/jev-mcp
45
66
  ```
46
67
 
47
- ### Claude Code
68
+ </details>
69
+
70
+ <details>
71
+ <summary>Claude Code</summary>
48
72
 
49
73
  ```bash
50
74
  claude mcp add jev -- npx -y @jkudish/jev-mcp
51
75
  ```
52
76
 
53
- ### Codex (`~/.codex/config.toml`)
77
+ </details>
78
+
79
+ <details>
80
+ <summary>Codex (<code>~/.codex/config.toml</code>)</summary>
54
81
 
55
82
  ```toml
56
83
  [mcp_servers.jev]
@@ -58,7 +85,10 @@ command = "npx"
58
85
  args = ["-y", "@jkudish/jev-mcp"]
59
86
  ```
60
87
 
61
- ### OpenCode (`opencode.json`)
88
+ </details>
89
+
90
+ <details>
91
+ <summary>OpenCode (<code>opencode.json</code>)</summary>
62
92
 
63
93
  ```json
64
94
  {
@@ -72,7 +102,10 @@ args = ["-y", "@jkudish/jev-mcp"]
72
102
  }
73
103
  ```
74
104
 
75
- ### Any other MCP client
105
+ </details>
106
+
107
+ <details>
108
+ <summary>Any other MCP client</summary>
76
109
 
77
110
  ```json
78
111
  {
@@ -86,6 +119,8 @@ args = ["-y", "@jkudish/jev-mcp"]
86
119
  }
87
120
  ```
88
121
 
122
+ </details>
123
+
89
124
  Some MCP clients filter the environment before spawning servers, which silently drops `TYPESAFE_API_KEY`. If the server reports a missing key, pass it explicitly as shown above.
90
125
 
91
126
  ## The tools
@@ -180,15 +215,298 @@ Rank candidates against a plain-language query. No embeddings, no index to maint
180
215
  - Up to 250 candidates per call. Candidate texts are truncated at 2,000 characters.
181
216
  - Pattern from the [semantic-find cookbook](https://docs.typesafe.ai/cookbooks/semantic_find).
182
217
 
218
+ ### jev_classify
219
+
220
+ Assign each item to one class from a shared catalog, in one batched request: the catalog is sent once and every item becomes an independent Choice question. Designed for labeling many documents, messages, or records against a stable label set.
221
+
222
+ ```jsonc
223
+ // arguments
224
+ {
225
+ "purpose": "Route support messages",
226
+ "items": [
227
+ { "id": "m1", "text": "I was charged twice for my subscription this month." },
228
+ { "id": "m3", "text": "Do you have a student discount?" }
229
+ ],
230
+ "classes": [
231
+ { "id": "billing", "description": "Payments, invoices, refunds, subscription charges" },
232
+ { "id": "sales", "description": "Pricing questions, discounts, upgrade inquiries" }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ ```jsonc
238
+ // live result, abridged: 4 items classified in one call for 669 input tokens
239
+ {
240
+ "summary": { "items": 4, "auto": 4, "review": 0, "by_class": { "billing": 1, "technical": 2, "sales": 1 } },
241
+ "results": [
242
+ { "id": "m1", "classification": "billing", "margin": 1.0, "confidence": 1, "decision": "auto" }
243
+ ]
244
+ }
245
+ ```
246
+
247
+ - Auto-acceptance requires both a top probability at or above `auto_accept` (default `0.85`) and a winner-to-runner-up `margin` at or above `minimum_margin` (default `0.5`); conservative by design, based on classification spike testing where choice wording swayed uncertain cases.
248
+ - Include a `manual_review` class in your catalog if you want an explicit escape hatch; the tool never invents one.
249
+ - Class descriptions carry the decision. Strong ones state a precise definition, what belongs, what does not, precedence over overlapping classes, and a short example.
250
+ - Up to 250 classes and 64 items per call, with an 8,000 item-class budget per batch (split larger waves into multiple calls); item text is truncated at 2,000 characters.
251
+ - A malformed or incomplete model response is reported as `status: invalid_response` on that item, never as model uncertainty.
252
+
253
+ ### jev_decide
254
+
255
+ One bounded decision, 2-6 candidates, evidence, and explicit priorities. Jev returns a Choice distribution over the candidates plus escape hatches, and a per-candidate per-requirement check, in one request.
256
+
257
+ ```jsonc
258
+ // arguments
259
+ {
260
+ "decision": "Choose the report status update channel.",
261
+ "evidence": "Polling updates within 30 seconds. Managed push updates within one second but adds a paid vendor.",
262
+ "priorities": "The user accepts 30 seconds and prioritizes no new paid services.",
263
+ "candidates": [
264
+ { "id": "poll", "description": "Poll the existing authenticated endpoint." },
265
+ { "id": "push", "description": "Add the managed push service." }
266
+ ],
267
+ "requirements": ["No new paid service is needed."]
268
+ }
269
+ ```
270
+
271
+ ```jsonc
272
+ // live result, abridged
273
+ {
274
+ "recommendation": { "selected": "poll", "escaped": false, "confidence": 1,
275
+ "probabilities": { "poll": 1, "push": 0, "ask_user": 0 } },
276
+ "checks": [ { "candidate": "poll", "requirement": 0, "answer": "supported" },
277
+ { "candidate": "push", "requirement": 0, "answer": "contradicted" } ]
278
+ }
279
+ ```
280
+
281
+ - Escape hatches (`ask_user`, `investigate`, `none`) let the model decline to rank when a preference or fact is missing; `escaped: true` in the result marks it. Disable with `escape_hatches: false` for closed-world choices.
282
+ - Requirement checks run as independent questions in the same request and may disagree with the recommendation; a contradiction on the recommended candidate surfaces as a warning.
283
+ - One call per unchanged decision. Repeat only with materially new evidence or criteria.
284
+
285
+ <sub>Pattern credit: [thesammykins/jev_ampcode](https://github.com/thesammykins/jev_ampcode).</sub>
286
+
287
+ ### jev_rerank
288
+
289
+ Score every candidate's relevance to a query and get them back sorted. You bring the candidates (file contents, database rows, search hits); Jev scores and sorts what you hand it. Unlike `jev_find`, which picks one best answer, rerank gives each candidate its own relevance probability, so the whole ordering survives. TypeSafe's rerank cookbook reports that on the CLERC benchmark this pattern lifted top-1 from 5% to 18% and top-10 from 38% to 62%.
290
+
291
+ ```jsonc
292
+ // arguments
293
+ {
294
+ "query": "why did our bandwidth charges triple",
295
+ "candidates": [
296
+ {
297
+ "id": "infra/main.tf",
298
+ "text": "resource \"aws_instance\" \"api\" {\n count = 3 # always-on\n instance_type = \"m5.large\"\n}"
299
+ },
300
+ {
301
+ "id": "src/cache.ts",
302
+ "text": "// CDN cache control\nexport const CDN_TTL_SECONDS = 60; // was 86400 until the perf sprint"
303
+ },
304
+ {
305
+ "id": "docs/runbook.md",
306
+ "text": "# On-call runbook\n\nEscalation contacts and the weekly rotation schedule."
307
+ }
308
+ ]
309
+ }
310
+ ```
311
+
312
+ ```jsonc
313
+ // live result, abridged
314
+ {
315
+ "ranked": [
316
+ { "rank": 1, "id": "src/cache.ts", "relevance": 0.74 },
317
+ { "rank": 2, "id": "infra/main.tf", "relevance": 0.23 },
318
+ { "rank": 3, "id": "docs/runbook.md", "relevance": 0.03 }
319
+ ]
320
+ }
321
+ ```
322
+
323
+ Each candidate is a file: `id` is any handle you choose, echoed back verbatim, and `text` is the file's contents (truncated at 2,000 characters). No candidate contains the words bandwidth or triple. A shorter CDN TTL means more origin fetches, so `src/cache.ts` ranks first on meaning alone; the always-on VMs are cloud spend too, just not bandwidth.
324
+
325
+ - One relevance probability per candidate, all in a single request; cost scales with the number of candidates, not with candidate-pairs.
326
+ - Candidate ids are preserved verbatim. If any answer comes back malformed, the whole ranking is reported `invalid_response` rather than sorting a missing score as a confident zero.
327
+ - Up to 250 candidates and a 100,000-character aggregate budget; split larger batches.
328
+ - Ranking whole documents? Chunk them into ~2,000-character candidates with distinct ids (`report.md#c1`, `report.md#c2`) and merge per document by its best chunk's score.
329
+ - Use `jev_find` when you want one best answer plus an existence check; use `jev_rerank` when the ordering itself is the deliverable. See the [rerank cookbook](https://docs.typesafe.ai/cookbooks/rerank_typesafe).
330
+
331
+ ### jev_compare
332
+
333
+ Judge how two passages relate: `same_fact`, `contradicts`, or `different_facts`, with the full probability distribution, confidence, and an auto-versus-review decision. Supply optional aspects (price, launch date, method) and each gets its own independent judgment in the same request.
334
+
335
+ ```jsonc
336
+ // arguments
337
+ {
338
+ "passage_a": "The Pro plan costs $29 per month and includes unlimited builds.",
339
+ "passage_b": "The Pro plan is priced at $59 per month. All plans include unlimited builds.",
340
+ "aspects": ["price", "build limits"]
341
+ }
342
+ ```
343
+
344
+ ```jsonc
345
+ // live result, abridged
346
+ {
347
+ "overall": { "relation": "contradicts", "confidence": 1, "decision": "auto" },
348
+ "aspects": [
349
+ { "aspect": "price", "relation": "contradicts", "decision": "auto" },
350
+ { "aspect": "build limits", "relation": "same_fact", "decision": "auto" }
351
+ ]
352
+ }
353
+ ```
354
+
355
+ - Per-aspect judgments are independent and may disagree with the overall relation; that disagreement is signal, not noise.
356
+ - Each passage is capped at 20,000 characters; requests above that are rejected up front.
357
+ - At aspect granularity, `different_facts` explicitly means the passages do not both make a comparable assertion about the aspect: at least one does not address it, or their mentions do not overlap.
358
+ - The request supplies no evidence beyond the two passages, so a `same_fact` verdict means they agree with each other, not that they are true.
359
+ - Use for source reconciliation, changelog-versus-code drift, or checking that a summary matches its source.
360
+
361
+ ### jev_extract
362
+
363
+ Pull structured fields out of a document with your regex and Jev's judgment. Your regex finds candidate substrings in code, Jev picks which candidate is the field's real value, and the value comes back verbatim, exactly as it appears in the document, never model-generated.
364
+
365
+ ```jsonc
366
+ // arguments
367
+ {
368
+ "document": "Starter is $9/mo. Pro is $29/mo. Enterprise: contact sales. Version 3.2.1 released 2024-06-01. The early-bird launch price for Pro was $19/mo.",
369
+ "fields": [
370
+ { "id": "price_pro", "pattern": "\\$\\d+", "description": "The current monthly price of the Pro plan in US dollars" },
371
+ { "id": "version", "pattern": "\\d+\\.\\d+\\.\\d+", "description": "The release version number of the software" }
372
+ ]
373
+ }
374
+ ```
375
+
376
+ ```jsonc
377
+ // live result, abridged
378
+ {
379
+ "results": [
380
+ { "id": "price_pro", "value": "$29", "status": "auto", "candidates_considered": 3,
381
+ "candidates_truncated": false, "matches_skipped_too_long": 0 },
382
+ { "id": "version", "value": "3.2.1", "status": "auto", "candidates_considered": 1,
383
+ "candidates_truncated": false, "matches_skipped_too_long": 0 }
384
+ ]
385
+ }
386
+ ```
387
+
388
+ - A field whose regex matches nothing comes back `not_found` with reason `no_regex_matches` and never reaches the model: no hallucinated value. In a call where every field is a zero-match, no API call is made at all. Jev can also pick `none_of_them` when every regex match is wrong for the field; that `not_found` is model-judged and gated on top probability and winner margin like any pick.
389
+ - Values are verbatim document substrings, exactly as the regex matched them. The model picks among matches; it never writes a value.
390
+ - Ambiguous picks come back flagged `review` with the value still attached; treat a `review` value as provisional, not extracted. If the regex found more matches than the cap allows, or skipped matches longer than 2,000 characters, the field can never be `auto` and a `none_of_them` pick can never be a definite `not_found`: it returns `review` with reason `candidate_limit` and `candidates_truncated` or `matches_skipped_too_long` set, because the best value may be among the unsent matches. When every match is over 2,000 characters and none is eligible at all, the reason is `matches_too_long` instead. A malformed model answer is still `invalid_response`, not a semantic outcome.
391
+ - Invalid patterns and regexes that time out (they run in a sandboxed worker with a 1-second deadline, so a pathological pattern cannot hang the server) return `invalid_pattern` with the error instead of failing the whole call.
392
+ - Up to 32 fields per call and 20 candidate matches per field, judged in one request. The document is capped at 50,000 characters, and the candidate match text at 50,000 characters in aggregate.
393
+
394
+ ### jev_review
395
+
396
+ Score a proposed diff against the request before the task is called done. Jev answers four rubric questions, correctness, spec match, test gap, and blast radius, each 0..2, plus one safe-to-apply probability; the server combines them into a weighted composite and one action: `auto`, `review`, or `escalate`. It judges what you hand it. It never runs tests and never applies the patch.
397
+
398
+ ```jsonc
399
+ // arguments
400
+ {
401
+ "request": "Our CLI reads a JSON config from stdin. It crashed on empty input. Make it tolerate an empty document and any whitespace-only document, returning our zero-value config instead.",
402
+ "diff": "--- a/src/parse.ts\n+++ b/src/parse.ts\n@@ def parse(stdin) @@\n- return JSON.parse(stdin);\n+ const trimmed = stdin.trim();\n+ if (trimmed === \"\") return zeroConfig();\n+ return JSON.parse(trimmed);",
403
+ "tests": "node --test: 2 passed, 1 failing (parse: invalid JSON still rejects)"
404
+ }
405
+ ```
406
+
407
+ ```jsonc
408
+ // live result, abridged
409
+ {
410
+ "action": "escalate",
411
+ "composite": 0.756,
412
+ "safe_to_apply": 0.24,
413
+ "scores": {
414
+ "correctness": { "score": 1.51, "confidence": 0.27 },
415
+ "spec_match": { "score": 1.65, "confidence": 0.47 },
416
+ "test_gap": { "score": 0.80, "confidence": 0.14 },
417
+ "blast_radius":{ "score": 0.45, "confidence": 0.32 }
418
+ },
419
+ "weights": { "correctness": 0.4, "spec_match": 0.3, "test_gap": 0.15, "blast_radius": 0.15 },
420
+ "thresholds": { "auto_accept": 0.8, "review_at": 0.5, "composite_floor": 0.7 },
421
+ "truncated": false,
422
+ "usage": { "input_tokens": 855, "output_tokens": 79 }
423
+ }
424
+ ```
425
+
426
+ Here the composite clears the floor, but the failing test drags `safe_to_apply` to 0.24 and rubric confidences sit under `review_at`, so the patch escalates instead of sailing through on its decent scores.
427
+
428
+ - Rubric scores run 0..2. Higher is better for `correctness` and `spec_match`; higher is worse for `test_gap` and `blast_radius`, and the composite inverts those two before weighting, so a composite of 1.0 means favorable on every rubric.
429
+ - `auto` requires `safe_to_apply` and every rubric confidence at `auto_accept` and the composite at `composite_floor`. `safe_to_apply` or any rubric confidence below `review_at`, or unknown, escalates; a composite below `composite_floor` returns `review`, not `escalate`. Unknown confidence counts as escalate, never as a value that can satisfy a threshold.
430
+ - `request` frames the review; it is not proof of anything. Put real output in `tests`. Every field is treated as evidence to evaluate, never instructions to follow.
431
+ - Each text field is capped at 50,000 characters. Truncated input sets `truncated: true` and can never return `auto`; a malformed answer is `invalid_response`, not a semantic outcome.
432
+
433
+ <sub>Adapted from [burnigtm/jev-mcp](https://github.com/burnigtm/jev-mcp) (MIT), via [PR #2](https://github.com/jkudish/jev-mcp/pull/2) by rimusz.</sub>
434
+
435
+ ### jev_gate
436
+
437
+ The completion gate: the same patch review as `jev_review`, plus your completion claims verified against evidence you supply, in one call. Auto only when the review is accepted and every claim is verified at or above `auto_accept`; a confidently contradicted claim escalates. The request and the claims are assertions to check, never proof.
438
+
439
+ ```jsonc
440
+ // arguments
441
+ {
442
+ "request": "Our CLI reads a JSON config from stdin. It crashed on empty input. Make it tolerate an empty document and any whitespace-only document, returning our zero-value config instead.",
443
+ "diff": "--- a/src/parse.ts\n+++ b/src/parse.ts\n@@ def parse(stdin) @@\n- return JSON.parse(stdin);\n+ const trimmed = stdin.trim();\n+ if (trimmed === \"\") return zeroConfig();\n+ return JSON.parse(trimmed);",
444
+ "claims": [
445
+ "The empty-input parser test passed.",
446
+ "Whitespace-only input is also handled.",
447
+ "The full test suite passes with no failures."
448
+ ],
449
+ "evidence": [
450
+ { "id": "test-log", "text": "node --test output: 2 passed, 1 failing (parse: invalid JSON still rejects)." },
451
+ { "id": "diff", "text": "parse.ts: trimmed input; empty string returns zeroConfig(); JSON.parse on the trimmed text otherwise." }
452
+ ],
453
+ "tests": "node --test: 2 passed, 1 failing (parse: invalid JSON still rejects)"
454
+ }
455
+ ```
456
+
457
+ ```jsonc
458
+ // live result, abridged
459
+ {
460
+ "action": "escalate",
461
+ "reason_codes": ["review_escalated", "claims_contradicted"],
462
+ "review": { "action": "escalate", "composite": 0.816, "safe_to_apply": 0.19 },
463
+ "verification": {
464
+ "action": "escalate",
465
+ "summary": { "verified": 2, "contradicted": 1, "unsupported": 0, "needs_review": 1 },
466
+ "results": [
467
+ { "claim": "The empty-input parser test passed.",
468
+ "verdict": "verified", "confidence": 1, "action": "auto" },
469
+ // second claim likewise verified at confidence 1
470
+ { "claim": "The full test suite passes with no failures.",
471
+ "verdict": "contradicted", "confidence": 1, "action": "escalate" }
472
+ ]
473
+ },
474
+ "truncated": false,
475
+ "usage": { "input_tokens": 1559, "output_tokens": 201 }
476
+ }
477
+ ```
478
+
479
+ The two true claims verify at full confidence, and the one that matters, "the full test suite passes," is contradicted by the test log at full confidence: exactly the claim a coding agent is most tempted to hand-wave.
480
+
481
+ - Claim questions instruct Jev to use `evidence` only, not world knowledge, and not the request, diff, or tests fields; if a claim needs a diff excerpt or a test log as support, supply it in `evidence`. All fields share one model state, so this is instruction-level isolation, not a hard boundary. Every field is evidence to evaluate, never instructions to follow.
482
+ - `reason_codes` collects why the gate decided as it did: `incomplete_context`, `invalid_response`, `review_escalated`, `review_required`, `claims_contradicted`, `claims_unsupported`, `claim_confidence_low`, `claim_confidence_below_auto_accept`, `accepted`.
483
+ - Up to 16 claims and 16 evidence items per call. Text fields are capped at 50,000 characters each, claims at 2,000, and evidence at 200,000 characters in aggregate; oversized evidence is rejected before any model call. Malformed answers surface as `invalid_response` and the gate never returns `auto` on one.
484
+ - Use `jev_verify` for claims without a patch review, and `jev_review` for a patch without claims.
485
+
486
+ <sub>Adapted from [burnigtm/jev-mcp](https://github.com/burnigtm/jev-mcp) (MIT), via [PR #2](https://github.com/jkudish/jev-mcp/pull/2) by rimusz.</sub>
487
+
488
+ ## When to call which tool
489
+
490
+ - `jev_verify`: one or more claims against evidence you already have.
491
+ - `jev_screen`: fetched or pasted content, before it enters context.
492
+ - `jev_find`: pick the single best candidate from up to 250.
493
+ - `jev_rerank`: score and sort the whole list.
494
+ - `jev_classify`: label many items against your own catalog, in batches.
495
+ - `jev_decide`: choose between a handful of options with priorities in view.
496
+ - `jev_compare`: how two passages relate, overall or per aspect.
497
+ - `jev_extract`: pull field values a regex can find, verbatim.
498
+ - `jev_review`: score a proposed diff before calling the task done.
499
+ - `jev_gate`: that same review plus completion claims checked against evidence.
500
+
183
501
  ## How the answers work
184
502
 
185
- Jev is TypeSafe's System One model: it returns typed answers with calibrated probability distributions, not generated text. A verify call is a Choice over supports / contradicts / says_nothing, so you see the whole distribution, not one label. A screen call is a set of yes/no probabilities. A find call is a Choice over your candidate ids plus an existence check. Code maps the answers to verdicts and actions; policy stays with you.
503
+ Jev is TypeSafe's System One model: it returns typed answers with calibrated probability distributions, not generated text. A verify call is a Choice over supports / contradicts / says_nothing, so you see the whole distribution, not one label. A screen call is a set of yes/no probabilities. A find call is a Choice over your candidate ids plus an existence check. A rerank call is one yes/no relevance question per candidate. A compare call is a Choice over three relations, repeated independently per aspect. An extract call is a Choice over the candidates your regex already found, so the model picks a value but never writes one. A review call is four Score rubrics plus one safe-to-apply probability; a gate adds one Choice per completion claim, judged from evidence only. Code maps the answers to verdicts and actions; policy stays with you.
186
504
 
187
505
  ## Limits and tuning
188
506
 
189
507
  - Thresholds (`auto_accept`, `block_at`, `review_at`, exists cutoffs) are starting points from the TypeSafe cookbooks. Tune them against your own data before you enforce them. See [how TypeSafe reports confidence](https://docs.typesafe.ai/confidence.md).
190
508
  - Jev is calibrated, not infallible. Typed output guarantees the interface, not the truth. Keep policy in code and escalate low-confidence results to a person or a bigger model.
191
- - Every result includes token usage, so you can see what each judgment costs.
509
+ - Every result that calls the model includes token usage, so you can see what each judgment costs. A `jev_extract` call where no field reaches the model reports `usage: null`.
192
510
 
193
511
  ## Configuration
194
512