@pablozrrrr/jevguard 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JevGuard contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,417 @@
1
+ # JevGuard
2
+
3
+ > **Semantic policy engine for coding agents.**
4
+
5
+ ![JevGuard: semantic policy engine for coding agents](https://raw.githubusercontent.com/pablozr/JevGuard/main/27b864b2-3f64-4211-99dd-26764c549d03.png)
6
+
7
+ JevGuard checks the code an agent just changed against the policies that matter in
8
+ your repository. It uses Jev for a narrow semantic judgment per rule, then applies
9
+ your local deterministic gate to return `PASS`, `WARN`, or `FAIL`.
10
+
11
+ ```text
12
+ agent completes a turn
13
+ ↓
14
+ task + attributed diff + local rules
15
+ ↓
16
+ Jev per rule
17
+ ↓
18
+ local policy gate per rule
19
+ ↓
20
+ PASS · WARN · FAIL
21
+ ```
22
+
23
+ Not a code generator. Not a prose review bot. Not another static linter.
24
+
25
+ JevGuard is a semantic linter: it asks whether a specific change violated a
26
+ specific policy, and leaves planning, code generation, and correction with the
27
+ coding agent.
28
+
29
+ > [!WARNING]
30
+ > JevGuard is in active development. The current release targets OpenCode `1.18.31`,
31
+ > evaluates every rule block declared in `.jev/rules.md` plus the `SCOPE-CREEP` and
32
+ > `COMPLEXITY` built-ins, and runs in observe mode only: it reports results but never
33
+ > alters the agent context or blocks a task.
34
+
35
+ ## Why JevGuard
36
+
37
+ Traditional linters are excellent at syntax, types, and patterns. They cannot
38
+ reliably answer repository-specific questions such as:
39
+
40
+ - Did a controller start making domain decisions?
41
+ - Did this business-rule change arrive without meaningful tests?
42
+ - Did the agent solve a small task by introducing an unnecessary abstraction?
43
+ - Did a change cross an architectural boundary that this codebase protects?
44
+
45
+ JevGuard makes those questions versioned, scoped, and machine-actionable.
46
+
47
+ ```md
48
+ ## ARCH-001
49
+
50
+ severity: error
51
+ scope: backend/**
52
+
53
+ ### Rule
54
+
55
+ HTTP controllers must not contain business logic.
56
+
57
+ ### Violation
58
+
59
+ A controller performs domain decisions, calculations, or state mutations directly.
60
+
61
+ ### Allowed
62
+
63
+ Validation, HTTP mapping and delegation to services.
64
+ ```
65
+
66
+ The rule stays in the repository beside the code it governs. `Allowed` is a real
67
+ exception, not a suggestion. JevGuard evaluates it as part of the same rule.
68
+
69
+ ## The contract
70
+
71
+ JevGuard is built around one non-negotiable idea: review the **change that belongs
72
+ to the turn**, not whatever happens to be in the worktree.
73
+
74
+ ```text
75
+ user task
76
+ ↓
77
+ assistant response
78
+ ↓
79
+ attributed patch
80
+ ↓
81
+ applicable rules
82
+ ↓
83
+ one semantic judgment per rule
84
+ ```
85
+
86
+ If complete attributed evidence is unavailable, too large, invalid, or contains a
87
+ blocked sensitive file, JevGuard marks the affected rule `UNAVAILABLE`. It never
88
+ turns incomplete evidence into a reassuring verdict. Evidence problems are scoped
89
+ to the rules they affect: a rule whose applicable files are all safe still runs
90
+ even when another rule's evidence is blocked.
91
+
92
+ Some failures happen before any policy rule is evaluated. If the attributed diff
93
+ cannot be built, the review is a single synthetic `UNAVAILABLE` entry with no rule
94
+ ID, and neither lane runs. If the turn is attributed but the policy files cannot be
95
+ read or validated, the rule lane reports one synthetic `UNAVAILABLE` entry with no
96
+ rule ID — it does not invent one result per declared rule — while the built-in batch
97
+ still runs against the turn's attributed patch.
98
+
99
+ ## How a review works
100
+
101
+ For every applicable rule, JevGuard sends Jev one focused yes/no question:
102
+
103
+ > Does this attributed change violate `ARCH-001`?
104
+
105
+ Jev returns a Noul: the probability that the answer is yes. JevGuard owns the
106
+ policy that translates it into an outcome.
107
+
108
+ | Rule severity | PASS | WARN | FAIL |
109
+ | --- | ---: | ---: | ---: |
110
+ | `error` | `< 40%` | `40%–69%` | `≥ 70%` |
111
+ | `warning` | `< 60%` | `≥ 60%` | never |
112
+
113
+ Those thresholds are per repository rule and locally configurable in
114
+ `.jev/config.yaml`.
115
+
116
+ JevGuard also runs two product-owned built-ins on every attributed turn,
117
+ `SCOPE-CREEP` and `COMPLEXITY`. Both receive the turn's complete, safe attributed
118
+ patch with no scope filtering, and both are answered by a **single** Jev request
119
+ that carries two independent questions. Each answer is validated on its own, so a
120
+ malformed answer fails only its own check.
121
+
122
+ `SCOPE-CREEP` asks whether the change contains material functional, behavioral,
123
+ architectural, dependency, configuration, documentation, or refactoring work the task
124
+ did not request and that is not reasonably necessary to complete it. It uses fixed
125
+ `error` thresholds (`40%`/`70%`), independent of `.jev/config.yaml`.
126
+
127
+ `COMPLEXITY` asks whether the change introduces material complexity disproportionate
128
+ to, or not reasonably necessary for, completing the task, such as unnecessary
129
+ abstractions, layers or indirections without proportional gain, new dependencies
130
+ without a clear need, excessive configuration, premature generalization, or structure
131
+ materially larger than the problem requires. It is advisory: it warns at `50%` and
132
+ can never fail.
133
+
134
+ The rule lane and the built-in batch are launched together. One shared FIFO
135
+ concurrency limit caps the plugin instance at two Jev requests in flight, counting the
136
+ built-in batch as a single request. Results join the same aggregate in a fixed order:
137
+ rules in source order, then `SCOPE-CREEP`, then `COMPLEXITY`.
138
+
139
+ Reviews run in the background. The idle event returns as soon as the serialized
140
+ attribution step is scheduled; policy reads, Jev calls, and presentation never sit on
141
+ the agent's critical path.
142
+
143
+ ## Install
144
+
145
+ Once `@pablozrrrr/jevguard` is published to npm, the intended install is one line in
146
+ `opencode.json`:
147
+
148
+ ```json
149
+ { "plugin": ["@pablozrrrr/jevguard"] }
150
+ ```
151
+
152
+ OpenCode then resolves the package from the npm registry or its cache and loads it
153
+ on the next start. Adding the entry only loads the plugin; it does **not** put the
154
+ `jevguard` CLI on `PATH` (see [Install the CLI](#install-the-cli)). Restart
155
+ OpenCode after changing the plugin list.
156
+
157
+ > [!IMPORTANT]
158
+ > `@pablozrrrr/jevguard` is **not published to npm yet**. The one-line entry above is
159
+ > future behavior and will not resolve until the package is released. Before
160
+ > publication, load the plugin as a local artifact plugin instead.
161
+
162
+ ### Build the local artifact
163
+
164
+ The artifact ships the plugin as a **locally packed tarball** that bundles the
165
+ private `@jevguard/core` and `@jevguard/opencode-adapter` workspace code, so a
166
+ clean consumer installs one tarball and never resolves a workspace link or a
167
+ private registry package. It also packages the `jevguard-rules` skill and its
168
+ private rule validator under `skills/jevguard-rules/`.
169
+
170
+ Build the tarball from this repository:
171
+
172
+ ```sh
173
+ corepack enable
174
+ pnpm install
175
+ pnpm artifact:build
176
+ pnpm artifact:pack
177
+ ```
178
+
179
+ `pnpm artifact:pack` writes `artifacts/pablozrrrr-jevguard-<version>.tgz`. Both
180
+ `packages/plugin/dist/` and `artifacts/` are build output and are not committed.
181
+
182
+ The packed manifest depends only on public runtime packages
183
+ (`@inquirer/password`, `@napi-rs/keyring`, `@typesafe-ai/sdk`, `yaml`). Installing
184
+ the tarball therefore needs npm registry access for those packages; the artifact
185
+ does not vendor them and does not promise an offline install.
186
+
187
+ ### Load the local plugin before publication
188
+
189
+ OpenCode `1.18.31` resolves a **bare** `opencode.json` `plugin` entry from the npm
190
+ registry or its cache, not from the consumer's `node_modules`. Until the package is
191
+ published, install the tarball into the project's `.opencode` directory and load it
192
+ through a local plugin shim:
193
+
194
+ ```sh
195
+ cd /path/to/consumer/.opencode
196
+ bun add /path/to/pablozrrrr-jevguard-<version>.tgz
197
+ ```
198
+
199
+ ```ts
200
+ // .opencode/plugins/jevguard.ts
201
+ export { JevGuardPlugin } from "@pablozrrrr/jevguard";
202
+ ```
203
+
204
+ OpenCode loads `.opencode/plugins/` with its bundled Bun runtime. The shim runs as
205
+ a local plugin module and resolves `@pablozrrrr/jevguard` from
206
+ `.opencode/node_modules`. Do **not** add `@pablozrrrr/jevguard` to the `opencode.json`
207
+ `plugin` list before publication; that bare entry does not resolve locally.
208
+
209
+ ### Author rules with the bundled skill
210
+
211
+ The package ships a `jevguard-rules` OpenCode skill beside the plugin. On startup
212
+ the plugin's `config` hook appends the installed skill directory to the host's
213
+ `skills.paths`, so the skill is discovered without a manual `opencode.json` entry.
214
+ Skill discovery is read at OpenCode startup: restart OpenCode after installing or
215
+ updating the package so the skill appears.
216
+
217
+ Policy content is different. `.jev/rules.md` and `.jev/config.yaml` are read once
218
+ per attributed turn, so editing rule text takes effect on the next reviewed turn
219
+ without restarting OpenCode. The skill validates a candidate with the same parser
220
+ JevGuard uses, requires explicit final confirmation, and replaces `.jev/rules.md`
221
+ only after the candidate passes; it never edits `.jev/config.yaml` and never calls
222
+ Jev.
223
+
224
+ ### Install the CLI
225
+
226
+ Loading the plugin does not expose the `jevguard` binary on `PATH`. Once the
227
+ package is published, run the login command without a global install:
228
+
229
+ ```sh
230
+ bunx --package @pablozrrrr/jevguard jevguard login
231
+ ```
232
+
233
+ Or install the package globally so `jevguard` is on `PATH`:
234
+
235
+ ```sh
236
+ bun install --global @pablozrrrr/jevguard
237
+ jevguard login
238
+ ```
239
+
240
+ Until the package is published, install the local tarball globally instead:
241
+
242
+ ```sh
243
+ bun install --global /path/to/pablozrrrr-jevguard-<version>.tgz
244
+ jevguard login
245
+ ```
246
+
247
+ Known limitations:
248
+
249
+ - `@pablozrrrr/jevguard` has no registry release yet, so the one-line `opencode.json`
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
252
+ pending; a local `1.18.28` run worked.
253
+ - The plugin and CLI run on Bun, and the packed `jevguard` bin keeps a Bun
254
+ shebang. Install and run the artifact with the Bun runtime.
255
+
256
+ ## Connect Jev
257
+
258
+ For local use, connect once with the installed command:
259
+
260
+ ```sh
261
+ jevguard login
262
+ ```
263
+
264
+ Loading the plugin does not put `jevguard` on `PATH`. If it is not installed
265
+ globally, run the one-line login instead:
266
+
267
+ ```sh
268
+ bunx --package @pablozrrrr/jevguard jevguard login
269
+ ```
270
+
271
+ JevGuard stores the key in your operating system's credential store instead of a
272
+ repository file or shell history. In CI, provide `TYPESAFE_API_KEY` through the
273
+ platform secret manager. See the [security model](./docs/security.md) for details.
274
+
275
+ ```text
276
+ JevGuard FAIL
277
+ pass 1 · warn 1 · fail 1 · skipped 0 · unavailable 0
278
+ ```
279
+
280
+ One toast per turn shows the aggregate outcome and the outcome counts; the
281
+ structured log carries every rule's result, its raw violation probability or typed
282
+ reason, and its scoped paths. The counts include every entry, including the single
283
+ synthetic entry a review-level failure produces.
284
+
285
+ `PASS`, `WARN`, and `FAIL` are per-rule semantic outcomes. `SKIPPED` means that
286
+ rule had no applicable change to evaluate. `UNAVAILABLE` means JevGuard could not
287
+ safely or completely evaluate that rule.
288
+
289
+ ## Implemented behavior
290
+
291
+ The current release implements the full local, observe-only path:
292
+
293
+ - OpenCode V1 plugin loads.
294
+ - A completed assistant response is detected.
295
+ - The direct parent user task and assistant-attributed diff are acquired.
296
+ - Every `## <RULE-ID>` block in `.jev/rules.md` is parsed in source order. Each
297
+ block is validated independently, so one invalid block does not suppress its
298
+ valid siblings, and every occurrence of a duplicated ID is invalid.
299
+ - Each valid rule is processed independently. It asks Jev at most once, and only
300
+ when the gate config is valid, the rule is applicable, and its scoped evidence is
301
+ complete and safe; it produces one typed violation probability or an operational
302
+ outcome.
303
+ - A failure before rule evaluation — no attributed diff, unreadable policy files, or
304
+ a missing `.jev/rules.md` — is reported by the rule lane as one synthetic
305
+ `UNAVAILABLE` entry with no rule ID, not one result per declared rule. When the
306
+ turn itself is attributed, the built-in batch still runs.
307
+ - The product-owned `SCOPE-CREEP` built-in runs on every attributed turn over the
308
+ turn's complete, safe attributed patch, with fixed `error` thresholds
309
+ (`0.65`/`0.90`) and no scope filtering. It can produce `PASS`, `WARN`, or `FAIL`.
310
+ - The product-owned `COMPLEXITY` built-in runs on every attributed turn over the same
311
+ complete, safe attributed patch. It uses a fixed advisory threshold of `0.50`,
312
+ independent of `.jev/config.yaml`, and can produce `PASS` or `WARN` but never
313
+ `FAIL`.
314
+ - Both built-ins are sent as one batch request with two independent named answers.
315
+ One malformed or missing answer fails only its own check; the valid sibling still
316
+ gates. A failed or malformed batch envelope makes both checks `UNAVAILABLE`.
317
+ - The built-in batch runs concurrently with the sequential rule lane. One shared FIFO
318
+ concurrency limit allows at most two Jev requests in flight across the plugin
319
+ instance, counting the batch as one request. With no attributed patch a built-in is
320
+ `SKIPPED`; blocked or oversized evidence is `UNAVAILABLE`; a rule, policy-load, or
321
+ config failure never suppresses the batch, and one built-in's answer never
322
+ suppresses the other.
323
+ - Reviews run in the background. The idle event resolves as soon as the serialized
324
+ attribution step is scheduled, so policy reads, Jev calls, and presentation do not
325
+ block the agent.
326
+ - The local gate maps each rule to `PASS`, `WARN`, or `FAIL`; a rule with no
327
+ applicable scope is `SKIPPED`, and a rule that cannot be safely evaluated is
328
+ `UNAVAILABLE`.
329
+ - One aggregate TUI toast and one structured log entry report the turn. The log
330
+ carries every entry's outcome, raw probability, or reason, including the built-in
331
+ results and the synthetic review-level entry when there is one. The fixed result
332
+ order is rules in source order, then `SCOPE-CREEP`, then `COMPLEXITY`.
333
+
334
+ No feedback is injected into the agent session. No remediation is attempted. No
335
+ task is blocked.
336
+
337
+ ## Roadmap
338
+
339
+ The multi-rule, scope-creep, and complexity slices are implemented. Planned work
340
+ beyond them:
341
+
342
+ ```text
343
+ V0.3 Remaining built-in semantic check: test adequacy
344
+ ↓
345
+ V0.4 jev-init: evidence-based policy bootstrap
346
+ ↓
347
+ V0.5 On-demand evidence and explanations
348
+ ↓
349
+ V0.6 Opt-in, bounded auto-remediation
350
+ ↓
351
+ V0.7 Observe / enforce / remediate modes
352
+ ↓
353
+ V0.8 Rule packs
354
+ ↓
355
+ V0.9 Local feedback and calibration
356
+ ↓
357
+ V1 Claude Code and Codex adapters
358
+ ```
359
+
360
+ CI and pull-request policy review come after V1, using the same versioned rules.
361
+
362
+ ## Architecture
363
+
364
+ JevGuard is a pnpm TypeScript monorepo. The core has no dependency on OpenCode or
365
+ its runtime, which keeps the evaluation model portable to future adapters.
366
+
367
+ ```text
368
+ packages/
369
+ ├── core/ domain, policy, evaluation, gate
370
+ ├── opencode-adapter/ OpenCode attribution and presentation
371
+ ├── plugin/ OpenCode composition root and CLI
372
+ └── testkit/ fixtures and contract helpers
373
+
374
+ plugin → opencode-adapter → core
375
+ ```
376
+
377
+ ## Status
378
+
379
+ The multi-rule, multi-built-in, observe-only review is implemented. It loads in
380
+ OpenCode, attributes one completed turn, parses every rule block in `.jev/rules.md`,
381
+ asks Jev once per applicable rule and once per built-in batch, runs the `SCOPE-CREEP`
382
+ and `COMPLEXITY` built-ins over the complete attributed patch behind one shared
383
+ concurrency limit, applies the local gate to each, and presents one aggregate result
384
+ as a transient TUI toast and a structured log entry.
385
+
386
+ The local, private tarball (`pnpm artifact:build`, `pnpm artifact:pack`) packages
387
+ that slice so a clean consumer can install it without workspace links.
388
+
389
+ OpenCode sees only transient toasts and structured logs. JevGuard does not inject
390
+ anything into the agent context, does not add session messages, and does not block
391
+ a task. Real-host validation against exact OpenCode `1.18.31` is still pending.
392
+
393
+ ## Development
394
+
395
+ Requirements: Node.js `>=22.13.0`, pnpm `10.33.2` (via Corepack), and the Bun
396
+ runtime for the CLI and OpenCode plugin loading.
397
+
398
+ ```sh
399
+ corepack enable
400
+ pnpm install
401
+ pnpm typecheck
402
+ pnpm test
403
+ ```
404
+
405
+ Use `pnpm check` to run format, lint, typecheck, and tests in sequence. See
406
+ [CONTRIBUTING.md](./CONTRIBUTING.md) for the full contribution workflow.
407
+
408
+ ## Documentation
409
+
410
+ - [Configuration reference](./docs/configuration.md)
411
+ - [Security model](./docs/security.md)
412
+ - [Contributing](./CONTRIBUTING.md)
413
+ - [Agent instructions](./AGENTS.md)
414
+
415
+ ## License
416
+
417
+ MIT — see [LICENSE](./LICENSE).