Collect every function
ESLint already walks the file, so the plugin takes each function’s name, signature, leading comment and body — one function at a time, no surrounding file.
ESLint plugin
Rules that are plain-English questions about each function — does the name promise what the body does, does the comment still hold, would this error message help anyone — judged by TypeSafe’s Jev and thresholded in your ESLint config.
Three functions, the run recorded for the demo, cache on disk. Full bench numbers.
Demo
Every warning below came out of the plugin against the live model. Pick an example, run it, then fix it and run it again.
The jev/check config used for this file
How it works
ESLint already walks the file, so the plugin takes each function’s name, signature, leading comment and body — one function at a time, no surrounding file.
Every enabled rule’s question is bundled into a single request per function, with written true and false criteria. Jev answers with a probability instead of prose.
The rule compares that probability to the threshold in your config and reports at the name, the comment, or the throwing line. Answers are cached on disk by content hash.
Install
Two steps. The three built-in rules run at warn; with no key found
anywhere they produce no diagnostics at all and your editor behaves exactly as before.
npm i -D @shahriarbijoy/eslint-plugin-jev @typescript-eslint/parser # only if you lint TypeScript
echo 'TYPESAFE_API_KEY=...' >> .env # get a key from the TypeSafe console: https://console.typesafe.ai
# echo 'OPENROUTER_API_KEY=...' >> .env # alternative: a key from https://openrouter.ai/keys
With both keys set, TypeSafe is used unless settings.jev.provider
says otherwise; with neither set, the rules stay off and print one warning naming both
TYPESAFE_API_KEY and OPENROUTER_API_KEY.
// eslint.config.js
import { defineConfig } from "eslint/config";
import tsParser from "@typescript-eslint/parser";
import jev from "@shahriarbijoy/eslint-plugin-jev";
export default defineConfig([
{ files: ["**/*.ts"], languageOptions: { parser: tsParser } }, // drop this line for plain JavaScript
...jev.configs.recommended,
]);
Each key is looked up independently, in this order, first hit wins:
.env file in the directory ESLint runs from~/.config/jev/config.json, under apiKey for TypeSafe or
openrouterApiKey for OpenRouterTypeSafe reads TYPESAFE_API_KEY; OpenRouter reads
OPENROUTER_API_KEY. The key is read inside a worker thread and never appears
in a diagnostic, a log line, or the cache.
The ESLint extension runs a language server that does not inherit your shell’s
environment, so a key exported in .zshrc will not reach it. That is why step
two writes the key to .env: the plugin reads .env from the
workspace root itself, so VS Code works with no extra configuration.
If you keep your key somewhere else, hand it to the server directly with
{ "eslint.execArgv": ["--env-file=.env"] } in
.vscode/settings.json.
Everything below is optional. Put it under settings.jev
in any config object.
settings: {
jev: {
model: "jev-latest", // pin a version once thresholds are tuned
timeoutMs: 8000, // per file, across all its functions; on timeout the file is skipped
maxFunctionTokens: 6000, // estimated at 4 chars/token; larger functions are skipped with a diagnostic
concurrency: 6, // parallel Jev requests per file
cacheDir: "node_modules/.cache/eslint-plugin-jev",
strict: false, // true: a missing key or transport failure is reported as a lint problem at line 1, at the rule's configured severity
ignoreNames: ["^use[A-Z]", "^on[A-Z]", "^handle[A-Z]", "^toJSON$"], // regexes, for name-matches-body
provider: "auto", // "auto" (default) | "typesafe" | "openrouter" — which backend answers questions
}
}
"auto" uses TypeSafe when TYPESAFE_API_KEY is set
and falls back to OpenRouter otherwise; through OpenRouter the model id is sent unchanged,
so use jev-latest or an OpenRouter id such as typesafe/jev-1.13;
TypeSafe's three-part ids such as jev-1.13.0 are TypeSafe-only. The model's
listed per-token price is the same through either route, and OpenRouter's own credit fees
are separate. Through TypeSafe that price is $0.042 per million input tokens, output
free.
Rules
The threshold is the probability at which a rule speaks up. Raise it to hear less, lower it to hear more. Every message prints both the probability and the threshold, so you tune from real output.
jev/name-matches-body“Does function.name promise a different action, object,
or result than function.body performs?”
threshold 0.80 Rule docs
jev/comment-matches-code“Does function.comment state something about this
function that function.body contradicts?” — and separately,
“does function.body perform a side effect that
function.comment never mentions?” Two defects, two messages. Behaviour
carried out by a helper the body calls still counts as the function’s own.
threshold 0.80 Rule docs
jev/helpful-error-message“Would a developer reading the error message in
function.throws.<id>.message in a log be unable to tell what went wrong
or what to check next?”
threshold 0.85 Rule docs
jev/checkYour own question, one sentence, asked of every function. The plugin
appends “Judge only function in the state.” and, unless you turn
it off, asks a second question to locate the offending line.
threshold 0.80 Rule docs
jev/too-largeNot a question. A function over
maxFunctionTokens is never sent, and silence would read as a clean bill of
health — so the one function nothing checked says so itself. Asks nothing, costs
nothing.
no threshold Rule docs
Phrase your own question so that yes means the code is bad. That is the only wording rule.
Agent setup
If you work with a coding agent, hand it this prompt instead of doing the install by hand. It detects your package manager, edits the ESLint config in place, keeps your key out of git, and runs the linter once so you see real output.
Set up eslint-plugin-jev in this repository.
1. Detect the package manager from the lockfile: pnpm-lock.yaml means pnpm,
yarn.lock means yarn, otherwise npm.
2. Install @shahriarbijoy/eslint-plugin-jev as a dev dependency, plus eslint (^9 or ^10) if
the repository does not already have it. If the repository contains any
.ts files, also install @typescript-eslint/parser as a dev dependency.
3. If eslint.config.js, .mjs, .cjs or .ts exists, add
`import jev from "@shahriarbijoy/eslint-plugin-jev";` at the top and spread
`...jev.configs.recommended` after the existing entries (use `require`
and `module.exports` instead if the file is CommonJS). If none exists,
create eslint.config.mjs containing:
import jev from "@shahriarbijoy/eslint-plugin-jev";
export default [...jev.configs.recommended];
Only if the repository has .ts files, also add
`import tsParser from "@typescript-eslint/parser";` and put
`{ files: ["**/*.ts"], languageOptions: { parser: tsParser } }` before
the spread.
4. If the user gave you a TypeSafe key, append TYPESAFE_API_KEY=<key> to .env
in the directory ESLint runs from (normally the repository root). If they
gave you an OpenRouter key instead, append OPENROUTER_API_KEY=<key> there.
Otherwise add both TYPESAFE_API_KEY= and OPENROUTER_API_KEY= to
.env.example, and tell the user to get a TypeSafe key at
https://console.typesafe.ai or an OpenRouter key at https://openrouter.ai/keys,
then put that key in .env.
5. Make sure .env is listed in .gitignore. Add it if it is missing.
6. Run `npx eslint <one source file>` and paste the warnings verbatim. If it
prints nothing and no key was given, say the rules are inactive until a
key is set.
7. Never commit .env. Never print the key.
8. Report what you changed in five lines.
The same text lives at agent-setup.md, and llms.txt points an agent at the rest.
Honesty
name-matches-body offers a rename as a
suggestion you accept by hand; nothing changes under --fix.warn and
prints the number it judged on.Input tokens are $0.042 per million and output is free, so a 200-function repository costs well under a cent to lint cold, and nothing at all warm — the cache means you only pay for functions you changed.