@pablozrrrr/jevguard 0.1.0 → 0.2.2
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 +156 -44
- package/cli/main.js +688 -38
- package/index.js +808 -70
- package/package.json +5 -3
- package/skills/jev-init/SKILL.md +253 -0
- package/skills/jev-init/validate-init.js +1536 -0
- package/skills/jevguard-rules/SKILL.md +17 -1
- package/skills/jevguard-rules/validate-rules.js +264 -0
package/README.md
CHANGED
|
@@ -27,10 +27,13 @@ specific policy, and leaves planning, code generation, and correction with the
|
|
|
27
27
|
coding agent.
|
|
28
28
|
|
|
29
29
|
> [!WARNING]
|
|
30
|
-
> JevGuard is in active development. The current release targets OpenCode `1.18.
|
|
31
|
-
> evaluates every rule block declared in `.jev/rules.md` plus the `SCOPE-CREEP`
|
|
32
|
-
> `COMPLEXITY` built-ins
|
|
33
|
-
>
|
|
30
|
+
> JevGuard is in active development. The current release targets OpenCode `1.18.32`
|
|
31
|
+
> and evaluates every rule block declared in `.jev/rules.md` plus the `SCOPE-CREEP`
|
|
32
|
+
> and `COMPLEXITY` built-ins. Reviews are background and observe-only: they report
|
|
33
|
+
> results but never alter the agent context or block a task. When remediation is
|
|
34
|
+
> enabled and a review has any `FAIL`, the plugin asks a hidden `jevguard-proposer`
|
|
35
|
+
> subagent for a proposal in an isolated child session and shows a generic toast; the
|
|
36
|
+
> proposer never edits code.
|
|
34
37
|
|
|
35
38
|
## Why JevGuard
|
|
36
39
|
|
|
@@ -122,7 +125,7 @@ malformed answer fails only its own check.
|
|
|
122
125
|
`SCOPE-CREEP` asks whether the change contains material functional, behavioral,
|
|
123
126
|
architectural, dependency, configuration, documentation, or refactoring work the task
|
|
124
127
|
did not request and that is not reasonably necessary to complete it. It uses fixed
|
|
125
|
-
`error` thresholds (`
|
|
128
|
+
`error` thresholds (`65%`/`90%`), independent of `.jev/config.yaml`.
|
|
126
129
|
|
|
127
130
|
`COMPLEXITY` asks whether the change introduces material complexity disproportionate
|
|
128
131
|
to, or not reasonably necessary for, completing the task, such as unnecessary
|
|
@@ -140,32 +143,99 @@ Reviews run in the background. The idle event returns as soon as the serialized
|
|
|
140
143
|
attribution step is scheduled; policy reads, Jev calls, and presentation never sit on
|
|
141
144
|
the agent's critical path.
|
|
142
145
|
|
|
146
|
+
## Safe auto-propose remediation
|
|
147
|
+
|
|
148
|
+
Reviews are observe-only by default: a review never injects feedback into the agent
|
|
149
|
+
context, adds a session message, prompts a session, blocks a turn, or changes your
|
|
150
|
+
code. A separate, configurable path can turn a review result into a proposal, and it is
|
|
151
|
+
delivered entirely by the same server plugin.
|
|
152
|
+
|
|
153
|
+
When `.jev/config.yaml` enables remediation (it is enabled by default) and a review
|
|
154
|
+
contains any `FAIL`, the plugin, after presenting the review, sends one aggregate
|
|
155
|
+
proposal request to a hidden `jevguard-proposer` subagent:
|
|
156
|
+
|
|
157
|
+
- The request aggregates every `FAIL` finding in the review — local `error` rules and
|
|
158
|
+
the `SCOPE-CREEP` built-in — together with the turn's task and its **complete, safe,
|
|
159
|
+
full attributed patch**. `COMPLEXITY` is advisory and never fails, and `warning`
|
|
160
|
+
rules never fail.
|
|
161
|
+
- The full attributed patch must pass the same safety policy as the review. If any
|
|
162
|
+
attributed file is blocked or the patch is oversized, the plugin records no proposal
|
|
163
|
+
at all — even when a rule-scoped `FAIL` exists — because there is no complete safe
|
|
164
|
+
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. The
|
|
166
|
+
`jevguard-proposer` subagent has wildcard-deny permissions and wildcard-disabled
|
|
167
|
+
tools, so it cannot call a tool, edit a file, or produce a patch. It returns a
|
|
168
|
+
strategy and one manual apply instruction only.
|
|
169
|
+
- `remediation.model` is optional. When set in `.jev/config.yaml`, it selects the
|
|
170
|
+
proposer model; when it is absent, the proposer inherits the host's model.
|
|
171
|
+
- A generic toast reports that the proposal is ready in the child session. It carries
|
|
172
|
+
no rule, task, diff, finding, or credential.
|
|
173
|
+
|
|
174
|
+
The plugin creates at most one proposal per evaluated turn, and the child session is
|
|
175
|
+
excluded from review for the plugin lifetime, so a proposal can never recurse or
|
|
176
|
+
trigger a second one. A host, model, or toast failure is contained and never changes
|
|
177
|
+
the review. A contained proposal failure — an invalid model specifier, a child-session
|
|
178
|
+
failure, or a prompt failure — shows a generic error toast and writes one structured
|
|
179
|
+
`error` log entry carrying only a typed reason code, never any task, diff, finding,
|
|
180
|
+
path, or credential.
|
|
181
|
+
|
|
182
|
+
The proposer is a strategy step, not an apply step: it never edits code, and nothing
|
|
183
|
+
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 completed
|
|
185
|
+
assistant turn, so the server plugin reviews it normally on the next idle — exactly
|
|
186
|
+
like any other turn.
|
|
187
|
+
|
|
188
|
+
The proposal prompt treats the task, paths, diff, rules, and findings as untrusted
|
|
189
|
+
data, and requires a separate explicit confirmation before proposing changes to tests,
|
|
190
|
+
configuration, or dependencies. No secret is ever carried, and no payload, task, diff,
|
|
191
|
+
or secret is logged.
|
|
192
|
+
|
|
143
193
|
## Install
|
|
144
194
|
|
|
145
|
-
|
|
146
|
-
`opencode.json
|
|
195
|
+
Installing `@pablozrrrr/jevguard` does **not** register it with OpenCode by itself.
|
|
196
|
+
OpenCode only loads plugins listed in `plugin: [...]` in `opencode.json`, so register
|
|
197
|
+
the plugin from the installed CLI:
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
# global config: ~/.config/opencode/opencode.json
|
|
201
|
+
jevguard install
|
|
202
|
+
|
|
203
|
+
# project config: ./opencode.json
|
|
204
|
+
jevguard install --project
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The installer asks for confirmation, edits only the `plugin` array, and is
|
|
208
|
+
idempotent. It only edits a `plugin` array of plain strings, so OpenCode's
|
|
209
|
+
`[["pkg", { "options": {} }]]` tuple form is a known limitation: the installer leaves
|
|
210
|
+
the file unchanged and prints the manual snippet instead. If `jevguard` is not on
|
|
211
|
+
`PATH`, run it through `bunx`:
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
bunx --package @pablozrrrr/jevguard jevguard install
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The manual alternative is one entry in `opencode.json`:
|
|
147
218
|
|
|
148
219
|
```json
|
|
149
220
|
{ "plugin": ["@pablozrrrr/jevguard"] }
|
|
150
221
|
```
|
|
151
222
|
|
|
152
|
-
OpenCode
|
|
153
|
-
|
|
154
|
-
`jevguard` CLI on `PATH` (see [Install the CLI](#install-the-cli)). Restart
|
|
155
|
-
|
|
223
|
+
OpenCode resolves the package from the npm registry or its cache and loads it on the
|
|
224
|
+
next start. Adding the entry only loads the plugin; it does **not** put the
|
|
225
|
+
`jevguard` CLI on `PATH` (see [Install the CLI](#install-the-cli)). Restart OpenCode
|
|
226
|
+
after changing the plugin list.
|
|
156
227
|
|
|
157
228
|
> [!IMPORTANT]
|
|
158
|
-
>
|
|
159
|
-
>
|
|
160
|
-
> publication, load the plugin as a local artifact plugin instead.
|
|
229
|
+
> A bare plugin entry resolves only from the npm registry. To run an unreleased
|
|
230
|
+
> local build, load the packed tarball as a local artifact plugin instead.
|
|
161
231
|
|
|
162
232
|
### Build the local artifact
|
|
163
233
|
|
|
164
234
|
The artifact ships the plugin as a **locally packed tarball** that bundles the
|
|
165
235
|
private `@jevguard/core` and `@jevguard/opencode-adapter` workspace code, so a
|
|
166
236
|
clean consumer installs one tarball and never resolves a workspace link or a
|
|
167
|
-
private registry package. It also packages the `jevguard-rules`
|
|
168
|
-
private
|
|
237
|
+
private registry package. It also packages the `jevguard-rules` and `jev-init`
|
|
238
|
+
skills and their private validators under `skills/<name>/`.
|
|
169
239
|
|
|
170
240
|
Build the tarball from this repository:
|
|
171
241
|
|
|
@@ -184,12 +254,19 @@ The packed manifest depends only on public runtime packages
|
|
|
184
254
|
the tarball therefore needs npm registry access for those packages; the artifact
|
|
185
255
|
does not vendor them and does not promise an offline install.
|
|
186
256
|
|
|
187
|
-
### Load the
|
|
257
|
+
### Load the plugin
|
|
188
258
|
|
|
189
|
-
OpenCode `1.18.
|
|
190
|
-
registry or its cache
|
|
191
|
-
|
|
192
|
-
|
|
259
|
+
OpenCode `1.18.32` resolves a **bare** `opencode.json` `plugin` entry from the npm
|
|
260
|
+
registry or its cache:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"plugin": ["@pablozrrrr/jevguard"]
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
To run an unreleased local build instead, install the packed tarball into the
|
|
269
|
+
project's `.opencode` directory and load it through a local plugin shim:
|
|
193
270
|
|
|
194
271
|
```sh
|
|
195
272
|
cd /path/to/consumer/.opencode
|
|
@@ -203,16 +280,28 @@ export { JevGuardPlugin } from "@pablozrrrr/jevguard";
|
|
|
203
280
|
|
|
204
281
|
OpenCode loads `.opencode/plugins/` with its bundled Bun runtime. The shim runs as
|
|
205
282
|
a local plugin module and resolves `@pablozrrrr/jevguard` from
|
|
206
|
-
`.opencode/node_modules`.
|
|
207
|
-
|
|
283
|
+
`.opencode/node_modules`. Use either the bare `plugin` entry or the local shim, not
|
|
284
|
+
both.
|
|
285
|
+
|
|
286
|
+
Remediation is built into the same server plugin: the `config` hook registers the
|
|
287
|
+
hidden `jevguard-proposer` subagent, so no second entrypoint or shim is needed. The
|
|
288
|
+
server plugin reviews and reports turns, and — when remediation is enabled and a review
|
|
289
|
+
fails — creates the proposal child session automatically.
|
|
208
290
|
|
|
209
291
|
### Author rules with the bundled skill
|
|
210
292
|
|
|
211
|
-
The package ships
|
|
212
|
-
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
updating the package so
|
|
293
|
+
The package ships two OpenCode skills beside the plugin: `jevguard-rules` to author
|
|
294
|
+
rules, and `jev-init` to bootstrap a policy where none exists. On startup the plugin's
|
|
295
|
+
`config` hook appends the installed skill directories to the host's `skills.paths`, so
|
|
296
|
+
the skills are discovered without a manual `opencode.json` entry. Skill discovery is
|
|
297
|
+
read at OpenCode startup: restart OpenCode after installing or updating the package so
|
|
298
|
+
the skills appear.
|
|
299
|
+
|
|
300
|
+
`jev-init` runs only when neither `.jev/rules.md` nor `.jev/config.yaml` exists. It
|
|
301
|
+
reads a bounded inventory of the repository, keeps rules only from the user's explicit
|
|
302
|
+
constraints and strongly evidenced local conventions, records provenance in a preamble,
|
|
303
|
+
and previews both candidate files before requiring explicit confirmation naming both
|
|
304
|
+
exact paths. Once a policy exists, every rule change belongs to `jevguard-rules`.
|
|
216
305
|
|
|
217
306
|
Policy content is different. `.jev/rules.md` and `.jev/config.yaml` are read once
|
|
218
307
|
per attributed turn, so editing rule text takes effect on the next reviewed turn
|
|
@@ -223,8 +312,8 @@ Jev.
|
|
|
223
312
|
|
|
224
313
|
### Install the CLI
|
|
225
314
|
|
|
226
|
-
Loading the plugin does not expose the `jevguard` binary on `PATH`.
|
|
227
|
-
|
|
315
|
+
Loading the plugin does not expose the `jevguard` binary on `PATH`. Run the login
|
|
316
|
+
command without a global install:
|
|
228
317
|
|
|
229
318
|
```sh
|
|
230
319
|
bunx --package @pablozrrrr/jevguard jevguard login
|
|
@@ -237,7 +326,7 @@ bun install --global @pablozrrrr/jevguard
|
|
|
237
326
|
jevguard login
|
|
238
327
|
```
|
|
239
328
|
|
|
240
|
-
|
|
329
|
+
To run an unreleased local build, install the packed tarball globally instead:
|
|
241
330
|
|
|
242
331
|
```sh
|
|
243
332
|
bun install --global /path/to/pablozrrrr-jevguard-<version>.tgz
|
|
@@ -246,9 +335,7 @@ jevguard login
|
|
|
246
335
|
|
|
247
336
|
Known limitations:
|
|
248
337
|
|
|
249
|
-
-
|
|
250
|
-
plugin entry does not resolve until the package is published.
|
|
251
|
-
- Target host is OpenCode `1.18.31`. Exact `1.18.31` runtime smoke is still
|
|
338
|
+
- Target host is OpenCode `1.18.32`. Exact `1.18.32` runtime smoke is still
|
|
252
339
|
pending; a local `1.18.28` run worked.
|
|
253
340
|
- The plugin and CLI run on Bun, and the packed `jevguard` bin keeps a Bun
|
|
254
341
|
shebang. Install and run the artifact with the Bun runtime.
|
|
@@ -288,7 +375,7 @@ safely or completely evaluate that rule.
|
|
|
288
375
|
|
|
289
376
|
## Implemented behavior
|
|
290
377
|
|
|
291
|
-
The current release implements the full local
|
|
378
|
+
The current release implements the full local review path:
|
|
292
379
|
|
|
293
380
|
- OpenCode V1 plugin loads.
|
|
294
381
|
- A completed assistant response is detected.
|
|
@@ -330,9 +417,18 @@ The current release implements the full local, observe-only path:
|
|
|
330
417
|
carries every entry's outcome, raw probability, or reason, including the built-in
|
|
331
418
|
results and the synthetic review-level entry when there is one. The fixed result
|
|
332
419
|
order is rules in source order, then `SCOPE-CREEP`, then `COMPLEXITY`.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
420
|
+
- After presentation, when remediation is enabled and the review contains any `FAIL`,
|
|
421
|
+
at most one aggregate proposal request is sent to the hidden `jevguard-proposer`
|
|
422
|
+
subagent in an isolated child session parented to the source session, and a generic
|
|
423
|
+
toast reports that the proposal is ready. The request carries every `FAIL` finding
|
|
424
|
+
(local `error` rules and the `SCOPE-CREEP` built-in), the task, and the complete,
|
|
425
|
+
safe, full attributed patch; a blocked or oversized patch records no proposal. A
|
|
426
|
+
notifier or host failure never affects the review, presentation, or later turns.
|
|
427
|
+
|
|
428
|
+
The review itself injects no feedback into the agent session, prompts no session, and
|
|
429
|
+
blocks no task. The proposal above is a strategy only; nothing is applied, and
|
|
430
|
+
the user copies its manual apply instruction into their normal coding agent, whose
|
|
431
|
+
ordinary turn is reviewed normally.
|
|
336
432
|
|
|
337
433
|
## Roadmap
|
|
338
434
|
|
|
@@ -357,6 +453,17 @@ V0.9 Local feedback and calibration
|
|
|
357
453
|
V1 Claude Code and Codex adapters
|
|
358
454
|
```
|
|
359
455
|
|
|
456
|
+
The auto-propose slice is implemented: after a review with a `FAIL`, an enabled
|
|
457
|
+
configuration asks the hidden `jevguard-proposer` subagent for a strategy in an
|
|
458
|
+
isolated child session, and never applies a change. The `V0.6` entry remains the
|
|
459
|
+
future, unattended corrective loop; the proposer is a bounded, strategy-only step, not
|
|
460
|
+
that loop.
|
|
461
|
+
|
|
462
|
+
The `V0.4` `jev-init` skill is implemented: when no policy exists it bootstraps
|
|
463
|
+
`.jev/rules.md` and `.jev/config.yaml` from the user's explicit constraints and
|
|
464
|
+
strongly evidenced local conventions, records each rule's provenance, and writes both
|
|
465
|
+
files only after validating the confirmed candidates.
|
|
466
|
+
|
|
360
467
|
CI and pull-request policy review come after V1, using the same versioned rules.
|
|
361
468
|
|
|
362
469
|
## Architecture
|
|
@@ -376,9 +483,10 @@ plugin → opencode-adapter → core
|
|
|
376
483
|
|
|
377
484
|
## Status
|
|
378
485
|
|
|
379
|
-
The multi-rule, multi-built-in, observe-only review is implemented. It
|
|
380
|
-
OpenCode, attributes one completed turn, parses every rule block in
|
|
381
|
-
asks Jev once per applicable rule and once per built-in batch, runs
|
|
486
|
+
The multi-rule, multi-built-in, background and observe-only review is implemented. It
|
|
487
|
+
loads in OpenCode, attributes one completed turn, parses every rule block in
|
|
488
|
+
`.jev/rules.md`, asks Jev once per applicable rule and once per built-in batch, runs
|
|
489
|
+
the `SCOPE-CREEP`
|
|
382
490
|
and `COMPLEXITY` built-ins over the complete attributed patch behind one shared
|
|
383
491
|
concurrency limit, applies the local gate to each, and presents one aggregate result
|
|
384
492
|
as a transient TUI toast and a structured log entry.
|
|
@@ -386,9 +494,13 @@ as a transient TUI toast and a structured log entry.
|
|
|
386
494
|
The local, private tarball (`pnpm artifact:build`, `pnpm artifact:pack`) packages
|
|
387
495
|
that slice so a clean consumer can install it without workspace links.
|
|
388
496
|
|
|
389
|
-
OpenCode sees only transient toasts
|
|
390
|
-
|
|
391
|
-
|
|
497
|
+
OpenCode sees only transient toasts, structured logs, and — when remediation is
|
|
498
|
+
enabled and a review has a `FAIL` — one proposal child session and a generic toast.
|
|
499
|
+
The review does not inject anything into the agent context, prompt the source session,
|
|
500
|
+
add session messages, or block a task. The proposal is a separate, configurable,
|
|
501
|
+
strategy-only workflow delivered by the server plugin; it never edits code and never
|
|
502
|
+
applies a change. Real-host validation against exact OpenCode `1.18.32` is still
|
|
503
|
+
pending.
|
|
392
504
|
|
|
393
505
|
## Development
|
|
394
506
|
|