Skip to content
pablozrPublic

About

Semantic policy engine for AI coding agents. Reviews every agent change against your repo's plain-Markdown rules and reports PASS/WARN/FAIL per rule. Runs in the background for OpenCode and Claude Code, never blocks the agent, never edits your code.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

JevGuard

CI

Semantic policy engine for coding agents.

JevGuard reviews every change an AI coding agent makes against the rules of your repository, written in plain Markdown, and reports PASS, WARN, or FAIL per rule. It runs in the background for OpenCode and Claude Code, never blocks the agent, and never edits your code.

JevGuard: semantic policy engine for coding agents

JevGuard checks the code an agent just changed against the policies that matter in your repository. It uses Jev for a narrow semantic judgment per rule, then applies your local deterministic gate to return PASS, WARN, or FAIL.

Host Package Status
OpenCode @pablozrrrr/jevguard Available, targets OpenCode 2.2.x (>=2.2.2 <2.3)
Claude Code @pablozrrrr/jevguard-claude-code Available through this repository's plugin marketplace
Codex — Planned

Quick start

# 1. Install the plugin on every supported host found on this machine
#    (prints the plan first; --yes applies it without asking)
npx @pablozrrrr/jevguard-cli setup

# Only the API key, if you skip setup (stored in the OS credential store, shared by both hosts)
npx @pablozrrrr/jevguard-cli login

setup stores your API key (hidden prompt), installs the plugin on each supported host and asks whether to enable delivery: agent (default No) and to create a starter .jev/rules.md. Flags: --host <opencode|claude-code>, --project, --init, --agent-delivery and --yes; --yes never enables delivery: agent or creates .jev/ by itself. Without a terminal and without --yes it only prints the plan and changes nothing. The host-specific steps are also available on their own: npx @pablozrrrr/jevguard-cli install --host claude-code runs claude plugin marketplace add and claude plugin install for you. Both act on the Claude Code config directory in use: CLAUDE_CONFIG_DIR when it is set (agent platforms that run Claude Code with their own config directory per agent), otherwise ~/.claude. Run the install with the same CLAUDE_CONFIG_DIR the agent runs with; the other directory is not touched, and the output names the one used.

Then add your rules to .jev/rules.md (in OpenCode, the bundled jev-init skill can bootstrap them) and keep working with your agent as usual. Details are in Install and the Claude Code guide.

agent completes a turn
        ↓
task + attributed diff + local rules
        ↓
   Jev per rule
        ↓
local policy gate per rule
        ↓
  PASS · WARN · FAIL

Not a code generator. Not a prose review bot. Not another static linter.

JevGuard is a semantic linter: it asks whether a specific change violated a specific policy, and leaves planning, code generation, and correction with the coding agent.

Warning

JevGuard is in active development. The OpenCode plugin targets OpenCode 2.2.x (minimum 2.2.2, >=2.2.2 <2.3) through the V2 plugin API and evaluates every rule block declared in .jev/rules.md plus the SCOPE-CREEP and COMPLEXITY built-ins. Reviews are background and observe-only: they report results but never alter the agent context or block a task. When remediation is enabled and a review has any FAIL, the plugin asks for a proposal in an isolated, deny-all child session; the proposal never edits code. See OpenCode 2.2.x limitations. On Claude Code and OpenCode, a user who opts in with delivery: agent in their own claude-code.yaml or opencode.yaml gets at most one visible message per reviewed unit to the agent (the failing findings and the strategy), at most 3 per session; see Remediation proposals and OpenCode delivery: agent. The Claude Code plugin is distributed through this repository's marketplace; see the Claude Code guide.

Why JevGuard

Traditional linters are excellent at syntax, types, and patterns. They cannot reliably answer repository-specific questions such as:

  • Did a controller start making domain decisions?
  • Did this business-rule change arrive without meaningful tests?
  • Did the agent solve a small task by introducing an unnecessary abstraction?
  • Did a change cross an architectural boundary that this codebase protects?

JevGuard makes those questions versioned, scoped, and machine-actionable.

## ARCH-001

severity: error
scope: backend/**
scope: !backend/generated/**
evidence: code

### Rule

HTTP controllers must not contain business logic.

### Violation

A controller performs domain decisions, calculations, or state mutations directly.

### Allowed

Validation, HTTP mapping and delegation to services.

scope: may repeat. Each line adds an inclusion glob, and a leading ! adds an exclusion, so a path is in scope when it matches at least one inclusion and no exclusion. evidence: chooses which files the rule is evaluated against — code (the default), docs, or any. Because the default is code, prose documentation (.md, .mdx, .txt) no longer participates in a rule's evidence unless the rule opts in with evidence: docs or evidence: any; this is intentional. Config and data formats (.json, .yaml, .toml, .xml, .csv, and others) stay in the code class, so config rules keep their coverage.

The rule stays in the repository beside the code it governs. Allowed is a real exception, not a suggestion. JevGuard evaluates it as part of the same rule.

The contract

JevGuard is built around one non-negotiable idea: review the change that belongs to the turn, not whatever happens to be in the worktree.

user task
   ↓
assistant response
   ↓
attributed patch
   ↓
applicable rules
   ↓
one semantic judgment per rule

If complete attributed evidence is unavailable, invalid, or contains a blocked sensitive file, JevGuard marks the affected rule UNAVAILABLE. It never turns incomplete evidence into a reassuring verdict. Evidence problems are scoped to the rules they affect: a rule whose applicable files are all safe still runs even when another rule's evidence is blocked.

Oversized evidence is no longer refused outright. A scoped diff above maxDiffLength is split, in attributed file order, into one slice per file; an individually oversized file is split into its ordered unified-diff hunks with the file preamble repeated in every slice. Nothing is truncated, summarized, or dropped: an unparseable oversized file or an indivisible oversized hunk makes the whole rule UNAVAILABLE/OVERSIZED_DIFF.

Some failures happen before any policy rule is evaluated. If the attributed diff cannot be built, the review is a single synthetic UNAVAILABLE entry with no rule ID, and neither lane runs. If the turn is attributed but the policy files cannot be read or validated, the rule lane reports one synthetic UNAVAILABLE entry with no rule ID — it does not invent one result per declared rule — while the built-in batch still runs against the turn's attributed patch.

How a review works

For every applicable rule, JevGuard sends Jev one focused yes/no question:

Does this attributed change violate ARCH-001?

Jev returns a Noul: the probability that the answer is yes. JevGuard owns the policy that translates it into an outcome.

Rule severity PASS WARN FAIL
error < 40% 40%–69% ≥ 70%
warning < 60% ≥ 60% never

Those thresholds are per repository rule and locally configurable in .jev/config.yaml. Slicing does not change them: each slice is gated independently, the rule outcome is the highest slice outcome (FAIL > WARN > PASS), and the rule's violationProbability is the maximum across its slices. PASS requires every planned slice to pass. A rule may produce at most 16 slices, and slicing may add at most 32 repository-rule Jev calls per turn beyond one per applicable rule; a rule that does not fit is UNAVAILABLE/SLICE_LIMIT_EXCEEDED with no Jev call and later rules still use the remaining budget. Any failed, blocked, or out-of-range slice makes the whole rule UNAVAILABLE, and no sibling probability is presented as a verdict. The built-ins do not slice.

JevGuard also runs two product-owned built-ins on every attributed turn, SCOPE-CREEP and COMPLEXITY. Both receive the turn's complete, safe attributed patch with no scope filtering, and both are answered by a single Jev request that carries two independent questions. Each answer is validated on its own, so a malformed answer fails only its own check.

SCOPE-CREEP asks whether the change contains material functional, behavioral, architectural, dependency, configuration, documentation, or refactoring work the task did not request and that is not reasonably necessary to complete it. It uses fixed error thresholds (65%/90%), independent of .jev/config.yaml.

COMPLEXITY asks whether the change introduces material complexity disproportionate to, or not reasonably necessary for, completing the task, such as unnecessary abstractions, layers or indirections without proportional gain, new dependencies without a clear need, excessive configuration, premature generalization, or structure materially larger than the problem requires. It is advisory: it warns at 50% and can never fail.

The rule lane and the built-in batch are launched together. One shared FIFO concurrency limit caps the plugin instance at two Jev requests in flight, counting the built-in batch as a single request. Results join the same aggregate in a fixed order: rules in source order, then SCOPE-CREEP, then COMPLEXITY.

Reviews run in the background. The unit of review is one session execution, from session.execution.started to session.execution.succeeded; the event handler returns as soon as the serialized attribution step is scheduled, so policy reads, Jev calls, and presentation never sit on the agent's critical path. An interrupted or failed execution is not reviewed: the plugin discards it and logs JevGuard skipped · interrupted|failed.

Safe auto-propose remediation

Reviews are observe-only by default: a review never injects feedback into the agent context, adds a session message, prompts a session, blocks a turn, or changes your code. A separate, configurable path can turn a review result into a proposal, and it is delivered entirely by the same server plugin.

When .jev/config.yaml enables remediation (it is enabled by default) and a review contains any FAIL, the plugin, after presenting the review, sends one aggregate proposal request to an isolated child session:

  • The request aggregates every FAIL finding in the review — local error rules and the SCOPE-CREEP built-in — together with the turn's task and its complete, safe, full attributed patch. COMPLEXITY is advisory and never fails, and warning rules never fail.
  • The full attributed patch must pass the same safety policy as the review. If any attributed file is blocked or the patch is oversized, the plugin records no proposal at all — even when a rule-scoped FAIL exists — because there is no complete safe evidence to send. It never falls back to a repository or global diff.
  • The request is sent in an isolated child session parented to the source session. The child session is created with wildcard-deny permissions, so it cannot call a tool, edit a file, or produce a patch. It returns a strategy and one manual apply instruction only. No agent is registered for it.
  • The child session always gets an explicit model: remediation.model from .jev/config.yaml when set, otherwise the model of the source session. When neither is known, no proposal is created and the plugin logs REMEDIATION_PROPOSAL_SKIPPED_NO_MODEL.
  • A proposal that does not finish within 120 seconds is interrupted and logged as REMEDIATION_PROPOSAL_TIMEOUT.
  • The server plugin has no toast or TUI channel on OpenCode 2.2.x, so readiness is reported through the structured log only. It carries no rule, task, diff, finding, or credential.

The plugin creates at most one proposal per evaluated turn, and the child session is excluded from review for the plugin lifetime, so a proposal can never recurse or trigger a second one. A host, model, or toast failure is contained and never changes the review. A contained proposal failure — an invalid model specifier, a child-session failure, a prompt failure, or a timeout — writes one structured error log entry carrying only a typed reason code, never any task, diff, finding, path, or credential.

The proposer is a strategy step, not an apply step: it never edits code, and nothing is applied automatically. You read the proposal, copy its manual apply instruction into your normal coding agent, and apply it there. That apply is an ordinary execution, so the server plugin reviews it normally when it succeeds — exactly like any other turn.

The proposal prompt treats the task, paths, diff, rules, and findings as untrusted data, and requires a separate explicit confirmation before proposing changes to tests, configuration, or dependencies. No secret is ever carried, and no payload, task, diff, or secret is logged.

Install

Installing @pablozrrrr/jevguard does not register it with OpenCode by itself. OpenCode 2.2.x only loads plugins listed in plugins: [...] in opencode.json.

The installer is run from the JevGuard CLI (@pablozrrrr/jevguard-cli, which runs on Node):

# global config: ~/.config/opencode/opencode.json
npx @pablozrrrr/jevguard-cli install --host opencode

# project config: ./opencode.json
npx @pablozrrrr/jevguard-cli install --host opencode --project

The installer asks for confirmation, edits only the plugins array, and is idempotent. It never writes the legacy plugin key, but an entry already there counts as installed. It only edits a plugins array of package strings or { "package": "..." } objects; any other shape is left unchanged and the installer prints the manual snippet instead. jevguard doctor also checks that opencode --version is in the supported range >=2.2.2 <2.3. The plugin package no longer ships a jevguard binary; every command below runs through the CLI package, as npx @pablozrrrr/jevguard-cli <command> or, once installed globally, as jevguard <command>.

The manual alternative is one entry in opencode.json:

{ "plugins": ["@pablozrrrr/jevguard"] }

OpenCode resolves the package from the npm registry or its cache and loads it on the next start. Adding the entry only loads the plugin; it does not put the jevguard CLI on PATH (see Install the CLI). Restart OpenCode after changing the plugin list.

Important

A bare plugin entry resolves only from the npm registry. To run an unreleased local build, load the packed tarball as a local artifact plugin instead.

Build the local artifact

The artifact ships the plugin as a locally packed tarball that bundles the private @jevguard/core, @jevguard/node-infra and @jevguard/opencode-adapter workspace code, so a clean consumer installs one tarball and never resolves a workspace link or a private registry package. It also packages the jevguard-rules and jev-init skills and their private validators under skills/<name>/.

Build the tarball from this repository:

corepack enable
pnpm install
pnpm artifact:build
pnpm artifact:pack

pnpm artifact:pack writes artifacts/pablozrrrr-jevguard-<version>.tgz. Both packages/plugin/dist/ and artifacts/ are build output and are not committed.

The packed manifest depends only on public runtime packages (@inquirer/password, @napi-rs/keyring, @typesafe-ai/sdk, yaml). Installing the tarball therefore needs npm registry access for those packages; the artifact does not vendor them and does not promise an offline install.

Load the plugin

OpenCode 2.2.x resolves a bare opencode.json plugins entry from the npm registry or its cache:

{
  "plugins": ["@pablozrrrr/jevguard"]
}

To run an unreleased local build instead, install the packed tarball into the project's .opencode directory and load it through a local plugin shim:

cd /path/to/consumer/.opencode
bun add /path/to/pablozrrrr-jevguard-<version>.tgz
// .opencode/plugins/jevguard.ts
export { default } from "@pablozrrrr/jevguard";

OpenCode loads .opencode/plugins/ with its bundled Bun runtime. The shim runs as a local plugin module and resolves @pablozrrrr/jevguard from .opencode/node_modules. Use either the bare plugins entry or the local shim, not both.

The package's default export is the V2 plugin object ({ id: "jevguard", setup }). Remediation is built into the same server plugin, so no second entrypoint or shim is needed: it reviews and reports executions, and — when remediation is enabled and a review fails — creates the proposal child session automatically. The bundled skills are registered through the host skill editor (skill.transform).

Claude Code

Inside Claude Code:

/plugin marketplace add pablozr/JevGuard
/plugin install jevguard@jevguard

The Claude Code plugin reviews each closed unit of work in a detached background worker, shows the result in the session (status line, transcript line, and a toast on FAIL) without sending it to the model, and records it in the same local history. Remediation proposals are off by default and are enabled from your user config, never from the repository. It needs node >=22.13.0, npm, and git on PATH. See the Claude Code guide for what is reviewed, the in-session display, and remediation.

Author rules with the bundled skill

The package ships two OpenCode skills beside the plugin: jevguard-rules to author rules, and jev-init to bootstrap a policy where none exists. On startup the plugin's config hook appends the installed skill directories to the host's skills.paths, so the skills are discovered without a manual opencode.json entry. Skill discovery is read at OpenCode startup: restart OpenCode after installing or updating the package so the skills appear.

jev-init runs only when neither .jev/rules.md nor .jev/config.yaml exists. It reads a bounded inventory of the repository, keeps rules only from the user's explicit constraints and strongly evidenced local conventions, records provenance in a preamble, and previews both candidate files before requiring explicit confirmation naming both exact paths. Once a policy exists, every rule change belongs to jevguard-rules.

Policy content is different. .jev/rules.md and .jev/config.yaml are read once per attributed turn, so editing rule text takes effect on the next reviewed turn without restarting OpenCode. The skill validates a candidate with the same parser JevGuard uses, requires explicit final confirmation, and replaces .jev/rules.md only after the candidate passes; it never edits .jev/config.yaml and never calls Jev.

Install the CLI

Loading the plugin does not expose the jevguard binary on PATH. Run the login command without a global install:

npx @pablozrrrr/jevguard-cli login

Or install the package globally so jevguard is on PATH:

bun install --global @pablozrrrr/jevguard
jevguard login

To run an unreleased local build, install the packed tarball globally instead:

bun install --global /path/to/pablozrrrr-jevguard-<version>.tgz
jevguard login

Known limitations:

  • Target host is OpenCode 2.2.x (minimum 2.2.2). The plugin does not load on 2.1.x or 1.x. A real-host smoke on 2.2.2 (2026-11-02) covered observe and remediation (FAIL review, deny-all proposer). The single-delivery flow replaced the earlier chain and has not been re-smoked on the real host.
  • OpenCode 2.2.2 does not install dependencies for a file: plugin spec. Use the registry package; a local file: build needs its dependencies installed next to it.
  • The plugin and CLI run on Bun, and the packed jevguard bin keeps a Bun shebang. Install and run the artifact with the Bun runtime.

OpenCode 2.2.x limitations

  • Minimum version 2.2.2. The plugin declares engines.opencode >=2.2.2 <2.3 and is pinned to the 2.2.x plugin API.
  • Git-ignored files are out of scope. The execution snapshot comes from the host's working-tree diff, which leaves out files ignored by Git; a write the diff cannot show makes the execution UNAVAILABLE (INCOMPLETE_FILE_DIFF) instead of reviewing a partial patch.
  • Interrupted or failed executions are not reviewed. Their writes stay on disk and count as pre-existing changes for the next execution's snapshot, so they are not attributed to it.
  • opencode run --standalone. The review runs after the execution succeeds; a standalone run can exit before it finishes. JevGuard reviews reliably in the TUI or with opencode serve plus opencode run --server.
  • Concurrent executions. When two sessions run at once, or an execution starts while the previous one is still being finished, the review is UNAVAILABLE (CONCURRENT_SESSIONS, BOUNDARY_RACE) instead of mixing changes. Nested child sessions count as part of their parent execution.
  • No toast or TUI routing. The server plugin reports through structured logs written to the host's stderr capture.

Connect Jev

For local use, connect once with the installed command:

jevguard login

Loading the plugin does not put jevguard on PATH. If it is not installed globally, run the one-line login instead:

npx @pablozrrrr/jevguard-cli login

JevGuard stores the key in your operating system's credential store instead of a repository file or shell history. In CI, provide TYPESAFE_API_KEY through the platform secret manager. See the security model for details.

JevGuard FAIL
pass 1 · warn 1 · fail 1 · skipped 0 · unavailable 0

The structured log (and, on hosts with a toast channel, one toast per turn) shows the aggregate outcome and the outcome counts; the structured log carries every rule's result, its raw violation probability or typed reason, and its scoped paths. The counts include every entry, including the single synthetic entry a review-level failure produces.

PASS, WARN, and FAIL are per-rule semantic outcomes. SKIPPED means that rule had no applicable change to evaluate. UNAVAILABLE means JevGuard could not safely or completely evaluate that rule.

Review history

JevGuard keeps a bounded, local, content-free history of your reviews so you can see how turns fared without grepping the host log. Every review appends the same safe structured-log projection plus an ISO 8601 timestamp to one JSON Lines file:

<XDG_DATA_HOME or ~/.local/share>/jevguard/reviews.jsonl

The file holds the aggregate counts, each result's rule or check ID, its outcome, and its typed reason or raw probability — the same allowlist as the structured log. It never holds a task, diff, prompt, model output, credential, or environment value. It is local-only, bounded (it rewrites itself once it passes 5 MB, keeping the newest records), and safe to delete at any time; the next review recreates it.

# human-readable summary
jevguard report

# the same aggregation as JSON
jevguard report --json

The report prints aggregated counts, rule IDs, check IDs, and reason codes only — never file paths, tasks, diffs, or credentials. A missing or empty history is a normal empty report, not an error.

Rule feedback

Tell JevGuard whether a FAIL was right. The answer is stored locally and only feeds the calibration shown by jevguard report; it never changes a rule, a threshold, or what the model returns.

# unanswered rule FAILs, newest first, numbered
jevguard feedback

# answer one: right = a real violation, wrong = a false alarm
jevguard feedback 1 wrong

Numbers stay fixed to the last listing. Built-in checks are not covered. See configuration for details.

Diagnose your setup

jevguard doctor checks your local setup without touching the network or changing anything:

jevguard doctor
jevguard doctor --json

It reports OK, WARN or FAIL for the credential source, .jev/rules.md, .jev/config.yaml and the OpenCode plugin registration, each with a hint when something is wrong. It never prints the API key or any part of it — only whether the key comes from the OS credential store or the TYPESAFE_API_KEY override. It exits 0 unless a check fails (warnings are fine) and 1 when one does. The project root is the first parent directory of the current directory that contains .git, or the current directory when there is none. For Claude Code it also reports whether the plugin is installed and enabled, the claude and node (22.13 or newer, on PATH) versions, and which config directory is in use (CLAUDE_CONFIG_DIR or ~/.claude); it only prints that path, never settings content.

Check rules on demand

jevguard rules check runs your repository rules against a diff you provide, without waiting for an agent turn:

git diff --cached | jevguard rules check --diff - --task "Add rate limiting to login"
jevguard rules check --diff change.diff --rule ARCH-001 --json

It evaluates the repository rules over the diff with the same scope filtering, evidence safety, slicing and gate thresholds as a review, but runs no built-in checks, starts no remediation and writes nothing to the review history. The input must be plain git diff output (or a plain ---/+++ unified diff) with a/ and b/ prefixes; anything else is refused whole, never partly evaluated (see configuration). --task is optional; without it a neutral default task is sent. It prints each rule's outcome, probability, severity and scoped paths, never the task or the diff. Exit codes: 0 completed with no FAIL and no UNAVAILABLE; 1 at least one rule FAILed; 2 it could not check completely (invalid arguments, an unreadable or unsupported diff, an unknown --rule, or an UNAVAILABLE result). See configuration for the full contract.

Implemented behavior

The current release implements the full local review path:

  • The OpenCode 2.2.x V2 plugin loads.
  • A completed assistant response is detected.
  • The direct parent user task and assistant-attributed diff are acquired.
  • Every ## <RULE-ID> block in .jev/rules.md is parsed in source order. Each block is validated independently, so one invalid block does not suppress its valid siblings, and every occurrence of a duplicated ID is invalid.
  • Each valid rule is processed independently. Its complete scoped evidence is planned locally, then every planned slice across all rules is dispatched through one batched Jev entry that resolves the credential once and uses bounded concurrency; each slice asks one Noul. The rule result is the deterministic maximum of its slice judgments, and a fitting rule still issues exactly one call. The rule asks Jev only when the gate config is valid, the rule is applicable, and its scoped evidence is complete and safe; it produces one typed violation probability or an operational outcome.
  • A failure before rule evaluation — no attributed diff, unreadable policy files, or a missing .jev/rules.md — is reported by the rule lane as one synthetic UNAVAILABLE entry with no rule ID, not one result per declared rule. When the turn itself is attributed, the built-in batch still runs.
  • The product-owned SCOPE-CREEP built-in runs on every attributed turn over the turn's complete, safe attributed patch, with fixed error thresholds (0.65/0.90) and no scope filtering. It can produce PASS, WARN, or FAIL.
  • The product-owned COMPLEXITY built-in runs on every attributed turn over the same complete, safe attributed patch. It uses a fixed advisory threshold of 0.50, independent of .jev/config.yaml, and can produce PASS or WARN but never FAIL.
  • Both built-ins are sent as one batch request with two independent named answers. One malformed or missing answer fails only its own check; the valid sibling still gates. A failed or malformed batch envelope makes both checks UNAVAILABLE.
  • The built-in batch runs concurrently with the rule lane. The rule lane dispatches one batched request for all of the turn's planned slices with a bounded adapter concurrency cap of four; the built-in batch keeps its own single request. One shared FIFO concurrency limit allows at most two Jev entries in flight across the plugin instance, counting the rule batch and the built-in batch as one entry each. With no attributed patch a built-in is SKIPPED; blocked or oversized evidence is UNAVAILABLE; a rule, policy-load, or config failure never suppresses the batch, and one built-in's answer never suppresses the other.
  • Reviews run in the background. The session.execution.succeeded handler returns as soon as the serialized attribution step is scheduled, so policy reads, Jev calls, and presentation do not block the agent.
  • The local gate maps each rule to PASS, WARN, or FAIL; a rule with no applicable scope is SKIPPED, and a rule that cannot be safely evaluated is UNAVAILABLE.
  • One structured log entry reports the turn (the 2.2.x server plugin has no toast channel). The log carries every entry's outcome, raw probability, or reason, including the built-in results and the synthetic review-level entry when there is one. The fixed result order is rules in source order, then SCOPE-CREEP, then COMPLEXITY.
  • After presentation, when remediation is enabled and the review contains any FAIL, at most one aggregate proposal request is sent to an isolated, deny-all child session parented to the source session, with an explicit model. The request carries every FAIL finding (local error rules and the SCOPE-CREEP built-in), the task, and the complete, safe, full attributed patch; a blocked or oversized patch records no proposal. A notifier or host failure never affects the review, presentation, or later turns.

The review itself injects no feedback into the agent session, prompts no session, and blocks no task. The proposal above is a strategy only; nothing is applied, and the user copies its manual apply instruction into their normal coding agent, whose ordinary turn is reviewed normally.

Roadmap

Implemented so far: multi-rule reviews, the SCOPE-CREEP and COMPLEXITY built-ins, the jev-init policy bootstrap, local review history with feedback and calibration (jevguard report, jevguard feedback), on-demand checks (jevguard rules check, jevguard doctor), and the Claude Code plugin. Planned work:

V0.3  Remaining built-in semantic check: test adequacy        planned
V0.4  jev-init: evidence-based policy bootstrap               done
V0.5  On-demand evidence and explanations                     partial (rules check)
V0.6  Opt-in, bounded auto-remediation                        planned
V0.7  Observe / enforce / remediate modes                     planned
V0.8  Rule packs                                              planned
V0.9  Local feedback and calibration                          done
V1    Claude Code and Codex adapters                          Claude Code done, Codex planned

The auto-propose slice is implemented: after a review with a FAIL, an enabled configuration asks for a strategy in an isolated, deny-all child session, and never applies a change. The V0.6 entry remains the future, unattended corrective loop; the proposer is a bounded, strategy-only step, not that loop.

The V0.4 jev-init skill is implemented: when no policy exists it bootstraps .jev/rules.md and .jev/config.yaml from the user's explicit constraints and strongly evidenced local conventions, records each rule's provenance, and writes both files only after validating the confirmed candidates.

CI and pull-request policy review come after V1, using the same versioned rules.

Architecture

JevGuard is a pnpm TypeScript monorepo. The core has no dependency on OpenCode, Claude Code, or their runtimes, which keeps the evaluation model portable across host adapters.

packages/
├── core/                 domain, policy, evaluation, gate
├── claude-code-adapter/  Claude Code tool-attributed units, hook and review worker
├── claude-code-plugin/   Claude Code plugin composition and artifact
├── node-infra/           shared Node infra: credentials, Jev transport, policy, history
├── opencode-adapter/     OpenCode attribution and presentation
└── plugin/               OpenCode composition root and CLI

plugin → opencode-adapter → node-infra → core
plugin ────────────────────→ node-infra
claude-code-plugin → claude-code-adapter → node-infra → core

Status

The multi-rule, multi-built-in, background and observe-only review is implemented. It loads in OpenCode, attributes one completed turn, parses every rule block in .jev/rules.md, asks Jev once per applicable rule and once per built-in batch, runs the SCOPE-CREEP and COMPLEXITY built-ins over the complete attributed patch behind one shared concurrency limit, applies the local gate to each, and presents one aggregate result as a structured log entry.

The local, private tarball (pnpm artifact:build, pnpm artifact:pack) packages that slice so a clean consumer can install it without workspace links.

OpenCode sees only structured logs and — when remediation is enabled and a review has a FAIL — one proposal child session. The review does not inject anything into the agent context, prompt the source session, add session messages, or block a task. The proposal is a separate, configurable, strategy-only workflow delivered by the server plugin; it never edits code and never applies a change. Only a user who opts in with delivery: agent in their own opencode.yaml gets one visible message per reviewed unit queued into the source session, at most 3 per session and never while the session is busy; see OpenCode delivery: agent. A real-host smoke on OpenCode 2.2.2 passed for observe; see OpenCode 2.2.x limitations.

The Claude Code plugin reuses the same core, policy files, gate, and local history. It reviews closed units of work in a detached worker and shows results in the session without sending them to the model; see the Claude Code guide.

Development

Requirements: Node.js >=22.13.0, pnpm 10.33.2 (via Corepack), and the Bun runtime for the CLI and OpenCode plugin loading.

corepack enable
pnpm install
pnpm typecheck
pnpm test

Use pnpm check to run format, lint, typecheck, and tests in sequence. See CONTRIBUTING.md for the full contribution workflow.

Documentation

License

MIT — see LICENSE.

About

Semantic policy engine for AI coding agents. Reviews every agent change against your repo's plain-Markdown rules and reports PASS/WARN/FAIL per rule. Runs in the background for OpenCode and Claude Code, never blocks the agent, never edits your code.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages