cli-jev · diff
v0.1.4.0 to v0.1.4.0
1 added, 2 removed. Audit A to A.
---
name: cli-jev
description: "TypeSafe Jev CLI transport for typed judgments: noul probabilities, choice selections, ordered scores, and batched run requests."
allowed-tools: [Bash, Read, Glob, Grep]
version: 0.1.4.0
---
<!-- generated by sync-skills-hermes.cjs; do not edit -->
> Canonical source: `.skilled/skills/cli-classifier/cli-jev/`. This is a generated markdown-only copy so Hermes can load the
> skill without scanning the canonical directory; read `references/`, `assets/` and `scripts/`
> from the canonical path above.
<!-- Keywords: cli-usage, cli-jev, jev cli, typesafe jev, jev judgment, noul, choice judgment, score judgment, jev run, jev-mcp, typed judgment, classification instead of prose, probability, yes-no judgment, ordered score, batched questions -->
# TypeSafe Jev CLI Transport - Typed Judgments for Dispatch
> **Transport, not executor.** `cli-jev` is the packet of the hub's `cli-jev` transport mode: it bridges
> the `jev` CLI and its judgment contract. Jev reads state, prints a typed judgment, and exits. It has
> no file tools, no loop and no lineage, so it never mutates this workspace and never completes a task
> on its own. Pair it with a workflow mode whenever the judgment feeds an edit.
Orchestrate `jev` for decisions a script can consume: a yes/no probability, one key from explicit
options, an ordered level, or several typed answers in one request. Jev returns a small
machine-readable object where prose generation would return a paragraph, which makes it the right
tool when the caller needs a value to branch on rather than text to read.
**Core principle**: send only the state the judgment needs, choose the narrowest question type that
answers the question, read the exit code before the payload, and keep every consequence behind the
caller's own authorization.
---
## 1. WHEN TO USE
### Activation Triggers
- **Typed judgment**: a task needs a probability, a category from a known set, or an ordered level
rather than generated text.
- **Branch input**: a script, hook or gate needs a value it can compare, so `--value` and the exit
code are the whole interface.
- **Batched classification**: several unrelated questions apply to one state, answered together by
`run` instead of by several calls.
- **Judgment transport discovery**: a request names `jev`, Jev, TypeSafe Jev, `noul`, `choice`,
`score`, or `jev-mcp`.
### When NOT to Use
- **The state is a secret or private record.** Jev forwards the state verbatim to the selected
provider. Authorization to send it is the caller's to hold, and a judgment is not worth a leak.
- **The question is a fact this repository can answer.** A grep, a test run or a read settles those,
and a model opinion about them is a guess wearing JSON.
- **The request needs an edit, a build or a test.** Jev has no file or process tools; dispatch a
workflow mode instead, and use this packet only for the judgment that steers it.
- **No credential resolves for the selected provider.** Without one every call exits 3 with no
judgment, so the route is unavailable rather than degraded; `auth status` is the check, not an
assumption about the machine.
- **A prose answer is the deliverable.** Use a dispatched CLI session; Jev deliberately returns no
explanation.
---
## 2. SMART ROUTING
### Prerequisite Detection
Run these before constructing a command:
```bash
command -v jev && jev --version # jev 0.6.2 at the pinned contract
jev auth status # {"ok": true, "stored": true, "store": "..."} or exit 3
```
`command -v jev` decides whether the route exists. `auth status` decides whether a judgment is
possible, and it never prints the key. Use `jev auth test` when the key must be proven accepted: it
sends a minimal request and reports `{"ok": true, "valid": true, "model": "..."}`.
### Phase Detection
```text
JEV TASK
|
+- STEP 0: Detect the intent: JEV_JUDGMENT, JEV_QUESTION, JEV_PROVIDER or JEV_MCP
+- STEP 1: Score intents (top-2 when ambiguity is small)
+- Phase 1: Availability (command -v jev and jev auth status before the first judgment)
+- Phase 2: Judgment (one typed subcommand: noul, choice, score or run)
+- Phase 3: Verification (read the exit code before trusting the payload)
```
### Resource Domains
The router discovers markdown resources recursively from `references/` and `assets/` and then applies
intent scoring from `INTENT_MODEL`.
```text
references/ one document per routed intent
assets/ the question-shaping card
```
- `references/` holds the CLI contract, the integration patterns, the provider setup and the MCP
server, selected by intent.
- `assets/` holds the question-shaping card, loaded when the question itself needs drafting.
- `feature-catalog/` and `manual-testing-playbook/` hold the capability index and the deterministic
scenarios, read only on explicit request.
### Resource Loading Levels
| Level | When to Load | Resources |
| ----------- | ------------------------ | ---------------------------- |
| ALWAYS | Every skill invocation | `references/cli-reference.md`, `references/integration-patterns.md` |
| CONDITIONAL | If intent signals match | `references/providers-and-models.md`, `references/mcp-server.md`, `assets/question-shaping-card.md` |
| ON_DEMAND | Only on explicit request | `feature-catalog/feature-catalog.md`, `manual-testing-playbook/manual-testing-playbook.md` |
### Smart Router Pseudocode
```python
from pathlib import Path
SKILL_ROOT = Path(__file__).resolve().parent
RESOURCE_BASES = (SKILL_ROOT / "references", SKILL_ROOT / "assets")
DEFAULT_RESOURCES = ["references/cli-reference.md", "references/integration-patterns.md"]
INTENT_MODEL = {
"JEV_JUDGMENT": {"keywords": [("ask jev", 4), ("jev judgment", 4), ("jev noul", 4), ("jev choice", 4), ("jev score", 4), ("jev urgency check", 4), ("jev yes or no", 4), ("jev probability", 4), ("cli-jev", 4), ("cli-usage", 4)]},
"JEV_QUESTION": {"keywords": [("jev pick one", 3), ("jev options", 3), ("jev question", 3), ("shape a jev question", 3), ("jev batch", 3), ("jev run", 3), ("several jev questions", 3), ("jev question card", 3)]},
"JEV_PROVIDER": {"keywords": [("jev provider", 3), ("jev model id", 3), ("jev model", 3), ("jev openrouter", 3), ("jev official provider", 3), ("jev auth status", 3), ("jev auth", 3)]},
"JEV_MCP": {"keywords": [("jev mcp", 3), ("jev mcp host", 3), ("jev mcp server", 3), ("expose jev", 3)]},
}
RESOURCE_MAP = {
"JEV_JUDGMENT": ["references/cli-reference.md", "references/integration-patterns.md"],
"JEV_QUESTION": ["references/cli-reference.md", "assets/question-shaping-card.md"],
"JEV_PROVIDER": ["references/providers-and-models.md"],
"JEV_MCP": ["references/mcp-server.md"],
}
LOAD_LEVELS = {
"JEV_JUDGMENT": "ALWAYS",
"JEV_QUESTION": "CONDITIONAL",
"JEV_PROVIDER": "CONDITIONAL",
"JEV_MCP": "CONDITIONAL",
}
UNKNOWN_FALLBACK_CHECKLIST = [
"Confirm whether the request is a judgment, a question to shape, a provider question or an MCP host question",
"Confirm which judgment type is needed: noul, choice, score or run",
"Confirm the question and the state the judgment is scored against",
"Confirm a credential resolves for the selected provider before a judgment is expected",
]
AMBIGUITY_DELTA = 1
def _guard_in_skill(relative_path: str) -> str:
resolved = (SKILL_ROOT / relative_path).resolve()
resolved.relative_to(SKILL_ROOT)
if resolved.suffix.lower() != ".md":
raise ValueError(f"Only markdown resources are routable: {relative_path}")
return resolved.relative_to(SKILL_ROOT).as_posix()
def discover_markdown_resources() -> set[str]:
docs = []
for base in RESOURCE_BASES:
if base.exists():
docs.extend(path for path in base.rglob("*.md") if path.is_file())
return {doc.relative_to(SKILL_ROOT).as_posix() for doc in docs}
def get_routing_key(task, intents: list[str]) -> str:
override = str(getattr(task, "routing_key", "")).strip().lower()
if override:
return override
return (intents[0] if intents else "unknown").lower()
def classify_intents(user_request, task=None):
text = (user_request or "").lower()
scores = {intent: 0 for intent in INTENT_MODEL}
for intent, cfg in INTENT_MODEL.items():
for keyword, weight in cfg["keywords"]:
if keyword in text:
scores[intent] += weight
ranked = sorted(scores.items(), key=lambda pair: pair[1], reverse=True)
primary, primary_score = ranked[0]
if primary_score == 0:
return ("JEV_JUDGMENT", None, scores)
secondary, secondary_score = ranked[1]
if secondary_score > 0 and (primary_score - secondary_score) <= AMBIGUITY_DELTA:
return (primary, secondary, scores)
return (primary, None, scores)
def route_jev_resources(user_request, task=None):
inventory = discover_markdown_resources()
primary, secondary, scores = classify_intents(user_request, task)
intents = [primary] + ([secondary] if secondary else [])
routing_key = get_routing_key(task, intents)
loaded, seen = [], set()
def load_if_available(relative_path: str):
guarded = _guard_in_skill(relative_path)
if guarded in inventory and guarded not in seen:
load(guarded)
loaded.append(guarded)
seen.add(guarded)
for relative_path in DEFAULT_RESOURCES:
load_if_available(relative_path)
if max(scores.values() or [0]) < 0.5:
return {
"routing_key": routing_key,
"intents": intents,
"intent_scores": scores,
"load_level": "UNKNOWN_FALLBACK",
"needs_disambiguation": True,
"disambiguation_checklist": UNKNOWN_FALLBACK_CHECKLIST,
"resources": loaded,
}
for intent in intents:
for relative_path in RESOURCE_MAP.get(intent, []):
load_if_available(relative_path)
return {
"routing_key": routing_key,
"intents": intents,
"intent_scores": scores,
"load_level": LOAD_LEVELS.get(primary, "CONDITIONAL"),
"resources": loaded,
}
```
---
## 3. HOW IT WORKS
### Transport Guard
Jev does not dispatch itself. Two bounds apply unchanged by this packet:
- **No spawn chain, by construction.** Jev cannot start an agent, a CLI or a loop: it has no `Task`,
file or process tools, and each call returns one value and exits. No runtime refusal backs this.
The deep-loop recursion guard checks fan-out lineage and the dispatch stack only for a registered
`ExecutorKind`, and Jev has none, so the tool surface is the whole bound.
- **A transport pairs with a workflow before any effecting operation.** The judgment selects; the
workflow acts. A `choice` answer is evidence about the caller's options, never permission.
### Transport Selection
A `choice` question can run on either backend. The `jev` CLI is the default and the fallback, and Pi's
classifier runtime answers only when a caller or the environment asks for it by name. The switch is
resolved per call in `shared/scripts/jev-transport.mjs` (`resolveTransport`):
| Input | Route |
|---|---|
| `JEV_TRANSPORT` unset, `''`, or `jev`, and no `transport` option | the `jev` CLI, silently |
| `JEV_TRANSPORT=pi`, or the per-call `transport: 'pi'` option | Pi for a `choice` request, otherwise the CLI |
| any other value | one line `skip: unknown transport '<value>', using jev CLI`, then the CLI |
The per-call option wins over the environment, only `''` counts as unset, and an empty `transport`
option is unset too. The environment being read is the `env` object the caller already passes to `jev`,
never `process.env`, so a narrow env means the CLI.
**`choice` only.** The Pi route is entered only when the arguments are the declared `choice` shape,
`choice [--provider <p>] [-q <text>] (-o <key>=<desc>)+` with the state on stdin. Every other invocation,
including `auth`, `noul`, `score`, `run` and a `choice` call with an unmappable flag, is spawned on the
CLI unchanged and prints nothing.
**Three gates, then the backend.** `ModelRuntime.create()` is imported from the package `pi` resolves
to. Three gates run in order before any classifier call, and the first failure prints exactly one line
and then runs the CLI:
| Gate | Failure line |
|---|---|
| package: the package resolves and `ModelRuntime.create()` returns | `skip: pi transport unavailable (package), using jev CLI` |
| model: `getModelOfType('classifier', 'openrouter', 'typesafe/jev-1.13')` returns a model | `skip: pi transport unavailable (model), using jev CLI` |
| credential: `getAvailableOfType('classifier', 'openrouter')` lists `typesafe/jev-1.13` | `skip: pi transport unavailable (credential), using jev CLI` |
| backend: `classify()` throws or errors, or the answer names a key that was not submitted, or a submitted key has no finite probability | `skip: pi transport unavailable (backend), using jev CLI` |
The call is `runtime.classify(model, context, { signal })` under the caller's own timeout, and its answer
is written back on stdout in the CLI's shape: `answers.answer.choice`, `answers.answer.probabilities`, a
top-level `model`, compact JSON with a trailing newline, and exit code 0. A caller that opts in keeps
its parsing. No retry runs inside the transport.
**Credentials stay in Pi's store.** The transport calls `ModelRuntime.create()` and lets Pi resolve its
own credential. It reads no `.env`, prints no environment variable and copies no key into Pi's options.
The `env` object reaches the `jev` child only.
A Pi answer carries the same standing as a CLI answer: evidence about the caller's options, never
permission to act.
### Execution Ownership
The calling agent owns the question, the state and the consequence. Jev owns one thing: the typed
answer. Nothing in this packet decides what to do with it.
### The Dispatch Shape
```bash
jev noul -q 'Does this message express urgency?' -s 'Please restore service today.' --value </dev/null
jev choice -q 'Which team should handle this?' -s @state.txt \
-o billing='Payment, charge, or refund issues' -o technical='Bugs or integration failures' --pretty
jev score -q 'How severe is this incident?' -s @state.txt \
-l 'no user impact' -l 'degraded' -l 'outage' --value
jev run @request.json --pretty
```
The grammar is positional and small: one subcommand, `-q/--question`, a state from
`-s/--state` (text, `@file`, or `-`), and the type-specific criteria (`-o` pairs for `choice`, `-l`
levels for `score`, a JSON request for `run`). `--provider`, `--model`, `--json-state`, `--pretty`
and `--value` are shared across the judgment subcommands; `--endpoint` exists but is hidden from
help.
### Output Contract
Default output is compact JSON on stdout, `--pretty` indents it, and `--value` prints only the
primary answer (`noul` → probability, `choice` → the selected key, `score` → the zero-based
position, which may be fractional). Errors are structured JSON on **stderr** with `ok: false`, and
the process exit code carries the class.
| Exit | Meaning | Caller's move |
|---|---|---|
| 0 | A judgment was produced | Read the payload; it is a judgment, not a fact |
| 1 | Unexpected or unclassified API response | Do not retry blindly; inspect the body |
| 2 | Usage error: bad flags, unreadable state, malformed JSON, or `--value` with `run` | Fix the command; no quota was spent |
| 3 | Credential missing, empty or rejected (401/403) | Operator step: set the key, then re-run |
| 4 | Retryable transport: 429, 5xx, connection or timeout | Back off and retry; never read as a judgment |
| 130 | Interrupted | Operator aborted |
### Dispatch-Critical Gotchas
1. **State defaults to stdin.** Omit `-s` and Jev reads stdin to EOF; on an inherited terminal that
is a hang with no output. Every non-interactive dispatch closes or feeds stdin.
2. **`--value` is unavailable with `run`** and the CLI only says so *after* a successful call,
because the check runs on the parsed answer. Batched requests always need the JSON.
3. **Cardinality is enforced by the MCP tools, not by the CLI.** `choice` with one option and
`score` with one level both reach the provider from the shell and both are refused by `jev-mcp`.
Treat two as the floor in either direction.
4. **The credential store is the CLI's own.** `~/.config/jev-cli/credentials.json` (or
`$XDG_CONFIG_HOME/jev-cli/credentials.json`), directory `0700`, file `0600`, written atomically.
It is not shared with any other tool that uses the same upstream key.
5. **`custom` sends its bearer to whatever endpoint you name.** Point it at a trusted HTTPS origin,
and expect the native System One request and response shape, not chat completions.
6. **`vercel` is translated.** The client rewrites `noul` to `boolean`, moves the model id into an
`ai-model-id` header and normalizes the answer back, so a response you compare across providers
is the normalized one, not the wire one.
---
## 4. RULES
### ✅ ALWAYS
- Run `command -v jev && jev --version` before the first dispatch of a session, and `jev auth status`
before the first judgment.
- Choose the narrowest question type: `noul` for a yes/no probability, `choice` for one key from
explicit options, `score` for an ordered level, `run` only for several questions at once.
- Close or feed stdin whenever the state arrives from stdin.
- Give `choice` keys that are stable output values and descriptions that make the categories
mutually exclusive; give `score` levels in ascending order from lowest to highest.
- Read the exit code before the payload, and treat exit 4 as retryable transport rather than an
answer.
- Send only the state the judgment needs, stripped of credentials and private records.
- Keep the consequence behind the caller's authorization; a judgment is input to a decision, not the
decision.
### ⛔ NEVER
- Never inline an API key on the command line, print it, or commit it.
- Never run `jev-mcp` by hand, and never claim a repo file wires an MCP host.
- Never pass `--value` with `run`, and never pass a single option or level where the type requires
a choice or a scale.
- Never let a judgment stand in for a repository fact this repo can answer directly.
- Never claim a provider is reachable without a key that resolves: `auth status` or `auth test` is
the evidence.
### ⚠️ ESCALATE IF
- No provider credential exists for the selected provider: report the operator step rather than
routing around it.
- The selected provider returns 401/403 after a key was believed present: the key is stale, and
rotation is the operator's.
- A `custom` endpoint must be reached: confirm the operator trusts the origin, because the bearer
travels with the request.
- A judgment would drive an irreversible action: the operator owns that call, not the model.
---
## 5. REFERENCES
### Core References
- `references/cli-reference.md` — every subcommand, flag, input form, exit code and output shape.
- `references/providers-and-models.md` — the four providers, their key variables, endpoints, default
models and translation behavior.
- `references/integration-patterns.md` — the shipped judgment patterns: gate, triage, branch, batch.
- `references/mcp-server.md` — the `jev-mcp` stdio server, its four tools, and the host-only rule.
### Templates and Assets
- `assets/question-shaping-card.md` — how to phrase a jev question so the answer is usable.
- `manual-testing-playbook/manual-testing-playbook.md` — deterministic scenarios with expected
observables.
- `feature-catalog/feature-catalog.md` — the capability index with source anchors.
### Evidence Base
- Contract pin: `specs/cli-jev/001-cli-jev-creation/001-jev-contract-research-and-pin/implementation-summary.md`,
with the captured probes beside it at `scratch/probe-matrix.txt` and `scratch/probe-surface.txt`.
- Vendored source: `specs/cli-jev/001-cli-jev-creation/context/jev-cli-main/`
(read-only; `src/jev_cli/__init__.py` is the CLI contract and `src/jev_cli/mcp_server.py` the MCP
contract).
---
## 6. SUCCESS CRITERIA
### Dispatch Completion
A judgment dispatch is complete when it exits 0, the payload parses, the answer shape matches the
question type (`noul` a number in `[0, 1]`, `choice` one of the submitted keys, `score` a position
in the submitted list), and the caller has recorded which provider and model produced it.
### Packet Quality
A reader can decide whether to use Jev, compose a correct command for all four judgment types, wire
a provider, recognize every exit class, and know that `jev-mcp` belongs to a host — without opening
another packet.
---
## 7. INTEGRATION POINTS
### Hub Integration
- `cli-jev` is the packet of mode `cli-jev` in the `cli-classifier` hub, beside `cli-deem`. It is declared
- in `mode-registry.json` as `packetKind: "transport"` under the `transport-axis` extension,
+ `cli-jev` is the packet of mode `cli-jev` in the `cli-classifier` hub and its only registered mode today. A future classifier backend joins the same parent hub as a new mode with its own packet. It is declared in `mode-registry.json` as `packetKind: "transport"` under the `transport-axis` extension,
`mutatesWorkspace: false`, forbidding `Write`, `Edit` and `Task`, and it routes by hub membership
like every other mode. `hub-router.json` carries its intent signal and `leaf-manifest.json` its
leaf set; the hub's `ROUTER.md` is the stage-two surface router, `router_state: active`, and maps
this mode's judgment, question, provider and MCP intents to its leaves.
### Not a Deep-Loop Executor
Jev cannot run a lineage: it has no file tools, no iteration model and no stop policy. `/deep:research`
and `/deep:review` do not accept it, and no `ExecutorKind` exists for it. A lineage that needs a
judgment gets one from a workflow executor and reads the result.
### Code and Spec Integration
- A gate or hook may call `jev noul --value` and compare the number to its own threshold; the
threshold is the gate's, and the judgment is only one input to it.
- A triage step may call `jev choice` or `jev score` and switch on the returned key or position.
- Nothing here writes to a spec packet, and the judgment is not evidence until a human or a check
confirms the consequence.
### Tool Roles
- **Bash**: runs `jev`; the only surface this packet uses.
- **Read / Glob / Grep**: read the state file, the request JSON, and this packet's references.
- **Write / Edit / Task**: forbidden. A transport that edits is a workflow wearing the wrong
`packetKind`.
---
## 8. REFERENCES AND RELATED RESOURCES
- Upstream project: `specs/cli-jev/001-cli-jev-creation/context/jev-cli-main/README.md`.
- Upstream bundled skill: the same tree's `skills/jev-cli/SKILL.md` — the vendor's own guidance,
which this packet supersedes for repo dispatch behavior.
- Hub router: `hub-router.json`; stage-two control map: `ROUTER.md`; registry: `mode-registry.json`.
- Structural precedent for a transport packet: the hub doctrine's `transport-axis` extension in
`.skilled/skills/sk-doc/sk-create-skill/references/parent-skill/parent-skills-nested-packets.md`.