JevLint-LE
io.github.nolindnaidoov0.7.0Updated Oct 10, 2026
Lint questions for TypeSafe's Jev and OpenAI's Luna offline, or ask the model itself with your key.
Overview
Lints TypeSafe Jev and OpenAI Decisions questions in code and JSON, with optional model-backed checks using your own API key.
- What it does
- JevLint-LE reads Jev and OpenAI Decisions request shapes out of JSON, JavaScript, TypeScript, Python, Rust and Go files and reports questions written in ways measured to produce bad answers. It offers eight tools: lint_text and lint_paths for linting, fix_text for safe fixes, list_rules and explain_rule for rule documentation, plus check_with_jev, plan_jev and probe_question for model-backed checks. Linting itself makes no network calls and needs no key; the model-backed tools send questions to Jev or Luna using your key.
- When to use it
- Use it when an assistant writes or edits Jev or OpenAI Decisions questions and should catch malformed or ambiguous ones before they ship. It suits teams that want the same rules in the editor, in CI and for agents. Skip it if you only need general code linting.
- Requirements
- Runs locally over stdio via npx jevlint-le-mcp; no key is needed for linting. The model-backed tools read TYPESAFE_API_KEY or OPENAI_API_KEY from the environment the client started the server with, or from .env.local or .env in that directory. Desktop only.
Installation
In SourceWeft
- Open JevLint-LE in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
JevLint-LE
Lint the questions you send to TypeSafe's Jev model, and to OpenAI's Decisions API. JevLint-LE reads them out of your JSON and your code, and reports the ones written in a way that is measured to produce bad answers. Linting makes no API calls and needs no key. One optional command asks the model itself, Jev or Luna, to check a file, using your own key.
Part of the LE family.
[JevLint-LE in VS Code: two findings explained on hover, then two fixed with a quick fix]
Install
- VS Code: search for JevLint-LE in the Extensions view, or run
ext install nolindnaidoo.jevlint-le. - Cursor, VSCodium and other editors that use Open VSX: the same name,
nolindnaidoo.jevlint-le. - Command line:
npx jevlint-le. Nothing to install first. - MCP server, for an agent host:
npx -y jevlint-le-mcp.
At a glance
- Lints as you type in JSON, JavaScript, TypeScript, Python, Rust and Go.
- Fixes on the lightbulb for the mechanical mistakes, and a way to silence a finding you disagree with.
- The same rules in CI, from a command line, and for AI agents, from an MCP server. One settings file covers all three.
- Two vendors, one set of rules. TypeSafe's
/v1/systemoneshape and OpenAI's/v1/decisionsshape,predicate,choicesandlevels, are read into the same rules, and the AI SDK'sdecide()call for either. - Two optional commands that use your own key: one asks the model, Jev or Luna, whether your questions have problems a text pattern cannot see, and one re-sends a question with its layout changed to see whether the answer holds.
A request like this one looks fine and has three problems:
What it catches
Jev and Luna guarantee the type of the answer, not the answer. A badly formed question still comes back with a confident-looking number. These rules catch the mistakes that can be read from the text.
Each finding links to its own page, with an example that is flagged, one that is not, how to fix it and how to silence it. Each page links on to the TypeSafe documentation the rule comes from.
Where it looks
- JSON and JSONC request bodies, in TypeSafe's shape or OpenAI's.
- Object literals in JavaScript and TypeScript, including requests passed
inline to
client.systemOne(...)andclient.decisions.create(...), and the Vercel AI SDK'sdecide({ questions })for either vendor. noul(),choice()andscore()calls in files that use@typesafe-ai/sdk.- Python: dicts,
Noul(...),Choice(...)andScore(...)fromtypesafe_sdkor a library that wraps it, and requests passed tosystem_one(...)orclient.decisions.create(...). - Rust: the JSON inside
json!, structs and enum variants named for a question type, and::noul(...)style constructors. - Go: maps with string keys, structs with a
Typefield, and structs named for a question type. - A request pasted as JSON into a string, in any of these languages.
- Any other language: have your program write the request it sends to a
.jev.jsonfile and open that. Every check runs on it, and nothing in it is built at runtime, so nothing is skipped. jev/askrules in anoxlint-plugin-jevconfig.
OpenAI's Decisions API
OpenAI's Decisions API, model gpt-6-luna, asks the same kind of question
as Jev in a different shape. JevLint-LE reads it from 0.5.0. What that
covers, exactly:
- Read. A
/v1/decisionsrequest body,client.decisions.create(...)in TypeScript and Python, and the Vercel AI SDK'sdecide(). Apredicateis read as a Noul,choices: [{ value, description }]as a Choice's options andlevels: [{ label, description }]as a Score's levels, into the same rules. - Checked. Every rule with evidence behind it: the missing fallback option, a description that only repeats its name, terse instructions, degree levels, duplicates, counting, an undefined boundary and the rest. Findings name Luna. OpenAI's own guide asks for the same things.
- Held back.
JEV002andJEV003are about TypeSafe's request limits, and OpenAI publishes none.JEV001is about Jev's moving aliases, and OpenAI has one model id. - Shape mistakes are reported in OpenAI's own field names:
choiceswritten as a map or as bare names, a Choice with nochoices, a Score with nolevels. A mistypedpredicateis fixed on the lightbulb. The reshape fix is Jev-only. - Asking Luna. Set
jevlint-le.jev.modeltogpt-6-luna, or pass--jev-model gpt-6-luna, with an OpenAI key. The next section says how. - Measured on Jev, not yet on Luna. Every default in these rules was
set by measuring what the defect costs
jev-1.13.0. Luna has not been measured, so on a Luna question they are the same rules with Jev's defaults, and every finding from asking Luna says its cutoff was set on Jev. The request sent to Luna follows OpenAI's API reference and has not yet been run against the live API. Both wait on a key.
Check a file with Jev
Rules JEV301 to JEV312 are not part of linting. They run only when you run
JevLint-LE: Check This File with Jev, or pass --jev on the command
line. Either sends each question in the file to a model and asks it about
problems a text pattern cannot see: options that overlap, an option whose
name contradicts its description, criteria about the wrong thing, a Choice
that should be a Score, two questions that ask the same thing, a question
that needs another question's answer.
- Give it your key, in either of two ways. Run JevLint-LE: Set TypeSafe
API Key and paste it, which stores it in your operating system keychain.
Or type it into Settings under
jevlint-le.jev.apiKey, which can only be set in your user settings and is left out of Settings Sync. - Open a file with questions and run JevLint-LE: Check This File with Jev.
To ask Luna instead, set jevlint-le.jev.model to gpt-6-luna and give
it an OpenAI key the same two ways: JevLint-LE: Set OpenAI API Key, or
jevlint-le.jev.openaiApiKey. The same checks go to OpenAI's Decisions API
as predicates. One thing to know: the cutoffs that decide when a check fires
were set on jev-1.13.0, and every finding from Luna says so until Luna is
measured the same way. The probe asks Jev only.
What to know before you run it:
- It uses your key and your credits. One request per question and one more per request body, about 800 input tokens each. At TypeSafe's published price that is roughly three thousandths of a cent per question, and at OpenAI's about a hundredth of a cent.
- Only questions are sent: type, instructions, criteria and ids. Your
stateis not, unless you turn onjevlint-le.jev.sendState. With that on, a state written out in the file is sent too, and two more checks run: does the state hold what each question asks about, and does it carry text that gives orders to its reader. - A question with a part built at runtime is not sent. The summary says how many were held back.
- A run sends at most
jevlint-le.jev.maxCallsrequests, 25 by default. - It is disabled in an untrusted workspace. Run Workspaces: Manage Workspace Trust and trust the folder to turn it on.
- Findings disappear when you edit the file, because they were about the old text. Editing or closing the file during a check stops the check.
- Each finding shows the probability Jev gave it. Treat it as an argument with a number attached, not a verdict.
Turn on jevlint-le.jev.confirm and it first tells you how many requests it
will make, roughly how many tokens and what that costs, and waits for a yes.
It is off by default: running the command is the decision to send.
Project settings
Put a jevlint-le.json in your project and the editor and the command line
both read it, so what you see while writing is what CI reports.
exclude lists files and folders not to lint, relative to the settings file.
** crosses folders, * stays inside one, and a name with no slash matches
at any depth. The editor and the command line leave out the same files.
The nearest file wins, looking from the linted file upward, so a folder can
have its own. Where one applies, it replaces the rules, fallbackOptions
and ignore settings in the editor. If it cannot be read, nothing is linted
and the status bar says why. A key it does not know is an error, so a typo
cannot quietly leave a rule on.
To silence one finding, use the lightbulb: Disable JEV004 for this line
or for this file writes the comment for you. Strict JSON has no comments,
so there a finding is silenced with ignore.
Command line
The same linting runs outside the editor, for CI and for any editor that is not VS Code.
One version for the editor and CI
Install it in the project, as you would any linter:
The editor then lints with that copy, not the one the extension carries, so
what you see while typing is what npx jevlint-le reports in CI. The version
changes when package.json does, and for everyone at once. The status bar
shows project 0.5.2 while a project's copy is in use.
With nothing installed the extension lints with its own copy, with no setup.
It also uses its own copy, and the status bar says project has 0.2.0 with
the reason in its tooltip, when the project's copy cannot be used:
- the workspace is not trusted, because loading the copy runs code from it
- the copy is older than 0.3.0, the first version an editor can load
- the copy fails to load, or does not read that kind of file
Check This File with Jev and the probe always run the extension's own code, whatever the project installs.
[The command line reporting three findings on a small request]
It exits 0 when the run passes, 1 when a finding fails it, and 2 when it
could not do what was asked: an unknown option, a missing path, or nothing to
read. Errors always fail a run. Warnings fail it only past --max-warnings.
The summary line always says how many questions could not be read in full
and names any file skipped for its size and any folder or file it could not
read. It does not follow linked folders. It does not run the checks that ask
Jev, and it never uses the network, unless you pass --jev.
In CI and before a commit
On GitHub, the action annotates a pull request:
For GitHub code scanning, write SARIF and upload it:
--format junit writes the XML that Jenkins, GitLab and most test reporters
read. Only an error is a failed case there, since only an error fails a run.
With pre-commit:
Checking with Jev
--jev runs the checks that ask Jev itself, JEV301 to JEV312, after the
linting. It is the only option that uses the network, and nothing in a
settings file can turn it on.
- The key is read from
TYPESAFE_API_KEYand from nowhere else. It is never printed. - A question with a part built at runtime is never sent. The summary counts the ones held back.
- State is not sent unless you add
--jev-send-state. --jev-max-callsis a limit for the whole run. A run that needs more exits 2, so a job cannot pass after checking part of a project.- A rejected key, a failed request or a run you stop also exits 2, and says how many requests were answered of how many were planned.
- These findings are warnings or less by default, so they fail a run only
when
--ruleraises one or--max-warningsis passed.
For AI agents
The same program is an MCP server, so an agent that writes Jev or OpenAI Decisions questions can lint what it wrote and fix it before you see it. The tool descriptions and the server's instructions name both vendors and all three request shapes, so an agent knows to call it for any of them.
Agents running inside VS Code get it with no setup: the extension offers the server to the editor, which starts it when an agent calls a tool. For any other client, point it at the server package:
A project that already pins jevlint-le for CI has the same server behind
npx jevlint-le --mcp. The package with no flag is the one to give a client,
because jevlint-le started bare is a linter that exits, not a server.
It offers eight tools. lint_text lints a request or source code passed as
text and lint_paths lints files on disk, with each finding carrying the
edit that would mend it. fix_text returns the text with the safe fixes
applied and the report of what is left, and writes nothing. list_rules
lists every rule with what it means, and explain_rule returns a rule's own
page, with an example that is flagged and one that is not. Those send
nothing and need no key.
The other three are the editor's Check This File with Jev and Probe
for an agent. check_with_jev asks Jev, or Luna with model: gpt-6-luna,
to check the questions in text or in files, and returns the lint report with
the model's findings added. plan_jev says what that would send, and sends
nothing. probe_question sends one question with its layout varied and
reports whether the answer held. The two that send use your key and cost
money, and their descriptions tell an agent to call them only when you ask.
The key is read from TYPESAFE_API_KEY or OPENAI_API_KEY in the
environment the client started the server with, or from .env.local or
.env in the directory it started it in, and never from a tool argument, so
no key lands in an agent's transcript. Every tool answers as structured
content beside the text, and none writes a file.
It is listed on the MCP registry as io.github.nolindnaidoo/jevlint-le,
for a client that installs servers from there.
Probe a question
Put the cursor in a question and run JevLint-LE: Probe the Jev Question at the Cursor. It sends that question against the state written beside it, three times as written and then with the layout changed: options reversed, option names hidden, levels reversed, criteria removed. A report opens showing whether the answer moved.
If the answer changes when only the order of the options changes, the question is not deciding it. If three identical requests disagree, the input is too close to call.
It needs the state written out in the file, it sends that state, and it costs five or six requests.
Nothing else in this extension uses the network. On the command line, only
--jev does.
What it will not tell you
- Whether a well-formed question is right on your data. That needs labeled examples and calls to Jev.
- Anything about a value it cannot see. A question built from variables is reported as unreadable and counted in the status bar. A file is never shown as clean while part of it went unread.
- Everything about wording.
JEV101toJEV112are heuristics over English text. A rule is on only where a badly written question cost answers or confidence against Jev, in our runs or in someone else's, and it stays on only while it is right on at least 9 of 10 of its findings on public code it was not tuned against. Four that describe failure modes in TypeSafe's docs,JEV104,JEV107,JEV108andJEV111, did not fail onjev-1.13.0and are off. Switch any of them on injevlint-le.rules. Two more were removed in 0.4.0 for being wrong every time they fired. - Anything about a question that is not in English.
- What a lone object lacks. A
{ type, instructions }outside aquestionsmap may be a template or half of a builder, so a missingcriteriais only reported inside a request.
JEV004 is informational because the advice behind it is conditional: add a
fallback when the options might not cover every input. It fires on 9 of the 11
Choice examples in TypeSafe's own docs. If your options are exhaustive, turn it
off for that question.
Suppressing a finding
In code:
jevlint-le-disable-line covers the same line and jevlint-le-disable the whole file.
Leave the code off to silence every rule.
A comment that silences nothing is reported as JEV010, so one left behind
after its finding was fixed cannot hide a new finding later.
Fixing
Some findings can be mended without a decision from you: a Noul criteria key
written yes where the API takes true, a mistyped question type, criteria
in the wrong shape. npx jevlint-le --fix writes those, and the summary of
any run says how many it would mend.
In the editor, fix on save does the same:
Adding a fallback option and pinning a model change what a working request does, so they are never written for you. They stay on the lightbulb.
JSON has no comments, so use the setting:
Keyboard shortcuts
Every command can be given a shortcut. Open Keyboard Shortcuts, search for
JevLint-LE, and press the keys you want on any command in the list. None is
bound out of the box, so nothing here can collide with a shortcut you already
use.
Or write them into keybindings.json. The keys here are only an example:
The two that say Jev send requests with your key, so pick keys for them you will not press by accident.
Settings
Development
AGENTS.md is the engineering standard. SPEC.md is what the product is and
what is planned. samples/ is a workspace with planted mistakes, used by
bun run test:integration, which drives a real VS Code.
License
MIT
Source: README.md at commit a703d98
Tools
0Version history
1- v0.7.0LatestOct 10, 2026


