Lint your jev requests before they come back confidently wrong.
JavaScript
2
9 commits
updated Sep 23, 2026
Lint your jev requests before they come back confidently wrong.
question "route_team"
warn Choice "route_team" has no "other"/"none of the above" option. If an input fits none of
[billing, technical, account], jev must still pick one — measured at confidence 1.00 on a
wrong answer, so confidence gating will not catch it.
fix: Add e.g. {"other": "A case that fits none of the above"}
question "agent_name"
error Question "agent_name" references `ticket.assigned_agent.name`, but state has nothing at
"ticket.assigned_agent".
TypeSafe's jev is a model that doesn't write text. You hand it some data and a question, and it hands back a typed answer your code can use directly — a true/false probability, a pick from a list, or a rating. Three question types:
can wait → needs attention today.You send it state (the stuff to look at) and questions (what to judge). It sends back answers.
No prompt engineering, no parsing JSON out of prose.
jev is strict about how you write a request and completely relaxed about what you ask.
Send it malformed JSON and it rejects you immediately with a precise error — wrong field type, missing key, it tells you exactly where. That part is handled.
But ask it a badly designed question and it just... answers. Confidently. No warning.
We measured this. We generated 40 jev requests the way a normal developer would, then checked them:
Say you write a Choice question: "What is this email asking for?" with four options — meeting request, pricing question, support issue, introduction.
Then an email arrives saying "Please remove me from this mailing list. I never signed up."
That's none of the four. But you didn't give jev a way to say "none of these," so it has to pick one. Here is what actually happened when we ran it:
without a "none of the above" option -> "support issue" confidence 1.00
with a "none of the above" option -> "other" confidence 0.93
Confidence 1.00. Maximum. The model is as certain as it can possibly be, and it is wrong.
This matters because the standard advice for handling AI uncertainty is "check the confidence score, and send the low-confidence ones to a human." That advice cannot catch this bug. The confidence is perfect. Your monitoring sees a healthy, decisive answer. The wrong label flows into your database.
That is the whole reason wellposed exists: some bad questions produce answers that look perfect. You can't catch those by inspecting the answer. You have to catch them by inspecting the question, before you send it.
It's a paper form with no "Other" box. If a survey asks which of four departments your complaint is about and yours is about none of them, you still tick a box. The form gets filed, looks complete, and is wrong. Nothing downstream can tell.
The contrast that shapes the whole design: when we gave a Choice two overlapping options (angry and
furious), confidence collapsed to 0.19. That failure is loud — ordinary confidence checks catch
it fine. So wellposed doesn't spend effort there. It focuses on the failures that stay silent.
Four parts, cheapest first.
A SKILL.md that loads into Claude Code or Codex. When you say "add jev
to classify these tickets," the agent already knows:
{"ticket": {...}, "order": {...}, "policy": "..."}. Then point at the pieces from your question using backticks:
Does `ticket.messages[0].text` request a refund? (This is TypeSafe's own convention; their
playground uses it.)0.73, which means "73% likely the answer is yes" to a question that has no
yes.No API key, no network, no waiting. It reads the JSON and checks 40 rules.
Things that are definitely broken (these fail the check):
`ticket.assigned_agent.name` but there is
nothing at that path in your data. This one is provably wrong: no opinion required, just walking
the JSON.Things that are probably wrong (warnings):
Suggestions (just advice):
Some questions genuinely require judgment. Code can see that your Choice has no "none of the above" option, but it can't know whether "none of these" is even possible for your data. That's a question about meaning.
So for exactly those cases, wellposed asks jev about your question. Seven checks, each a simple yes/no:
This costs one API call per question and only runs when you ask for it (--semantic).
The two layers are deliberately different. The free one is aggressive — it flags every Choice missing an escape hatch, including ones that are fine, because it can't know. The paid one is careful — it only gets asked the questions that genuinely need thought. Cheap-and-noisy filters first; expensive-and-accurate resolves the leftovers.
This is the part most tools skip. wellposed ships the 76 questions, hand-labelled, plus the grading rubric written before anything was generated, so the goalposts couldn't move afterward.
Run wellposed eval and it scores itself:
hand label n caught
----------------------------------------------
no-escape-hatch 9 9/9 fully covered
degree-as-noul 6 6/6 fully covered
jev-date-comparison 2 2/2 fully covered
jev-counting / arithmetic 2 2/2 fully covered
jev-double-negative 1 1/1 fully covered
bundled-judgments 3 2/3 partial
overlapping-choice-options 1 0/1 deferred to the semantic layer
unanswerable-from-state 2 0/2 deferred to the semantic layer
recall 22/26 = 85% precision 22/24 = 92%
In plain terms: it catches 85% of the known problems, and 92% of what it flags is genuinely a
problem. Those two figures are exactly what wellposed eval prints; the table above them is an
abridged view, since the real output also counts the clean questions and names its own false
positives. Run the command for the full version. If a future change makes the linter worse, the
numbers drop and you see it.
Plus 35 unit tests. Three exist specifically because we sent those exact broken requests to the real API and recorded what it said.
The semantic layer has its own corpus, added later than it should have been. 70 items, seven
checks, five positives and five deliberately adversarial negatives each — questions that look like the
defect but are actually fine. npm run eval:semantic scores it with one live jev call per item:
check n recall precision unsure
--------------------------------------------------------------
bundled-judgments 10 4/5 4/4 1
criteria-contradict-instructions 10 3/5 3/3 1
degree-as-noul 10 5/5 5/5 0
escape-hatch-needed 10 5/5 5/5 1
levels-unordered 10 5/5 5/5 0
options-not-exclusive 10 5/5 5/5 0
unanswerable-from-state 10 5/5 5/5 0
recall 32/35 = 91% precision 32/32 = 100%
The first run of this corpus scored 69%, and it found one check — degree-as-noul — at 0/5. It had
been written to detect questions that literally ask "how much", and missed the whole real failure
class: yes/no questions over a gradable property with no stated cutoff ("is this pull request
risky?"). Rewording it took that check to 5/5. The same run showed precision holding at 100% down to a
threshold of 0.30, so the warn threshold moved from 0.65 to 0.50 and picked up four more true
positives for free.
claude plugin marketplace add suraj-phanindra/wellposed
claude plugin install wellposed@wellposed
Then /wellposed:wellposed, or just describe what you're building — the skill self-triggers on jev work.
npx skills add suraj-phanindra/wellposed --skill wellposed -a claude-code -a codex
Copies the skill to ~/.agents/skills/wellposed and links each agent's directory at it. In Codex,
invoke with $wellposed.
Everything lives in one directory. Copy skills/wellposed/ anywhere your agent
reads skills from, or paste
SKILL.md
into the conversation.
npx wellposed lint request.json # nothing to install
npm i -g wellposed # or put it on PATH
Node ≥ 18, zero dependencies. No API key needed for the structural checks.
npx wellposed lint request.json # structural: free, offline, no API key
npx wellposed lint request.json --semantic # + jev-on-jev checks (needs TYPESAFE_API_KEY)
npx wellposed lint - < request.json # reads stdin
npx wellposed rules # every rule and where it comes from
npx wellposed eval # score the linter against the labelled corpus
Already installed as a skill and want to run it offline? The CLI ships inside the skill directory -
find ~/.claude/plugins ~/.agents/skills ~/.codex/skills -name wellposed.mjs -path '*wellposed/scripts/*' | head -1
and call node <that path> instead.
Exit code is 1 when errors are found, so it drops into CI unchanged. --json for machine output,
--max-warnings <n> to fail on warnings too.
Disagree with a rule? Turn it off. If your option list really is exhaustive, that warning is noise and you should silence it:
{ "rules": { "choice/no-escape-hatch": "off" } }
npx wellposed lint request.json --config wellposed.config.json
A working example ships at
examples/wellposed.config.json.
| Layer | File | Cost |
|---|---|---|
| 1. Adapter | SKILL.md | — |
| 2. Structural | structural.mjs | free, offline |
| 3. Semantic | semantic.mjs | 1 call/question |
| 4. Eval | corpus.json + agreement.mjs | free, offline |
error severity, failing requests the API answers at confidence
1.00. None of those phrasings were in the 40-item corpus, so the reported precision was unchanged
before and after the fix: the metric could not see them. The corpus now carries 36 adversarial
items (source: adversarial-2026-09-18 in corpus.json) specifically so it can.choice/no-escape-hatch over-flags by design. Whether "none of these" is reachable is a question
about meaning. If your option set really is exhaustive, turn the rule off.corpus.json under corrections rather than quietly applied.verified means reproduced against the live API; everything else cites
a docs page. wellposed rules shows which is which.codex --enable skills.This is an independent tool. TypeSafe, System One and jev are theirs; the rules here are drawn from their public documentation, which is the authority whenever it and this tool disagree — in particular jev-1.13 jaggedness, the most useful page in the docs and the one most often skipped.
npm test # 35 unit tests, zero dependencies
npm run eval # score the linter against the corpus
npm run lint:example # lint the bundled example
MIT.
9 commits
JavaScript
100.0%
Lint your jev requests before they come back confidently wrong.
JavaScript
2
9 commits
updated Sep 23, 2026
Lint your jev requests before they come back confidently wrong.
question "route_team"
warn Choice "route_team" has no "other"/"none of the above" option. If an input fits none of
[billing, technical, account], jev must still pick one — measured at confidence 1.00 on a
wrong answer, so confidence gating will not catch it.
fix: Add e.g. {"other": "A case that fits none of the above"}
question "agent_name"
error Question "agent_name" references `ticket.assigned_agent.name`, but state has nothing at
"ticket.assigned_agent".
TypeSafe's jev is a model that doesn't write text. You hand it some data and a question, and it hands back a typed answer your code can use directly — a true/false probability, a pick from a list, or a rating. Three question types:
can wait → needs attention today.You send it state (the stuff to look at) and questions (what to judge). It sends back answers.
No prompt engineering, no parsing JSON out of prose.
jev is strict about how you write a request and completely relaxed about what you ask.
Send it malformed JSON and it rejects you immediately with a precise error — wrong field type, missing key, it tells you exactly where. That part is handled.
But ask it a badly designed question and it just... answers. Confidently. No warning.
We measured this. We generated 40 jev requests the way a normal developer would, then checked them:
Say you write a Choice question: "What is this email asking for?" with four options — meeting request, pricing question, support issue, introduction.
Then an email arrives saying "Please remove me from this mailing list. I never signed up."
That's none of the four. But you didn't give jev a way to say "none of these," so it has to pick one. Here is what actually happened when we ran it:
without a "none of the above" option -> "support issue" confidence 1.00
with a "none of the above" option -> "other" confidence 0.93
Confidence 1.00. Maximum. The model is as certain as it can possibly be, and it is wrong.
This matters because the standard advice for handling AI uncertainty is "check the confidence score, and send the low-confidence ones to a human." That advice cannot catch this bug. The confidence is perfect. Your monitoring sees a healthy, decisive answer. The wrong label flows into your database.
That is the whole reason wellposed exists: some bad questions produce answers that look perfect. You can't catch those by inspecting the answer. You have to catch them by inspecting the question, before you send it.
It's a paper form with no "Other" box. If a survey asks which of four departments your complaint is about and yours is about none of them, you still tick a box. The form gets filed, looks complete, and is wrong. Nothing downstream can tell.
The contrast that shapes the whole design: when we gave a Choice two overlapping options (angry and
furious), confidence collapsed to 0.19. That failure is loud — ordinary confidence checks catch
it fine. So wellposed doesn't spend effort there. It focuses on the failures that stay silent.
Four parts, cheapest first.
A SKILL.md that loads into Claude Code or Codex. When you say "add jev
to classify these tickets," the agent already knows:
{"ticket": {...}, "order": {...}, "policy": "..."}. Then point at the pieces from your question using backticks:
Does `ticket.messages[0].text` request a refund? (This is TypeSafe's own convention; their
playground uses it.)0.73, which means "73% likely the answer is yes" to a question that has no
yes.No API key, no network, no waiting. It reads the JSON and checks 40 rules.
Things that are definitely broken (these fail the check):
`ticket.assigned_agent.name` but there is
nothing at that path in your data. This one is provably wrong: no opinion required, just walking
the JSON.Things that are probably wrong (warnings):
Suggestions (just advice):
Some questions genuinely require judgment. Code can see that your Choice has no "none of the above" option, but it can't know whether "none of these" is even possible for your data. That's a question about meaning.
So for exactly those cases, wellposed asks jev about your question. Seven checks, each a simple yes/no:
This costs one API call per question and only runs when you ask for it (--semantic).
The two layers are deliberately different. The free one is aggressive — it flags every Choice missing an escape hatch, including ones that are fine, because it can't know. The paid one is careful — it only gets asked the questions that genuinely need thought. Cheap-and-noisy filters first; expensive-and-accurate resolves the leftovers.
This is the part most tools skip. wellposed ships the 76 questions, hand-labelled, plus the grading rubric written before anything was generated, so the goalposts couldn't move afterward.
Run wellposed eval and it scores itself:
hand label n caught
----------------------------------------------
no-escape-hatch 9 9/9 fully covered
degree-as-noul 6 6/6 fully covered
jev-date-comparison 2 2/2 fully covered
jev-counting / arithmetic 2 2/2 fully covered
jev-double-negative 1 1/1 fully covered
bundled-judgments 3 2/3 partial
overlapping-choice-options 1 0/1 deferred to the semantic layer
unanswerable-from-state 2 0/2 deferred to the semantic layer
recall 22/26 = 85% precision 22/24 = 92%
In plain terms: it catches 85% of the known problems, and 92% of what it flags is genuinely a
problem. Those two figures are exactly what wellposed eval prints; the table above them is an
abridged view, since the real output also counts the clean questions and names its own false
positives. Run the command for the full version. If a future change makes the linter worse, the
numbers drop and you see it.
Plus 35 unit tests. Three exist specifically because we sent those exact broken requests to the real API and recorded what it said.
The semantic layer has its own corpus, added later than it should have been. 70 items, seven
checks, five positives and five deliberately adversarial negatives each — questions that look like the
defect but are actually fine. npm run eval:semantic scores it with one live jev call per item:
check n recall precision unsure
--------------------------------------------------------------
bundled-judgments 10 4/5 4/4 1
criteria-contradict-instructions 10 3/5 3/3 1
degree-as-noul 10 5/5 5/5 0
escape-hatch-needed 10 5/5 5/5 1
levels-unordered 10 5/5 5/5 0
options-not-exclusive 10 5/5 5/5 0
unanswerable-from-state 10 5/5 5/5 0
recall 32/35 = 91% precision 32/32 = 100%
The first run of this corpus scored 69%, and it found one check — degree-as-noul — at 0/5. It had
been written to detect questions that literally ask "how much", and missed the whole real failure
class: yes/no questions over a gradable property with no stated cutoff ("is this pull request
risky?"). Rewording it took that check to 5/5. The same run showed precision holding at 100% down to a
threshold of 0.30, so the warn threshold moved from 0.65 to 0.50 and picked up four more true
positives for free.
claude plugin marketplace add suraj-phanindra/wellposed
claude plugin install wellposed@wellposed
Then /wellposed:wellposed, or just describe what you're building — the skill self-triggers on jev work.
npx skills add suraj-phanindra/wellposed --skill wellposed -a claude-code -a codex
Copies the skill to ~/.agents/skills/wellposed and links each agent's directory at it. In Codex,
invoke with $wellposed.
Everything lives in one directory. Copy skills/wellposed/ anywhere your agent
reads skills from, or paste
SKILL.md
into the conversation.
npx wellposed lint request.json # nothing to install
npm i -g wellposed # or put it on PATH
Node ≥ 18, zero dependencies. No API key needed for the structural checks.
npx wellposed lint request.json # structural: free, offline, no API key
npx wellposed lint request.json --semantic # + jev-on-jev checks (needs TYPESAFE_API_KEY)
npx wellposed lint - < request.json # reads stdin
npx wellposed rules # every rule and where it comes from
npx wellposed eval # score the linter against the labelled corpus
Already installed as a skill and want to run it offline? The CLI ships inside the skill directory -
find ~/.claude/plugins ~/.agents/skills ~/.codex/skills -name wellposed.mjs -path '*wellposed/scripts/*' | head -1
and call node <that path> instead.
Exit code is 1 when errors are found, so it drops into CI unchanged. --json for machine output,
--max-warnings <n> to fail on warnings too.
Disagree with a rule? Turn it off. If your option list really is exhaustive, that warning is noise and you should silence it:
{ "rules": { "choice/no-escape-hatch": "off" } }
npx wellposed lint request.json --config wellposed.config.json
A working example ships at
examples/wellposed.config.json.
| Layer | File | Cost |
|---|---|---|
| 1. Adapter | SKILL.md | — |
| 2. Structural | structural.mjs | free, offline |
| 3. Semantic | semantic.mjs | 1 call/question |
| 4. Eval | corpus.json + agreement.mjs | free, offline |
error severity, failing requests the API answers at confidence
1.00. None of those phrasings were in the 40-item corpus, so the reported precision was unchanged
before and after the fix: the metric could not see them. The corpus now carries 36 adversarial
items (source: adversarial-2026-09-18 in corpus.json) specifically so it can.choice/no-escape-hatch over-flags by design. Whether "none of these" is reachable is a question
about meaning. If your option set really is exhaustive, turn the rule off.corpus.json under corrections rather than quietly applied.verified means reproduced against the live API; everything else cites
a docs page. wellposed rules shows which is which.codex --enable skills.This is an independent tool. TypeSafe, System One and jev are theirs; the rules here are drawn from their public documentation, which is the authority whenever it and this tool disagree — in particular jev-1.13 jaggedness, the most useful page in the docs and the one most often skipped.
npm test # 35 unit tests, zero dependencies
npm run eval # score the linter against the corpus
npm run lint:example # lint the bundled example
MIT.
9 commits
JavaScript
100.0%