# Semantic linting with Jev

Example: how Perch works on a semantic lint rule

Source: https://perchscan.com/blog/semantic-linting-with-jev/
Published: 2026-09-20
Updated: 2026-09-25

![A refund rule fails on an unguarded function. Adding a guard for already-refunded orders makes the check pass.](/demos/refund-rule.gif)

*This rule shows how a semantic lint fails on the broken function and passes on the guarded function.*

In the same way you use a linter like ruff or flake8 to check your code meets style requirements, you can use Perch to check your code meets semantic requirements. For example, in this page we show a rule “Refunded orders cannot be refunded again.” and how Perch with Jev can find the broken function without a guard (80% probability), and then find the guarded function passes. All of the [commands shown below and their results](/demos/refund-capture.json) are presented for you to try.

## Perch for agents: how to add a check

Agentic tools are notoriously difficult to instruct. They might follow your instructions on one edit, and then fall off on the next. One approach is to have the agent run a check after it has made changes. Here we show how to have the agent run a Perch check after it has changed the refund function. The function below correctly refunds an order once, but if it is called again it will refund the order twice.

```python
def refund(order, payment):
    payment.refund(order.total)
    order.status = "refunded"
    return order
```

First, we write the requirement into a `perch.yaml` file, scoping it to the code we want to check using `where` and `each`.

```yaml
- name: refund-once
  where: refund.py
  each: method
  ensure: Refunded orders cannot be refunded again.
```

Then we have the agent run `perch check` to see if the code meets the requirement.

```console
$ perch check refund.py::refund --rules refund-once
refund.py:1  refund
1 check, 1 broken.

  Confidence  Rule         Description
         80%  refund-once  Refunded orders cannot be refunded again.
```

`perch check` checks uncommitted code and returns exit code `3`, which we can use to have the agent inspect the findings. If it fixes the problem, then the same command would return `1 check, nothing to report.` The [full example including the correct function](#full-example) is at the bottom of this page.

## What is semantic linting?

A semantic lint is a linter that can answer semantic questions about your code. For example, it can tell you if there is a code path that meets a particular requirement. In this example, both functions include the word “refund” but only one guards against double refunding.

In Perch, semantic lints are plain English requirements scoped to pieces of matching code, using `where` and `each`. When you run a check, Perch uses [TypeSafe's Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) to evaluate the method and return a probability. Rather than asserting that the function is correct or incorrect, Perch returns a finding for you to inspect. You can always write a regression test that calls `refund` twice to be sure.

## How does Perch use Jev?

For each method, Perch sends the method itself and all the functions that the method has access to, including imports and other functions down the call graph. This gives Jev the context it needs to answer questions about whether an error bubbles up, or whether a refund happens before it is guarded. These requests are the same as for built-in defect and security questions.

Jev returns typed answers, including probabilities, categories and severity scores. Perch turns these answers into findings that you can filter, inspect or use to fail your CI. For more information on the format of the request and limits to the context, see the [scan reference](https://docs.perchscan.com/scan/).

## Checking your agent's work: documentation

Rules can apply to any file, so you can use them to check that your agent has written good documentation. It is important to be specific in your rules: rather than asking for “good documentation”, you might ask for the first six headings to be judged.

In the [example below](/examples/documentation-rules/), the heading “Getting started on your journey” does not meet the requirement, but “Installation” does.

![A documentation rule flags vague headings and passes after they become Installation and Check a file.](/demos/documentation-rule.gif)

## A note on variability

If you were to send the same request again, the model might give a different answer. To avoid this, `perch scan` will reuse previous answers if the code, context, questions and model configuration are the same. If you want to ask the same question again, you can use `perch check` instead.

When you are writing rules for agents, you should test them on code that you know meets or doesn't meet the rule. If the rule doesn't differentiate between the two, you should try writing a different rule. Remember that you can always write regression tests to be sure that your code is correct.

For an existing application, the [business-rule example](/examples/crm-workflow-rules/) saves Frappe CRM’s validation requirements as checks for later agent edits. The [permission-check example](/examples/security-scans/) applies the same workflow to an ownership boundary.

## Getting started with agents

If you're new to Perch, you can follow the [quick start guide](https://docs.perchscan.com/install/). To add Perch instructions to your agent of choice, you can run:

```sh
perch setup codex
```

We also have instructions for Claude Code, Cursor and pi. For more information, see the [agent setup guide](https://docs.perchscan.com/skill/). Perch sends the relevant code to TypeSafe for analysis and saves the findings locally as part of each scan.

## Full example

The guarded function returns early when the order is already refunded:

```python
def refund(order, payment):
    if order.status == "refunded":
        return order
    payment.refund(order.total)
    order.status = "refunded"
    return order
```

The same check now passes:

```console
$ perch check refund.py::refund --rules refund-once
refund.py:1  refund
1 check, nothing to report.
```

Both functions and the command output were captured on September 20; the [refund example](/examples/plain-english-rules/) walks through the same rule step by step.
