@pablozrrrr/jevguard 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +315 -95
- package/cli/main.js +4729 -1413
- package/index.js +4239 -1650
- package/package.json +6 -2
- package/skills/jev-init/SKILL.md +9 -3
- package/skills/jev-init/validate-init.js +2002 -696
- package/skills/jevguard-rules/SKILL.md +45 -3
- package/skills/jevguard-rules/validate-rules.js +1915 -613
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# JevGuard
|
|
2
2
|
|
|
3
3
|
> **Semantic policy engine for coding agents.**
|
|
4
|
+
>
|
|
5
|
+
> JevGuard reviews every change an AI coding agent makes against the rules of your
|
|
6
|
+
> repository, written in plain Markdown, and reports `PASS`, `WARN`, or `FAIL` per
|
|
7
|
+
> rule. It runs in the background for OpenCode and Claude Code, never blocks the
|
|
8
|
+
> agent, and never edits your code.
|
|
4
9
|
|
|
5
10
|

|
|
6
11
|
|
|
@@ -8,6 +13,30 @@ JevGuard checks the code an agent just changed against the policies that matter
|
|
|
8
13
|
your repository. It uses Jev for a narrow semantic judgment per rule, then applies
|
|
9
14
|
your local deterministic gate to return `PASS`, `WARN`, or `FAIL`.
|
|
10
15
|
|
|
16
|
+
| Host | Package | Status |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| [OpenCode](https://opencode.ai) | `@pablozrrrr/jevguard` | Available, targets OpenCode `1.18.32` |
|
|
19
|
+
| [Claude Code](https://claude.com/claude-code) | `@pablozrrrr/jevguard-claude-code` | Available through this repository's plugin marketplace |
|
|
20
|
+
| Codex | — | Planned |
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
# 1. Store your Jev key in the OS credential store (shared by both hosts)
|
|
26
|
+
bunx --package @pablozrrrr/jevguard jevguard login
|
|
27
|
+
|
|
28
|
+
# 2a. OpenCode: register the plugin in opencode.json
|
|
29
|
+
bunx --package @pablozrrrr/jevguard jevguard install
|
|
30
|
+
|
|
31
|
+
# 2b. Claude Code: install from the marketplace
|
|
32
|
+
claude plugin marketplace add pablozr/JevGuard
|
|
33
|
+
claude plugin install jevguard@jevguard
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then add your rules to `.jev/rules.md` (in OpenCode, the bundled `jev-init` skill can
|
|
37
|
+
bootstrap them) and keep working with your agent as usual. Details are in
|
|
38
|
+
[Install](#install) and the [Claude Code guide](./docs/configuration.md#claude-code).
|
|
39
|
+
|
|
11
40
|
```text
|
|
12
41
|
agent completes a turn
|
|
13
42
|
↓
|
|
@@ -27,13 +56,20 @@ specific policy, and leaves planning, code generation, and correction with the
|
|
|
27
56
|
coding agent.
|
|
28
57
|
|
|
29
58
|
> [!WARNING]
|
|
30
|
-
> JevGuard is in active development. The
|
|
31
|
-
>
|
|
32
|
-
> and `COMPLEXITY` built-ins.
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
59
|
+
> JevGuard is in active development. The OpenCode plugin targets OpenCode `2.2.x`
|
|
60
|
+
> (minimum `2.2.2`, `>=2.2.2 <2.3`) through the V2 plugin API and evaluates every rule
|
|
61
|
+
> block declared in `.jev/rules.md` plus the `SCOPE-CREEP` and `COMPLEXITY` built-ins.
|
|
62
|
+
> Reviews are background and observe-only: they report results but never alter the
|
|
63
|
+
> agent context or block a task. When remediation is enabled and a review has any
|
|
64
|
+
> `FAIL`, the plugin asks for a proposal in an isolated, deny-all child session; the
|
|
65
|
+
> proposal never edits code. See [OpenCode 2.2.x limitations](#opencode-22x-limitations).
|
|
66
|
+
> On Claude Code and OpenCode, a user who opts in with `mode: remediate` in their own
|
|
67
|
+
> `claude-code.yaml` or `opencode.yaml` gets a bounded chain of at most 3 visible
|
|
68
|
+
> correction messages to the agent; see
|
|
69
|
+
> [Remediation proposals](docs/configuration.md#remediation-proposals) and
|
|
70
|
+
> [OpenCode `mode: remediate`](docs/configuration.md#opencode-mode-remediate-bounded-chain).
|
|
71
|
+
> The Claude Code plugin is distributed through this repository's marketplace; see the
|
|
72
|
+
> [Claude Code guide](./docs/configuration.md#claude-code).
|
|
37
73
|
|
|
38
74
|
## Why JevGuard
|
|
39
75
|
|
|
@@ -52,6 +88,8 @@ JevGuard makes those questions versioned, scoped, and machine-actionable.
|
|
|
52
88
|
|
|
53
89
|
severity: error
|
|
54
90
|
scope: backend/**
|
|
91
|
+
scope: !backend/generated/**
|
|
92
|
+
evidence: code
|
|
55
93
|
|
|
56
94
|
### Rule
|
|
57
95
|
|
|
@@ -66,6 +104,15 @@ A controller performs domain decisions, calculations, or state mutations directl
|
|
|
66
104
|
Validation, HTTP mapping and delegation to services.
|
|
67
105
|
```
|
|
68
106
|
|
|
107
|
+
`scope:` may repeat. Each line adds an inclusion glob, and a leading `!` adds an
|
|
108
|
+
exclusion, so a path is in scope when it matches at least one inclusion and no
|
|
109
|
+
exclusion. `evidence:` chooses which files the rule is evaluated against — `code`
|
|
110
|
+
(the default), `docs`, or `any`. Because the default is `code`, prose documentation
|
|
111
|
+
(`.md`, `.mdx`, `.txt`) no longer participates in a rule's evidence unless the rule
|
|
112
|
+
opts in with `evidence: docs` or `evidence: any`; this is intentional. Config and
|
|
113
|
+
data formats (`.json`, `.yaml`, `.toml`, `.xml`, `.csv`, and others) stay in the
|
|
114
|
+
`code` class, so config rules keep their coverage.
|
|
115
|
+
|
|
69
116
|
The rule stays in the repository beside the code it governs. `Allowed` is a real
|
|
70
117
|
exception, not a suggestion. JevGuard evaluates it as part of the same rule.
|
|
71
118
|
|
|
@@ -86,11 +133,18 @@ applicable rules
|
|
|
86
133
|
one semantic judgment per rule
|
|
87
134
|
```
|
|
88
135
|
|
|
89
|
-
If complete attributed evidence is unavailable,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
136
|
+
If complete attributed evidence is unavailable, invalid, or contains a blocked
|
|
137
|
+
sensitive file, JevGuard marks the affected rule `UNAVAILABLE`. It never turns
|
|
138
|
+
incomplete evidence into a reassuring verdict. Evidence problems are scoped to the
|
|
139
|
+
rules they affect: a rule whose applicable files are all safe still runs even when
|
|
140
|
+
another rule's evidence is blocked.
|
|
141
|
+
|
|
142
|
+
Oversized evidence is no longer refused outright. A scoped diff above
|
|
143
|
+
`maxDiffLength` is split, in attributed file order, into one slice per file; an
|
|
144
|
+
individually oversized file is split into its ordered unified-diff hunks with the file
|
|
145
|
+
preamble repeated in every slice. Nothing is truncated, summarized, or dropped: an
|
|
146
|
+
unparseable oversized file or an indivisible oversized hunk makes the whole rule
|
|
147
|
+
`UNAVAILABLE/OVERSIZED_DIFF`.
|
|
94
148
|
|
|
95
149
|
Some failures happen before any policy rule is evaluated. If the attributed diff
|
|
96
150
|
cannot be built, the review is a single synthetic `UNAVAILABLE` entry with no rule
|
|
@@ -114,7 +168,15 @@ policy that translates it into an outcome.
|
|
|
114
168
|
| `warning` | `< 60%` | `≥ 60%` | never |
|
|
115
169
|
|
|
116
170
|
Those thresholds are per repository rule and locally configurable in
|
|
117
|
-
`.jev/config.yaml`.
|
|
171
|
+
`.jev/config.yaml`. Slicing does not change them: each slice is gated independently,
|
|
172
|
+
the rule outcome is the highest slice outcome (`FAIL > WARN > PASS`), and the rule's
|
|
173
|
+
`violationProbability` is the maximum across its slices. `PASS` requires every planned
|
|
174
|
+
slice to pass. A rule may produce at most 16 slices, and slicing may add at most 32
|
|
175
|
+
repository-rule Jev calls per turn beyond one per applicable rule; a rule that does not
|
|
176
|
+
fit is `UNAVAILABLE/SLICE_LIMIT_EXCEEDED` with no Jev call and later rules still use the
|
|
177
|
+
remaining budget. Any failed, blocked, or out-of-range slice makes the whole rule
|
|
178
|
+
`UNAVAILABLE`, and no sibling probability is presented as a verdict. The built-ins do
|
|
179
|
+
not slice.
|
|
118
180
|
|
|
119
181
|
JevGuard also runs two product-owned built-ins on every attributed turn,
|
|
120
182
|
`SCOPE-CREEP` and `COMPLEXITY`. Both receive the turn's complete, safe attributed
|
|
@@ -139,9 +201,12 @@ concurrency limit caps the plugin instance at two Jev requests in flight, counti
|
|
|
139
201
|
built-in batch as a single request. Results join the same aggregate in a fixed order:
|
|
140
202
|
rules in source order, then `SCOPE-CREEP`, then `COMPLEXITY`.
|
|
141
203
|
|
|
142
|
-
Reviews run in the background. The
|
|
143
|
-
|
|
144
|
-
the
|
|
204
|
+
Reviews run in the background. The unit of review is one session execution, from
|
|
205
|
+
`session.execution.started` to `session.execution.succeeded`; the event handler returns
|
|
206
|
+
as soon as the serialized attribution step is scheduled, so policy reads, Jev calls,
|
|
207
|
+
and presentation never sit on the agent's critical path. An `interrupted` or `failed`
|
|
208
|
+
execution is not reviewed: the plugin discards it and logs `JevGuard skipped ·
|
|
209
|
+
interrupted|failed`.
|
|
145
210
|
|
|
146
211
|
## Safe auto-propose remediation
|
|
147
212
|
|
|
@@ -152,7 +217,7 @@ delivered entirely by the same server plugin.
|
|
|
152
217
|
|
|
153
218
|
When `.jev/config.yaml` enables remediation (it is enabled by default) and a review
|
|
154
219
|
contains any `FAIL`, the plugin, after presenting the review, sends one aggregate
|
|
155
|
-
proposal request to
|
|
220
|
+
proposal request to an isolated child session:
|
|
156
221
|
|
|
157
222
|
- The request aggregates every `FAIL` finding in the review — local `error` rules and
|
|
158
223
|
the `SCOPE-CREEP` built-in — together with the turn's task and its **complete, safe,
|
|
@@ -162,28 +227,33 @@ proposal request to a hidden `jevguard-proposer` subagent:
|
|
|
162
227
|
attributed file is blocked or the patch is oversized, the plugin records no proposal
|
|
163
228
|
at all — even when a rule-scoped `FAIL` exists — because there is no complete safe
|
|
164
229
|
evidence to send. It never falls back to a repository or global diff.
|
|
165
|
-
- The request is sent in an isolated child session parented to the source session.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
230
|
+
- The request is sent in an isolated child session parented to the source session.
|
|
231
|
+
The child session is created with wildcard-deny permissions, so it cannot call a
|
|
232
|
+
tool, edit a file, or produce a patch. It returns a strategy and one manual apply
|
|
233
|
+
instruction only. No agent is registered for it.
|
|
234
|
+
- The child session always gets an explicit model: `remediation.model` from
|
|
235
|
+
`.jev/config.yaml` when set, otherwise the model of the source session. When neither
|
|
236
|
+
is known, no proposal is created and the plugin logs
|
|
237
|
+
`REMEDIATION_PROPOSAL_SKIPPED_NO_MODEL`.
|
|
238
|
+
- A proposal that does not finish within 120 seconds is interrupted and logged as
|
|
239
|
+
`REMEDIATION_PROPOSAL_TIMEOUT`.
|
|
240
|
+
- The server plugin has no toast or TUI channel on OpenCode `2.2.x`, so readiness is
|
|
241
|
+
reported through the structured log only. It carries no rule, task, diff, finding,
|
|
242
|
+
or credential.
|
|
173
243
|
|
|
174
244
|
The plugin creates at most one proposal per evaluated turn, and the child session is
|
|
175
245
|
excluded from review for the plugin lifetime, so a proposal can never recurse or
|
|
176
246
|
trigger a second one. A host, model, or toast failure is contained and never changes
|
|
177
247
|
the review. A contained proposal failure — an invalid model specifier, a child-session
|
|
178
|
-
failure,
|
|
248
|
+
failure, a prompt failure, or a timeout — writes one structured
|
|
179
249
|
`error` log entry carrying only a typed reason code, never any task, diff, finding,
|
|
180
250
|
path, or credential.
|
|
181
251
|
|
|
182
252
|
The proposer is a strategy step, not an apply step: it never edits code, and nothing
|
|
183
253
|
is applied automatically. You read the proposal, copy its manual apply instruction
|
|
184
|
-
into your normal coding agent, and apply it there. That apply is an ordinary
|
|
185
|
-
|
|
186
|
-
|
|
254
|
+
into your normal coding agent, and apply it there. That apply is an ordinary
|
|
255
|
+
execution, so the server plugin reviews it normally when it succeeds — exactly like any
|
|
256
|
+
other turn.
|
|
187
257
|
|
|
188
258
|
The proposal prompt treats the task, paths, diff, rules, and findings as untrusted
|
|
189
259
|
data, and requires a separate explicit confirmation before proposing changes to tests,
|
|
@@ -193,8 +263,14 @@ or secret is logged.
|
|
|
193
263
|
## Install
|
|
194
264
|
|
|
195
265
|
Installing `@pablozrrrr/jevguard` does **not** register it with OpenCode by itself.
|
|
196
|
-
OpenCode only loads plugins listed in `
|
|
197
|
-
|
|
266
|
+
OpenCode `2.2.x` only loads plugins listed in `plugins: [...]` in `opencode.json`.
|
|
267
|
+
|
|
268
|
+
> [!WARNING]
|
|
269
|
+
> The `jevguard install` command below still writes the OpenCode `1.x` `plugin` key.
|
|
270
|
+
> Until it is migrated, register the plugin on `2.2.x` with the manual `plugins`
|
|
271
|
+
> entry shown after it.
|
|
272
|
+
|
|
273
|
+
The installer is run from the installed CLI:
|
|
198
274
|
|
|
199
275
|
```sh
|
|
200
276
|
# global config: ~/.config/opencode/opencode.json
|
|
@@ -204,10 +280,12 @@ jevguard install
|
|
|
204
280
|
jevguard install --project
|
|
205
281
|
```
|
|
206
282
|
|
|
207
|
-
The installer asks for confirmation, edits only the `
|
|
208
|
-
idempotent. It
|
|
209
|
-
|
|
210
|
-
|
|
283
|
+
The installer asks for confirmation, edits only the `plugins` array, and is
|
|
284
|
+
idempotent. It never writes the legacy `plugin` key, but an entry already there counts
|
|
285
|
+
as installed. It only edits a `plugins` array of package strings or
|
|
286
|
+
`{ "package": "..." }` objects; any other shape is left unchanged and the installer
|
|
287
|
+
prints the manual snippet instead. `jevguard doctor` also checks that
|
|
288
|
+
`opencode --version` is in the supported range `>=2.2.2 <2.3`. If `jevguard` is not on
|
|
211
289
|
`PATH`, run it through `bunx`:
|
|
212
290
|
|
|
213
291
|
```sh
|
|
@@ -217,7 +295,7 @@ bunx --package @pablozrrrr/jevguard jevguard install
|
|
|
217
295
|
The manual alternative is one entry in `opencode.json`:
|
|
218
296
|
|
|
219
297
|
```json
|
|
220
|
-
{ "
|
|
298
|
+
{ "plugins": ["@pablozrrrr/jevguard"] }
|
|
221
299
|
```
|
|
222
300
|
|
|
223
301
|
OpenCode resolves the package from the npm registry or its cache and loads it on the
|
|
@@ -232,9 +310,9 @@ after changing the plugin list.
|
|
|
232
310
|
### Build the local artifact
|
|
233
311
|
|
|
234
312
|
The artifact ships the plugin as a **locally packed tarball** that bundles the
|
|
235
|
-
private `@jevguard/core` and `@jevguard/opencode-adapter`
|
|
236
|
-
clean consumer installs one tarball and never resolves a
|
|
237
|
-
private registry package. It also packages the `jevguard-rules` and `jev-init`
|
|
313
|
+
private `@jevguard/core`, `@jevguard/node-infra` and `@jevguard/opencode-adapter`
|
|
314
|
+
workspace code, so a clean consumer installs one tarball and never resolves a
|
|
315
|
+
workspace link or a private registry package. It also packages the `jevguard-rules` and `jev-init`
|
|
238
316
|
skills and their private validators under `skills/<name>/`.
|
|
239
317
|
|
|
240
318
|
Build the tarball from this repository:
|
|
@@ -256,12 +334,12 @@ does not vendor them and does not promise an offline install.
|
|
|
256
334
|
|
|
257
335
|
### Load the plugin
|
|
258
336
|
|
|
259
|
-
OpenCode `
|
|
337
|
+
OpenCode `2.2.x` resolves a **bare** `opencode.json` `plugins` entry from the npm
|
|
260
338
|
registry or its cache:
|
|
261
339
|
|
|
262
340
|
```json
|
|
263
341
|
{
|
|
264
|
-
"
|
|
342
|
+
"plugins": ["@pablozrrrr/jevguard"]
|
|
265
343
|
}
|
|
266
344
|
```
|
|
267
345
|
|
|
@@ -275,18 +353,36 @@ bun add /path/to/pablozrrrr-jevguard-<version>.tgz
|
|
|
275
353
|
|
|
276
354
|
```ts
|
|
277
355
|
// .opencode/plugins/jevguard.ts
|
|
278
|
-
export {
|
|
356
|
+
export { default } from "@pablozrrrr/jevguard";
|
|
279
357
|
```
|
|
280
358
|
|
|
281
359
|
OpenCode loads `.opencode/plugins/` with its bundled Bun runtime. The shim runs as
|
|
282
360
|
a local plugin module and resolves `@pablozrrrr/jevguard` from
|
|
283
|
-
`.opencode/node_modules`. Use either the bare `
|
|
361
|
+
`.opencode/node_modules`. Use either the bare `plugins` entry or the local shim, not
|
|
284
362
|
both.
|
|
285
363
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
fails — creates the proposal child session automatically.
|
|
364
|
+
The package's default export is the V2 plugin object (`{ id: "jevguard", setup }`).
|
|
365
|
+
Remediation is built into the same server plugin, so no second entrypoint or shim is
|
|
366
|
+
needed: it reviews and reports executions, and — when remediation is enabled and a
|
|
367
|
+
review fails — creates the proposal child session automatically. The bundled skills are
|
|
368
|
+
registered through the host skill editor (`skill.transform`).
|
|
369
|
+
|
|
370
|
+
### Claude Code
|
|
371
|
+
|
|
372
|
+
Inside Claude Code:
|
|
373
|
+
|
|
374
|
+
```text
|
|
375
|
+
/plugin marketplace add pablozr/JevGuard
|
|
376
|
+
/plugin install jevguard@jevguard
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The Claude Code plugin reviews each closed unit of work in a detached background
|
|
380
|
+
worker, shows the result in the session (status line, transcript line, and a toast on
|
|
381
|
+
`FAIL`) without sending it to the model, and records it in the same local history.
|
|
382
|
+
Remediation proposals are off by default and are enabled from your user config, never
|
|
383
|
+
from the repository. It needs `node` `>=22.13.0`, `npm`, and `git` on `PATH`. See the
|
|
384
|
+
[Claude Code guide](./docs/configuration.md#claude-code) for what is reviewed, the
|
|
385
|
+
in-session display, and remediation.
|
|
290
386
|
|
|
291
387
|
### Author rules with the bundled skill
|
|
292
388
|
|
|
@@ -335,11 +431,36 @@ jevguard login
|
|
|
335
431
|
|
|
336
432
|
Known limitations:
|
|
337
433
|
|
|
338
|
-
- Target host is OpenCode `
|
|
339
|
-
|
|
434
|
+
- Target host is OpenCode `2.2.x` (minimum `2.2.2`). The plugin does not load on
|
|
435
|
+
`2.1.x` or `1.x`. A real-host smoke on `2.2.2` (2026-11-02) covered observe
|
|
436
|
+
and `remediate` (FAIL review, deny-all proposer, attempt 1/2, `RESOLVED`).
|
|
437
|
+
- OpenCode `2.2.2` does not install dependencies for a `file:` plugin spec. Use
|
|
438
|
+
the registry package; a local `file:` build needs its dependencies installed
|
|
439
|
+
next to it.
|
|
340
440
|
- The plugin and CLI run on Bun, and the packed `jevguard` bin keeps a Bun
|
|
341
441
|
shebang. Install and run the artifact with the Bun runtime.
|
|
342
442
|
|
|
443
|
+
### OpenCode 2.2.x limitations
|
|
444
|
+
|
|
445
|
+
- **Minimum version `2.2.2`.** The plugin declares `engines.opencode` `>=2.2.2 <2.3`
|
|
446
|
+
and is pinned to the `2.2.x` plugin API.
|
|
447
|
+
- **Git-ignored files are out of scope.** The execution snapshot comes from the host's
|
|
448
|
+
working-tree diff, which leaves out files ignored by Git; a write the diff cannot
|
|
449
|
+
show makes the execution `UNAVAILABLE` (`INCOMPLETE_FILE_DIFF`) instead of reviewing
|
|
450
|
+
a partial patch.
|
|
451
|
+
- **Interrupted or failed executions are not reviewed.** Their writes stay on disk and
|
|
452
|
+
count as pre-existing changes for the next execution's snapshot, so they are not
|
|
453
|
+
attributed to it.
|
|
454
|
+
- **`opencode run --standalone`.** The review runs after the execution succeeds; a
|
|
455
|
+
standalone run can exit before it finishes. JevGuard reviews reliably in the TUI or
|
|
456
|
+
with `opencode serve` plus `opencode run --server`.
|
|
457
|
+
- **Concurrent executions.** When two sessions run at once, or an execution starts
|
|
458
|
+
while the previous one is still being finished, the review is `UNAVAILABLE`
|
|
459
|
+
(`CONCURRENT_SESSIONS`, `BOUNDARY_RACE`) instead of mixing changes. Nested child
|
|
460
|
+
sessions count as part of their parent execution.
|
|
461
|
+
- **No toast or TUI routing.** The server plugin reports through structured logs
|
|
462
|
+
written to the host's stderr capture.
|
|
463
|
+
|
|
343
464
|
## Connect Jev
|
|
344
465
|
|
|
345
466
|
For local use, connect once with the installed command:
|
|
@@ -364,8 +485,8 @@ JevGuard FAIL
|
|
|
364
485
|
pass 1 · warn 1 · fail 1 · skipped 0 · unavailable 0
|
|
365
486
|
```
|
|
366
487
|
|
|
367
|
-
|
|
368
|
-
structured log carries every rule's result, its raw violation probability or typed
|
|
488
|
+
The structured log (and, on hosts with a toast channel, one toast per turn) shows
|
|
489
|
+
the aggregate outcome and the outcome counts; the structured log carries every rule's result, its raw violation probability or typed
|
|
369
490
|
reason, and its scoped paths. The counts include every entry, including the single
|
|
370
491
|
synthetic entry a review-level failure produces.
|
|
371
492
|
|
|
@@ -373,6 +494,93 @@ synthetic entry a review-level failure produces.
|
|
|
373
494
|
rule had no applicable change to evaluate. `UNAVAILABLE` means JevGuard could not
|
|
374
495
|
safely or completely evaluate that rule.
|
|
375
496
|
|
|
497
|
+
## Review history
|
|
498
|
+
|
|
499
|
+
JevGuard keeps a bounded, local, content-free history of your reviews so you can see
|
|
500
|
+
how turns fared without grepping the host log. Every review appends the same safe
|
|
501
|
+
structured-log projection plus an ISO 8601 timestamp to one JSON Lines file:
|
|
502
|
+
|
|
503
|
+
```text
|
|
504
|
+
<XDG_DATA_HOME or ~/.local/share>/jevguard/reviews.jsonl
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
The file holds the aggregate counts, each result's rule or check ID, its outcome, and
|
|
508
|
+
its typed reason or raw probability — the same allowlist as the structured log. It
|
|
509
|
+
never holds a task, diff, prompt, model output, credential, or environment value. It
|
|
510
|
+
is local-only, bounded (it rewrites itself once it passes 5 MB, keeping the newest
|
|
511
|
+
records), and safe to delete at any time; the next review recreates it.
|
|
512
|
+
|
|
513
|
+
```sh
|
|
514
|
+
# human-readable summary
|
|
515
|
+
jevguard report
|
|
516
|
+
|
|
517
|
+
# the same aggregation as JSON
|
|
518
|
+
jevguard report --json
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
The report prints aggregated counts, rule IDs, check IDs, and reason codes only —
|
|
522
|
+
never file paths, tasks, diffs, or credentials. A missing or empty history is a
|
|
523
|
+
normal empty report, not an error.
|
|
524
|
+
|
|
525
|
+
### Rule feedback
|
|
526
|
+
|
|
527
|
+
Tell JevGuard whether a `FAIL` was right. The answer is stored locally and only
|
|
528
|
+
feeds the calibration shown by `jevguard report`; it never changes a rule, a
|
|
529
|
+
threshold, or what the model returns.
|
|
530
|
+
|
|
531
|
+
```sh
|
|
532
|
+
# unanswered rule FAILs, newest first, numbered
|
|
533
|
+
jevguard feedback
|
|
534
|
+
|
|
535
|
+
# answer one: right = a real violation, wrong = a false alarm
|
|
536
|
+
jevguard feedback 1 wrong
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Numbers stay fixed to the last listing. Built-in checks are not covered. See
|
|
540
|
+
[configuration](./docs/configuration.md)
|
|
541
|
+
for details.
|
|
542
|
+
|
|
543
|
+
## Diagnose your setup
|
|
544
|
+
|
|
545
|
+
`jevguard doctor` checks your local setup without touching the network or changing
|
|
546
|
+
anything:
|
|
547
|
+
|
|
548
|
+
```sh
|
|
549
|
+
jevguard doctor
|
|
550
|
+
jevguard doctor --json
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
It reports `OK`, `WARN` or `FAIL` for the credential source, `.jev/rules.md`,
|
|
554
|
+
`.jev/config.yaml` and the OpenCode plugin registration, each with a hint when
|
|
555
|
+
something is wrong. It never prints the API key or any part of it — only whether the
|
|
556
|
+
key comes from the OS credential store or the `TYPESAFE_API_KEY` override. It exits
|
|
557
|
+
`0` unless a check fails (warnings are fine) and `1` when one does. The project root is
|
|
558
|
+
the first parent directory of the current directory that contains `.git`, or the
|
|
559
|
+
current directory when there is none. It does not check Claude Code installs yet.
|
|
560
|
+
|
|
561
|
+
## Check rules on demand
|
|
562
|
+
|
|
563
|
+
`jevguard rules check` runs your repository rules against a diff you provide, without
|
|
564
|
+
waiting for an agent turn:
|
|
565
|
+
|
|
566
|
+
```sh
|
|
567
|
+
git diff --cached | jevguard rules check --diff - --task "Add rate limiting to login"
|
|
568
|
+
jevguard rules check --diff change.diff --rule ARCH-001 --json
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
It evaluates the repository rules over the diff with the same scope filtering,
|
|
572
|
+
evidence safety, slicing and gate thresholds as a review, but runs no built-in checks,
|
|
573
|
+
starts no remediation and writes nothing to the review history. The input must be
|
|
574
|
+
plain `git diff` output (or a plain `---`/`+++` unified diff) with `a/` and `b/`
|
|
575
|
+
prefixes; anything else is refused whole, never partly evaluated (see
|
|
576
|
+
[configuration](./docs/configuration.md#cli-diagnostics-and-on-demand-rule-checks)).
|
|
577
|
+
`--task` is optional; without it a neutral default task is sent. It prints each rule's outcome, probability, severity and
|
|
578
|
+
scoped paths, never the task or the diff. Exit codes: `0` completed with no `FAIL` and
|
|
579
|
+
no `UNAVAILABLE`; `1` at least one rule `FAIL`ed; `2` it could not check completely
|
|
580
|
+
(invalid arguments, an unreadable or unsupported diff, an unknown `--rule`, or an
|
|
581
|
+
`UNAVAILABLE` result). See [configuration](./docs/configuration.md#cli-diagnostics-and-on-demand-rule-checks)
|
|
582
|
+
for the full contract.
|
|
583
|
+
|
|
376
584
|
## Implemented behavior
|
|
377
585
|
|
|
378
586
|
The current release implements the full local review path:
|
|
@@ -383,10 +591,13 @@ The current release implements the full local review path:
|
|
|
383
591
|
- Every `## <RULE-ID>` block in `.jev/rules.md` is parsed in source order. Each
|
|
384
592
|
block is validated independently, so one invalid block does not suppress its
|
|
385
593
|
valid siblings, and every occurrence of a duplicated ID is invalid.
|
|
386
|
-
- Each valid rule is processed independently.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
594
|
+
- Each valid rule is processed independently. Its complete scoped evidence is planned
|
|
595
|
+
locally, then every planned slice across all rules is dispatched through one batched
|
|
596
|
+
Jev entry that resolves the credential once and uses bounded concurrency; each slice
|
|
597
|
+
asks one Noul. The rule result is the deterministic maximum of its slice judgments,
|
|
598
|
+
and a fitting rule still issues exactly one call. The rule asks Jev only when the
|
|
599
|
+
gate config is valid, the rule is applicable, and its scoped evidence is complete and
|
|
600
|
+
safe; it produces one typed violation probability or an operational outcome.
|
|
390
601
|
- A failure before rule evaluation — no attributed diff, unreadable policy files, or
|
|
391
602
|
a missing `.jev/rules.md` — is reported by the rule lane as one synthetic
|
|
392
603
|
`UNAVAILABLE` entry with no rule ID, not one result per declared rule. When the
|
|
@@ -401,26 +612,28 @@ The current release implements the full local review path:
|
|
|
401
612
|
- Both built-ins are sent as one batch request with two independent named answers.
|
|
402
613
|
One malformed or missing answer fails only its own check; the valid sibling still
|
|
403
614
|
gates. A failed or malformed batch envelope makes both checks `UNAVAILABLE`.
|
|
404
|
-
- The built-in batch runs concurrently with the
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
615
|
+
- The built-in batch runs concurrently with the rule lane. The rule lane dispatches
|
|
616
|
+
one batched request for all of the turn's planned slices with a bounded adapter
|
|
617
|
+
concurrency cap of four; the built-in batch keeps its own single request. One shared
|
|
618
|
+
FIFO concurrency limit allows at most two Jev entries in flight across the plugin
|
|
619
|
+
instance, counting the rule batch and the built-in batch as one entry each. With no
|
|
620
|
+
attributed patch a built-in is `SKIPPED`; blocked or oversized evidence is
|
|
621
|
+
`UNAVAILABLE`; a rule, policy-load, or config failure never suppresses the batch, and
|
|
622
|
+
one built-in's answer never suppresses the other.
|
|
623
|
+
- Reviews run in the background. The `session.execution.succeeded` handler returns
|
|
624
|
+
as soon as the serialized attribution step is scheduled, so policy reads, Jev calls,
|
|
625
|
+
and presentation do not block the agent.
|
|
413
626
|
- The local gate maps each rule to `PASS`, `WARN`, or `FAIL`; a rule with no
|
|
414
627
|
applicable scope is `SKIPPED`, and a rule that cannot be safely evaluated is
|
|
415
628
|
`UNAVAILABLE`.
|
|
416
|
-
- One
|
|
629
|
+
- One structured log entry reports the turn (the `2.2.x` server plugin has no toast
|
|
630
|
+
channel). The log
|
|
417
631
|
carries every entry's outcome, raw probability, or reason, including the built-in
|
|
418
632
|
results and the synthetic review-level entry when there is one. The fixed result
|
|
419
633
|
order is rules in source order, then `SCOPE-CREEP`, then `COMPLEXITY`.
|
|
420
634
|
- After presentation, when remediation is enabled and the review contains any `FAIL`,
|
|
421
|
-
at most one aggregate proposal request is sent to
|
|
422
|
-
|
|
423
|
-
toast reports that the proposal is ready. The request carries every `FAIL` finding
|
|
635
|
+
at most one aggregate proposal request is sent to an isolated, deny-all child
|
|
636
|
+
session parented to the source session, with an explicit model. The request carries every `FAIL` finding
|
|
424
637
|
(local `error` rules and the `SCOPE-CREEP` built-in), the task, and the complete,
|
|
425
638
|
safe, full attributed patch; a blocked or oversized patch records no proposal. A
|
|
426
639
|
notifier or host failure never affects the review, presentation, or later turns.
|
|
@@ -432,30 +645,25 @@ ordinary turn is reviewed normally.
|
|
|
432
645
|
|
|
433
646
|
## Roadmap
|
|
434
647
|
|
|
435
|
-
|
|
436
|
-
|
|
648
|
+
Implemented so far: multi-rule reviews, the `SCOPE-CREEP` and `COMPLEXITY`
|
|
649
|
+
built-ins, the `jev-init` policy bootstrap, local review history with feedback and
|
|
650
|
+
calibration (`jevguard report`, `jevguard feedback`), on-demand checks
|
|
651
|
+
(`jevguard rules check`, `jevguard doctor`), and the Claude Code plugin. Planned work:
|
|
437
652
|
|
|
438
653
|
```text
|
|
439
|
-
V0.3 Remaining built-in semantic check: test adequacy
|
|
440
|
-
|
|
441
|
-
V0.
|
|
442
|
-
|
|
443
|
-
V0.
|
|
444
|
-
|
|
445
|
-
V0.
|
|
446
|
-
|
|
447
|
-
V0.7 Observe / enforce / remediate modes
|
|
448
|
-
↓
|
|
449
|
-
V0.8 Rule packs
|
|
450
|
-
↓
|
|
451
|
-
V0.9 Local feedback and calibration
|
|
452
|
-
↓
|
|
453
|
-
V1 Claude Code and Codex adapters
|
|
654
|
+
V0.3 Remaining built-in semantic check: test adequacy planned
|
|
655
|
+
V0.4 jev-init: evidence-based policy bootstrap done
|
|
656
|
+
V0.5 On-demand evidence and explanations partial (rules check)
|
|
657
|
+
V0.6 Opt-in, bounded auto-remediation planned
|
|
658
|
+
V0.7 Observe / enforce / remediate modes planned
|
|
659
|
+
V0.8 Rule packs planned
|
|
660
|
+
V0.9 Local feedback and calibration done
|
|
661
|
+
V1 Claude Code and Codex adapters Claude Code done, Codex planned
|
|
454
662
|
```
|
|
455
663
|
|
|
456
664
|
The auto-propose slice is implemented: after a review with a `FAIL`, an enabled
|
|
457
|
-
configuration asks
|
|
458
|
-
|
|
665
|
+
configuration asks for a strategy in an isolated, deny-all child session, and never
|
|
666
|
+
applies a change. The `V0.6` entry remains the
|
|
459
667
|
future, unattended corrective loop; the proposer is a bounded, strategy-only step, not
|
|
460
668
|
that loop.
|
|
461
669
|
|
|
@@ -468,17 +676,24 @@ CI and pull-request policy review come after V1, using the same versioned rules.
|
|
|
468
676
|
|
|
469
677
|
## Architecture
|
|
470
678
|
|
|
471
|
-
JevGuard is a pnpm TypeScript monorepo. The core has no dependency on OpenCode
|
|
472
|
-
|
|
679
|
+
JevGuard is a pnpm TypeScript monorepo. The core has no dependency on OpenCode,
|
|
680
|
+
Claude Code, or their runtimes, which keeps the evaluation model portable across
|
|
681
|
+
host adapters.
|
|
473
682
|
|
|
474
683
|
```text
|
|
475
684
|
packages/
|
|
476
685
|
├── core/ domain, policy, evaluation, gate
|
|
686
|
+
├── claude-code-adapter/ Claude Code tool-attributed units, hook and review worker
|
|
687
|
+
├── claude-code-plugin/ Claude Code plugin composition and artifact
|
|
688
|
+
├── node-infra/ shared Node infra: credentials, Jev transport, policy, history
|
|
477
689
|
├── opencode-adapter/ OpenCode attribution and presentation
|
|
478
690
|
├── plugin/ OpenCode composition root and CLI
|
|
479
691
|
└── testkit/ fixtures and contract helpers
|
|
480
692
|
|
|
481
|
-
plugin → opencode-adapter → core
|
|
693
|
+
plugin → opencode-adapter → node-infra → core
|
|
694
|
+
plugin ────────────────────→ node-infra
|
|
695
|
+
claude-code-plugin → claude-code-adapter → node-infra → core
|
|
696
|
+
testkit ──────────────────────────────→ core
|
|
482
697
|
```
|
|
483
698
|
|
|
484
699
|
## Status
|
|
@@ -489,18 +704,23 @@ loads in OpenCode, attributes one completed turn, parses every rule block in
|
|
|
489
704
|
the `SCOPE-CREEP`
|
|
490
705
|
and `COMPLEXITY` built-ins over the complete attributed patch behind one shared
|
|
491
706
|
concurrency limit, applies the local gate to each, and presents one aggregate result
|
|
492
|
-
as a
|
|
707
|
+
as a structured log entry.
|
|
493
708
|
|
|
494
709
|
The local, private tarball (`pnpm artifact:build`, `pnpm artifact:pack`) packages
|
|
495
710
|
that slice so a clean consumer can install it without workspace links.
|
|
496
711
|
|
|
497
|
-
OpenCode sees only
|
|
498
|
-
|
|
712
|
+
OpenCode sees only structured logs and — when remediation is enabled and a review
|
|
713
|
+
has a `FAIL` — one proposal child session.
|
|
499
714
|
The review does not inject anything into the agent context, prompt the source session,
|
|
500
715
|
add session messages, or block a task. The proposal is a separate, configurable,
|
|
501
716
|
strategy-only workflow delivered by the server plugin; it never edits code and never
|
|
502
|
-
applies a change.
|
|
503
|
-
|
|
717
|
+
applies a change. A real-host smoke on OpenCode `2.2.2` passed; see
|
|
718
|
+
[OpenCode 2.2.x limitations](#opencode-22x-limitations).
|
|
719
|
+
|
|
720
|
+
The Claude Code plugin reuses the same core, policy files, gate, and local history. It
|
|
721
|
+
reviews closed units of work in a detached worker and shows results in the session
|
|
722
|
+
without sending them to the model; see the
|
|
723
|
+
[Claude Code guide](./docs/configuration.md#claude-code).
|
|
504
724
|
|
|
505
725
|
## Development
|
|
506
726
|
|