The project's README from github.com/codegirl-007/jevlint. As one markdown file: README.md.
Jevlint checks code against plain-language rules. It uses Tree-sitter to extract code units, then asks your configured decision backend whether each one passes.
Download a prebuilt binary from
Releases — linux and
macos on amd64/arm64, and windows on amd64 — unpack it, and put
jevlint on your PATH.
Or install with Go:
go install github.com/codegirl-007/jevlint/cmd/jevlint@latest
Install jevlint, then work through these steps in your repository:
.jevlint.json — jevlint init detects languages in the project
and writes a starter config (legacy jevlint.json is still read if present)..jevlint.json or install a pack. Without rules,
check has nothing to evaluate.jevlint doctor should report the config and API as OK.jevlint check . prints findings. Exit code 0 means
no failures above your confidence thresholds.cd your-project
jevlint init
# add rules to .jevlint.json, or: jevlint plugin install ...
export TYPESAFE_API_KEY=apikey_... # https://console.typesafe.ai/settings/keys
jevlint doctor
jevlint check .
Building from source (or installing with go install) needs:
Running checks needs credentials for a decision provider. The default is TypeSafe; create a key at https://console.typesafe.ai/settings/keys.
| Command | Description |
|---|---|
jevlint init |
Write a starter .jevlint.json for the project. |
jevlint doctor |
Check that the config loads and the credentials work. |
jevlint check [paths...] |
Check code against the rules. |
jevlint eval |
Score rules against fixtures in jevlint-evals.json. |
jevlint plugin ... |
Manage rule packs. |
jevlint version |
Print the version. |
init and doctor accept --json for machine-readable output.
When working on jevlint itself, set provider credentials (see Configuration) and run from the repository root:
go run ./cmd/jevlint init
go run ./cmd/jevlint doctor
go run ./cmd/jevlint check .
go run ./cmd/jevlint check --format json .
go run ./cmd/jevlint check --concurrency 8 .
go run ./cmd/jevlint check --refresh-cache .
go run ./cmd/jevlint eval
go run ./cmd/jevlint eval --rule database-joins --format json
| Flag | Description |
|---|---|
--changed |
Check only git-modified files (staged, unstaged, and untracked). |
--clear-cache |
Clear this project's cached evaluations before checking. New results are cached. |
--color auto|always|never |
Control colored text output. Defaults to auto. |
--config path |
Use a different rule file. Its directory becomes the project root. |
--concurrency number |
Set the maximum number of concurrent Jev requests. Defaults to 4. |
--format text|json |
Select human-readable or machine-readable output. Defaults to text. |
--refresh-cache |
Reevaluate code and replace matching cached results. |
Project rules live in .jevlint.json. jevlint init creates that file.
When you run commands without --config, jevlint loads .jevlint.json if it
exists; otherwise it loads jevlint.json for older projects. Run
jevlint doctor to rename a legacy jevlint.json to .jevlint.json. Use
--config path only when the file is not in the project root or you want a
non-default name. init does not create .env or other project files.
API access is configured with environment variables. Pick a backend and set its variables as described under Providers. Jevlint never writes credentials into the repository.
Debugging: set JEVLINT_DEBUG=1 to print each request URL, the
credential kind (never the value), the request payload (capped for size), and
the response to stderr. Search it with less: redirect stderr to a file and
look for payload to jev.
Set JEVLINT_PROVIDER to choose a backend (typesafe, jev, cloudflare,
clef, openrouter, or openai). When it is unset, jevlint uses TypeSafe
unless only OPENAI_API_KEY is set (see OpenAI).
The default. TypeSafe hosts Jev, the decision model jevlint is built for. Create an API key at https://console.typesafe.ai/settings/keys, then export it:
export TYPESAFE_API_KEY=apikey_...
# optional; typesafe and jev are equivalent names for this provider
export JEVLINT_PROVIDER=typesafe
| Variable | Meaning |
|---|---|
TYPESAFE_API_KEY |
TypeSafe API key (required for this provider). |
TYPESAFE_BASE_URL |
Service base URL. Defaults to https://api.typesafe.ai. |
TYPESAFE_DEFAULT_MODEL |
Model name. Defaults to jev-latest. |
TYPESAFE_ENDPOINT |
Full request URL, bypassing TYPESAFE_BASE_URL. |
Requests go to https://api.typesafe.ai/v1/systemone. Set TYPESAFE_ENDPOINT
to point at another SystemOne-compatible service (proxy, local gateway, or
alternate host).
Jevlint can use the Clef decision models on Cloudflare Workers
AI instead of Jev.
Set JEVLINT_PROVIDER=cloudflare:
export JEVLINT_PROVIDER=cloudflare
export CLOUDFLARE_ACCOUNT_ID=<account id>
export CLOUDFLARE_AUTH_TOKEN=<cloudflare api token>
export CLEF_MODEL=clef # or clef-flash
| Variable | Meaning |
|---|---|
CLOUDFLARE_ACCOUNT_ID |
Cloudflare account id. |
CLOUDFLARE_AUTH_TOKEN |
Cloudflare API token. CLOUDFLARE_API_TOKEN is accepted as an alias; if both are set, they must match. |
CLEF_MODEL |
clef (default) or clef-flash. |
Requests go to
https://api.cloudflare.com/client/v4/accounts/<account id>/ai/run/@cf/cloudflare/<model>.
Create a Workers AI API Token with Workers AI: Read and
Workers AI: Edit permissions:
You can also create one from the API Tokens page using the Workers AI template. The account id is on the Workers AI page, or in the dashboard URL. A Global API Key, or a token without the Workers AI permission, is rejected. See the Workers AI REST API guide for details.
Clef has the same request and response shape as Jev, so rules, findings, and caching work unchanged.
OpenRouter serves Jev through a System One endpoint, so
you can run Jevlint with an OpenRouter key and OpenRouter billing instead of a
TypeSafe account. Set JEVLINT_PROVIDER=openrouter:
export JEVLINT_PROVIDER=openrouter
export OPENROUTER_API_KEY=sk-or-...
export OPENROUTER_MODEL=typesafe/jev-1.13 # or ~typesafe/jev-latest
| Variable | Meaning |
|---|---|
OPENROUTER_API_KEY |
OpenRouter API key. |
OPENROUTER_MODEL |
Model id. Defaults to typesafe/jev-1.13. |
OPENROUTER_BASE_URL |
Service base URL. Defaults to https://openrouter.ai/api. |
OPENROUTER_SITE_URL |
Optional; sent as the HTTP-Referer attribution header. |
Requests go to https://openrouter.ai/api/v1/systemone, and the
X-Title: jevlint attribution header is sent. Create a key at
https://openrouter.ai/settings/keys. Jev is a decision model with the same
request and response shape as TypeSafe's Jev, so rules, findings, and caching
work unchanged.
The Decisions API on
OpenAI—not Jev. Set JEVLINT_PROVIDER=openai:
export JEVLINT_PROVIDER=openai
export OPENAI_API_KEY=sk-...
| Variable | Meaning |
|---|---|
OPENAI_API_KEY |
OpenAI API key. |
Confidence thresholds may differ from TypeSafe or OpenRouter; tune them for your project.
Jevlint reads .jevlint.json by default, or jevlint.json for older projects.
{
"languages": {
"go": {},
"typescript": {},
"tsx": {}
},
"rules": [
{
"id": "database-joins",
"description": "Join related database records in the database.",
"severity": "error",
"kinds": ["function"],
"include": ["src/**/*.go"],
"exclude": ["**/*_test.go"],
"exceptions": ["The records come from different databases."],
"localize": ["statement"]
}
]
}
No languages are enabled by default, and a config must enable at least one
language and define at least one rule (or list a pack). Available presets are
c, cpp, csharp, go, java, javascript, kotlin, php, python,
ruby, rust, tsx, and typescript.
Each preset includes common extensions, extraction queries, and localization regions. Override only what your project needs:
{
"languages": {
"cpp": {
"extensions": [".cpp", ".hpp"]
}
}
}
Presets use native Tree-sitter grammars compiled into Jevlint. Config can customize a preset, but it cannot load an arbitrary external grammar.
functionQueries replaces the built-in function extraction query for a
language. Use functionQueriesAppend to add patterns without losing the
built-in ones. Both take query strings that capture @function for the unit and
@name for its name. Appended patterns run after the built-in ones, and a unit
matched by more than one pattern is extracted once.
{
"languages": {
"typescript": {
"functionQueriesAppend": [
"(call_expression function: (identifier) @_fn arguments: (arguments . (string (string_fragment) @name) . [(arrow_function) (function_expression)] @function) (#any-of? @_fn \"describe\"))"
]
}
}
}
severity: info, warning, or errorinclude and exclude: doublestar file patternskinds: comment, docComment, field, function, statement, or typeexceptions: cases that should passlocalize: comment, docComment, field, or statement. Omit the key
or use [] to skip the second pass. Each matching region is another Jev
request on a fail, up to 24 regions per function or type.minConfidence: optional 0–1. Omit or 0 uses every Jev result. Failures
below the minimum are not reported. A rule minConfidence overrides the
global value when set.allowSkip: let Jev answer skip when the rule does not apply to the unit.
Skip is not a finding.allowAbstain: let Jev answer abstain when the rule applies but there is
not enough context to decide. Abstain is not a finding.context: deterministic repository evidence to include as extra state. The
evidence is gathered from a repository-wide index built once per run, and
every item carries file, line, and source provenance. Rules with the same
context are batched together, but each distinct context is a separate request:
a unit checked by rules with N different context settings is sent up to N
times.context.callees: functions this unit directly calls.context.callers: functions that directly call this unit (one hop).context.relatedTypes: directly related type declarations and, for a
method, its containing type.context.imports: imports the unit uses as a qualifier, in a call or a type
position.{
"id": "database-joins",
"description": "Fail when related database records are joined in application code instead of in the query.",
"kinds": ["function"],
"context": {
"callees": true
}
}
Context is conservative: when a relationship cannot be resolved, it is omitted
rather than guessed. Calls resolve to a same-file unique function, a
project-unique function, or a function in a package named by an unambiguous
import alias. Ambiguous names, receiver-typed calls, external packages, and
generic references are left out. The relatedTypes behavior is opt-in, so add
"relatedTypes": true to keep the type context earlier versions always sent.
Evidence is sent to the service in the code unit's evidence field and is also
written onto findings under evidence, so an output consumer can see exactly
what repository context Jevlint supplied without asking Jev to produce
provenance. Every relationship is one evidence item tagged with its kind.
The question tells the model to judge state.source and to treat
state.evidence (and state.parentSource) as background, not as the code
under review.
localize for a focused second pass.
That pass is extra Jev evaluations and is off unless the rule lists
categories.Jevlint sends extracted source code and file metadata to TypeSafe.
Jevlint caches validated results in the operating system's user cache directory. Entries are isolated by project and contain results only, never source code or API keys.
The cache key includes the API endpoint, model, API credential fingerprint, and the exact request sent to Jev. Code, context, rules, prompts, or batch changes create a new entry. Severity changes reuse the result because severity only affects reporting.
Entries do not expire automatically. Because jev-latest can change without
changing its name, use --refresh-cache to reevaluate and replace cached
results. Use --clear-cache to clear this project's cache before a run.
jevlint eval scores your rules against fixtures you list in
jevlint-evals.json (next to --config, or --evals). Check never loads that
file. Each case names a rule, a fixture relative to the eval file, and
expect: pass or expect: fail. Pack evals stay with the pack unless you
pass --packs.
Eval evaluates only that rule. It clears the rule's include and exclude so
fixtures still run, and keeps kinds, exceptions, localize, and minConfidence.
A case must evaluate at least one applicable code unit or eval exits 2.
The repository ships its own cases in jevlint-evals.json covering the
fixtures under examples/rules. Folder names such as good and bad are
organizational only; the case's expect value decides the outcome. A rule can
have many cases, including several for the same language.
{
"version": 1,
"cases": [
{
"rule": "database-joins",
"file": "examples/rules/database-joins/bad/example.go",
"expect": "fail"
}
]
}
| Flag | Description |
|---|---|
--clear-cache |
Clear this project's cached evaluations before evaluating. |
--color auto|always|never |
Control colored text output. Defaults to auto. |
--config path |
Use a different rule file. Its directory becomes the project root. |
--concurrency number |
Set the maximum number of concurrent Jev requests. Defaults to 4. |
--evals path |
Use a different eval file. Fixtures stay relative to that file. |
--format text|json |
Select human-readable or machine-readable output. Defaults to text. |
--packs |
Also run evals from packs listed in the project config. |
--refresh-cache |
Reevaluate code and replace matching cached results. |
--rule id |
Evaluate only this rule's cases. |
--verbose |
Include the per-unit decisions in JSON output. |
Each case evaluates the rule against every code unit in the fixture. Each
evaluation returns one raw decision: pass, fail, skip, or abstain. The
tool reports a violation only when Jev's confidence reaches the rule's
confidence floor (the rule's minConfidence, or the global one); each reported
violation becomes a finding.
The case outcome follows from those decisions:
fail: at least one violation was reported.inconclusive: no violation was reported, but Jev flagged one below the
confidence floor, or no unit returned an explicit pass (only skip or
abstain).pass: otherwise, meaning at least one explicit pass and no failure.A case matches when its outcome equals expect, and inconclusive never
matches. This keeps an expected pass from succeeding just because evaluations
skipped, abstained, or hid a failure under the confidence floor, and it makes
an expected fail require a reportable violation.
Text output streams as Jev answers come back. It starts with a legend, prints
each code unit's answer and confidence as it arrives, then prints the case's
expected and actual outcome. The run ends with
N/M eval cases matched expectations, K inconclusive. Cases stay in file
order; units within a case print in completion order.
JSON is written once after the run finishes. It includes the per-case
confidence, the raw decision counts (pass, fail, skip, abstain,
reported, belowFloor), the confidence floor, and the suite totals
(reportedFailures, belowFloorFailures). --verbose adds a units array
with each code unit's kind, name, lines, status, confidence, and whether its
failure was reported.
Exit codes:
0: every case matched1: at least one case was mismatched or inconclusive2: configuration, parsing, or provider errorA pack is a shared directory with a pack.json manifest, rules, optional evals,
and fixtures. Pins live in the project config; fetched files live in the user cache,
not the project tree.
pack.json
rules.json
jevlint-evals.json
fixtures/
{
"version": 1,
"id": "codegirl-007/database-joins",
"languages": ["go"],
"rules": "rules.json",
"evals": "jevlint-evals.json"
}
rules and evals are optional and default to rules.json and
jevlint-evals.json; both must stay inside the pack. Any languages a pack
declares must be enabled in your config. Pack ids are owner/name, where each
part is a simple identifier (letters, digits, ., _, -), and packs
containing symbolic links are rejected.
{
"languages": { "go": {} },
"minConfidence": 0.8,
"packs": [
{
"id": "codegirl-007/database-joins",
"source": "https://github.com/codegirl-007/jevlint.git",
"path": "examples/packs/database-joins",
"sha": "<commit-sha>"
}
],
"rules": [
{
"id": "database-joins",
"minConfidence": 0.5,
"include": ["src/**/*.go"]
}
]
}
Create a new pack with plugin init:
jevlint plugin init codegirl-007/database-joins
jevlint plugin init my-org/my-pack --languages go,typescript --dir ./my-pack
It writes pack.json, rules.json, and a README, plus example evals and Go
fixtures when go is one of the languages. Commit the result to a git
repository to install it.
plugin install writes the pin. A project can use only pack rules. Project
rules entries overlay a pack rule by id (confidence, include/exclude,
severity) or add a local rule. Check never opens pack evals.
The repository ships a sample pack at examples/packs/database-joins. Install
it with a GitHub tree URL, or install your own pack from a tree URL or a local
git repository:
go run ./cmd/jevlint plugin install https://github.com/codegirl-007/jevlint/tree/master/examples/packs/database-joins
go run ./cmd/jevlint plugin install /path/to/packs#database-joins
go run ./cmd/jevlint plugin list
go run ./cmd/jevlint plugin update
go run ./cmd/jevlint plugin remove codegirl-007/database-joins
| Preset | Extensions |
|---|---|
c |
.c |
cpp |
.cc, .cpp, .cxx, .h, .hpp, .hxx |
csharp |
.cs |
go |
.go |
java |
.java |
javascript |
.js, .jsx, .mjs, .cjs |
kotlin |
.kt, .kts |
php |
.php, .phtml |
python |
.py |
ruby |
.rb, .rake, .gemspec |
rust |
.rs |
tsx |
.tsx |
typescript |
.ts, .mts, .cts |
0: no findings1: findings remain2: configuration or runtime error