pi-jev-gate

Guard pi file mutations against project constraints using TypeSafe AI's Jev decision model

Packages

Package details

extension

Install pi-jev-gate from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-jev-gate
Package
pi-jev-gate
Version
0.1.1
Published
Sep 22, 2026
Downloads
279/mo · 279/wk
Author
hcl-z
License
MIT
Types
extension
Size
56.7 KB
Dependencies
0 dependencies · 0 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-jev-gate

Check file changes against your project's written constraints, at the moment they happen.

Your agent has read your AGENTS.md. It still drifts — three turns into a task it writes a file that plainly breaks one of your rules, and you find out at review time, after other work has been built on top.

This pi extension hooks every file-mutating tool call, asks TypeSafe AI's Jev whether the change violates any rule in your constraints.md, and interrupts when it thinks one is broken:

This change appears to violate a project constraint:

  • No business logic in transport  [constraints.md, 0.91]

What would you like to do?
› Send back for rework
  Allow this change
  Ignore this constraint for the session

Choosing rework hands the rule's own text back to the agent, which then corrects itself.

Jev fits where a second LLM would not: it returns typed probabilities instead of prose, evaluates every rule in one parallel request, answers in 70–500ms, and costs $0.042 per million input tokens with free output. Each rule becomes one independent question, so the answer says which rule was broken — and that rule's text is what the agent gets back.

Install

pi install npm:pi-jev-gate

Then add your TypeSafe API key to ~/.pi/agent/settings.json:

{
  "jevGate": {
    "apiKey": "your-key"
  }
}

TYPESAFE_API_KEY in the environment takes precedence, which is usually what you want in CI.

Write your constraints

Create constraints.md in your project root, or docs/constraints.md — both are read, and both are merged if you have both. The filename is matched case-insensitively.

Each ## or ### heading is one rule:

## No business logic in transport

Transport modules must not contain business rules. Parsing, validation and
calculation belong in the domain layer.

## No writes to generated code

Never edit anything under `src/generated/`, including subdirectories.
Regenerate instead.

Bullets under a heading belong to that heading's rule. That matters: a rule and its exceptions must be judged together, or they contradict each other.

A flat list with no headings works too — each top-level bullet is one rule:

- No business logic in transport modules.
- No new dependencies in the core package.
- No writes to generated code.

Run /jev-gate to see how your file was actually split. Do this once before you trust the guard — a rule you think is one constraint being split into three is the most likely thing to go wrong, and it is otherwise invisible.

Write rules the way you would write a contract

Jev reads instructions literally. It answers the question you wrote, not the one you meant. Don't put logic in the wrong place will not work; Transport modules must not contain parsing, validation or calculation will. If you look at a wrong answer and find yourself explaining what you really meant, that explanation is the missing half of the rule.

What gets checked

Tool When
write Always
edit Always
bash When the command looks like it changes files
powershell Same

pi has no delete tool and no mkdir tool, so deletions, moves and directory creation only reach the filesystem through a shell command. Those are checked. Read-only commands — npm test, git status, ls — are not sent at all, so they cost nothing.

Only the operation, the path and the change itself are sent. The target file's existing contents and your project structure are deliberately left out: Jev loses accuracy when the input carries detail the question does not need.

Configuration

All fields are optional. Global settings only, in ~/.pi/agent/settings.json:

{
  "jevGate": {
    "apiKey": "your-key",
    "threshold": 0.6,
    "model": "jev-1.13.0",
    "log": false
  }
}
Field Default Meaning
apiKey — TypeSafe API key. TYPESAFE_API_KEY wins if set
threshold 0.6 Probability above which a rule counts as violated
model jev-1.13.0 Which Jev version answers
log false Append every decision to a log file

A malformed config block falls back to these defaults rather than failing — a typo should never cost you a session.

About the threshold

0.6 is reasoned, not measured. It sits above the coin-flip line because rule text is prose and Jev reads it literally, so loosely-worded rules produce noise near 0.5 — and a guard that cries wolf gets switched off.

The right number for your project depends on how your rules are written. The dialog always shows the actual probability, so after a dozen decisions you will know whether to move it. Turn on log to do this properly: it records every probability alongside the button you pressed, which is the labelled data you need.

Why the model version is pinned

jev-latest is an alias that moves when TypeSafe ships a release. If you tune threshold against a moving alias, your guard's behaviour will change one morning for no visible reason. This extension pins a version by default for that reason; override model when you want to move, and re-check your threshold when you do.

The three choices

Choice What happens
Send back for rework The call is blocked; the agent receives the rule's text and corrects itself
Allow this change The call proceeds. Nothing is remembered
Ignore this constraint for the session The call proceeds, and that rule stops being checked until pi restarts

When several rules fire at once, the third option becomes Ignore one of these constraints… and asks which one — silencing a rule you did not name would quietly widen the hole you meant to poke.

Escape means rework — dismissing the dialog is the safe action, not the permissive one. There is no timeout: a gate that lets things through while you read a diff is not a gate.

The ignore list is in memory only and is never written to disk, so it cannot quietly weaken your project's rules beyond the current session. /jev-gate clear restores everything.

/jev-gate

/jev-gate          show parsed constraints, config and the ignore list
/jev-gate clear    stop ignoring everything
/jev-gate off      disable for this session
/jev-gate on       re-enable

When it does not run

Everything that goes wrong lets your change through, with a warning telling you the check was skipped:

  • no API key configured
  • network failure, timeout, rate limiting, service overload
  • a response that cannot be read

A project with no constraints.md is different: the guard stays completely silent and never calls the API, so installing it globally costs nothing in projects that don't use it.

This is deliberate. The guard is a constraint advisor, not a security boundary — anything that can run a shell command can bypass it trivially. Its value is catching honest drift early, and a guard that breaks your agent when the network hiccups is a guard you uninstall on day one.

It also does not run in pi -p or JSON mode. There is no dialog to ask in, so the outcome would be predetermined, and paying for a judgment nobody can act on would be waste.

Escape cancels an in-flight check.

The log

With log: true, every check appends one JSON line to ~/.pi/agent/jev-gate.jsonl:

{"timestamp":"...","tool":"write","path":"src/transport.ts","operation":"create or overwrite a file","change":"...","constraints":[...],"probabilities":{"constraints.md#No business logic in transport":0.91},"violations":["constraints.md#No business logic in transport"],"threshold":0.6,"model":"jev-1.13.0","verdict":"blocked","userChoice":"rework","elapsedMs":180,"inputTokens":312,"outputTokens":24}

One record per line, so you can aggregate:

# Which probabilities did I actually allow through?
jq -r 'select(.userChoice=="allow-once") | .probabilities | to_entries[] | .value' \
  ~/.pi/agent/jev-gate.jsonl | sort -n

Skipped checks are logged too, as verdict: "degraded", so a gap in enforcement is visible afterwards.

The log records your file changes verbatim. It lives in pi's agent directory, outside any project, so it cannot be committed by accident — but it is a plaintext record of what you wrote, so treat it accordingly. Your API key never appears in it.

A log write failure warns once and is then ignored. Logging never affects whether a change is allowed.

Limits worth knowing

Jev is good at semantic judgment and bad at some things you might reach for:

  • Arithmetic and counting. Not a calculator. Rules about "no more than N of X" will not work reliably.
  • Dates. Read as text, not as ordered values.
  • Indirection. A rule about a property of a property costs accuracy. Name the thing directly.
  • Adversarial content. Diffs are treated as data, not as hostile input. A crafted change can in principle influence the judgment — another reason this is not a security boundary.

TypeSafe publishes the full list. Typed output guarantees the interface, not the truth: validate the guard against your own rules before relying on it.

Development

npm test        # node --test, no framework
npm run typecheck

Node 22+. No runtime dependencies. TypeScript runs directly under pi with no build step.

License

MIT