pi-jev-guard 0.5.0 → 0.6.1

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 CHANGED
@@ -6,7 +6,7 @@ Verifica le risposte con [TypeSafe Jev](https://docs.typesafe.ai) dentro [pi](ht
6
6
  - **Domande tipate**: tool `jev_ask` per giudizi calibrati definiti dal modello (noul/choice/score) invece di prosa.
7
7
  - **Automatica**: gemello guarded `<modello>__jev` dentro lo stesso provider, che trattiene la risposta, la verifica con Jev e pubblica solo l'output approvato (con rigenerazione privata su `block`). Stessa auth del provider: nessun login extra, funziona anche con Codex/OAuth.
8
8
  - **Giudice output** (solo `automatic`, fail-open): dopo ogni `bash` cerca secret nel testo e classifica i fallimenti, allegando un consiglio al risultato.
9
- - **Verifica progetto** (opt-in): footer deterministico da `edit`/`write` osservati e check esplicitamente autorizzati nel manifest locale; il modello può eseguire soltanto quegli id, mai shell arbitraria.
9
+ - **Verifica progetto** (opt-in): footer deterministico da `edit`/`write` osservati e check autorizzati da override locale, manifest globale utente o profilo comune integrato; il modello può eseguire soltanto quegli id, mai shell arbitraria.
10
10
 
11
11
  ## Installazione
12
12
 
@@ -183,54 +183,101 @@ grounding (`warned`/`repaired`/`held`).
183
183
 
184
184
  ### Verifica progetto deterministica (opt-in)
185
185
 
186
- Il grounding è probabilistico; per **compilazione, lint e test** è meglio una
187
- prova deterministica. La verifica progetto mantiene, solo nella sessione,
188
- una lista di `edit`/`write` riusciti e degli esiti osservati dei check. Il
189
- footer della risposta viene generato dall'estensione, non dal modello:
190
- quindi non può trasformare un test non eseguito in “test passati”.
186
+ Il grounding è probabilistico; per **compilazione, lint e test** serve una
187
+ prova deterministica. La verifica progetto conserva, solo nella sessione,
188
+ `edit`/`write` riusciti e gli esiti osservati dei check. Il footer viene
189
+ generato dall'estensione, non dal modello: un test non eseguito non può
190
+ diventare “test passati”.
191
191
 
192
- 1. Copia e adatta il manifest versionato/localmente controllato:
192
+ #### Nuovi progetti: zero manifest locale
193
+
194
+ Una volta, in una sessione col twin `automatic`, abilita la modalità scelta:
195
+
196
+ ```text
197
+ /jev verification repair
198
+ ```
199
+
200
+ Da quel momento ogni progetto usa questa precedenza:
201
+
202
+ 1. `.pi/jev-verification.json` locale, se presente;
203
+ 2. `verification.globalManifestPath`, se l'hai configurato nella config
204
+ utente;
205
+ 3. profilo integrato `common` (default).
206
+
207
+ Il profilo `common` non legge comandi dal repository: abilita esclusivamente
208
+ comandi fissi dell'estensione quando trova segnali convenzionali:
209
+
210
+ | Rilevamento | Check disponibili |
211
+ |---|---|
212
+ | `package.json` con script | `npm run typecheck`, `npm run lint`, uno tra `npm run test:unit`, `npm run test:ci`, `npm test` |
213
+ | `Cargo.toml` | `cargo check`, `cargo test` |
214
+ | `go.mod` | `go test ./...` |
215
+ | configurazione pytest | `python -m pytest` |
216
+ | repository Git | `git diff --check` |
217
+
218
+ Il profilo sceglie un solo ecosistema (Node → Rust → Go → Python) e aggiunge
219
+ il check Git quando applicabile. I comandi sono bounded da
220
+ `verification.maxCommands` (default `4`) e timeout. Se nessun segnale è
221
+ rilevato, non esegue nulla e il footer dichiara la limitazione.
222
+
223
+ Dopo un edit il modello può scegliere solo id già acquisiti:
224
+
225
+ ```text
226
+ /jev verify recommended # check pertinenti non ancora freschi
227
+ /jev verify all # tutti, fino a verification.maxCommands
228
+ /jev verify node-typecheck # un id esplicito della policy corrente
229
+ ```
230
+
231
+ Il tool LLM `jev_verify` ha lo stesso vincolo, richiede un progetto trusted e
232
+ non accetta shell arbitraria. Footer/repair/hold operano nel twin
233
+ `automatic`; `/jev verify` è utilizzabile manualmente anche senza footer.
234
+
235
+ #### Override per progetti speciali
236
+
237
+ Per una pipeline specifica, aggiungi un manifest al progetto: prevale sempre
238
+ sul profilo globale.
193
239
 
194
240
  ```bash
195
241
  mkdir -p .pi
196
242
  cp config/jev-verification.example.json .pi/jev-verification.json
197
243
  ```
198
244
 
199
- 2. Controllalo tu e acquisiscilo nella sessione (o riacquisiscilo dopo una
200
- modifica manuale):
245
+ Controllalo tu, poi acquisiscilo (e ripeti dopo ogni modifica manuale):
201
246
 
202
247
  ```text
203
248
  /jev verification reload
204
- /jev verification warn # footer trasparente, nessuna rigenerazione
205
- # oppure: /jev verification repair|hold
206
249
  ```
207
250
 
208
- 3. Dopo un edit, il modello può eseguire soltanto gli id dichiarati nel
209
- manifest — mai una shell string fornita dal modello:
251
+ In alternativa puoi definire **una** policy comune per tutti i tuoi progetti
252
+ nella config utente, purché esista davvero e sia sotto il tuo controllo:
210
253
 
211
- ```text
212
- /jev verify recommended # i check pertinenti non ancora freschi
213
- /jev verify all # tutti, fino a verification.maxCommands
214
- /jev verify typecheck # un id esplicito del manifest
254
+ ```json
255
+ {
256
+ "verification": {
257
+ "globalManifestPath": "~/.pi/agent/jev-verification.global.json",
258
+ "defaultProfile": "common"
259
+ }
260
+ }
215
261
  ```
216
262
 
217
- Il tool LLM `jev_verify` ha lo stesso vincolo. Richiede un progetto trusted e
218
- non accetta comandi arbitrari. Footer/repair/hold operano nel twin in
219
- `automatic`; `/jev verify` può essere usato manualmente anche per controllare
220
- un progetto senza pubblicare un footer. Il manifest è acquisito a inizio sessione o
221
- con `/jev verification reload`; se cambia dopo, la verifica rifiuta di
222
- eseguire fino a una nuova acquisizione utente. `write`/`edit` del modello sul
223
- manifest sono bloccati; anche modifiche via shell vengono rilevate dal
224
- fingerprint e non aggiornano i comandi autorizzati. Il manifest è quindi una
225
- policy dell'utente/progetto, non una superficie di prompt injection.
263
+ Un `globalManifestPath` configurato ma assente o malformato non degrada
264
+ silenziosamente al profilo integrato: la verifica segnala l'errore. Per
265
+ disattivare il fallback integrato imposta `"defaultProfile": "off"`.
266
+ `/jev verification` e `/jev status` mostrano l'origine effettiva della policy.
267
+
268
+ Manifest e profilo sono acquisiti a inizio sessione, all'attivazione esplicita
269
+ o con `/jev verification reload`. Se un manifest, un `package.json` o un file
270
+ di configurazione che abilita il profilo cambia dopo l'acquisizione, nessun
271
+ check parte fino al reload utente. In automatic, con tool policy e verifica
272
+ attive, `write`/`edit` del modello su tali file sono bloccati; una modifica via shell
273
+ viene comunque rilevata dal fingerprint. Questo evita che il modello trasformi
274
+ uno script o un manifest in un nuovo comando autorizzato.
226
275
 
227
276
  Modalità: `warn` aggiunge il footer; `repair` rigenera una volta con feedback
228
- che chiede di invocare `jev_verify`; `hold` trattiene le risposte quando un
229
- check è fallito, e dopo il tentativo bounded anche quando resta mancante.
230
- Nessun manifest o nessun check pertinente è una limitazione dichiarata, non
231
- un fallimento del codice e non produce hold. La raccolta delle modifiche è
232
- limitata a `edit`/`write` osservati: una modifica fatta con shell non viene
233
- attribuita automaticamente al modello.
277
+ che chiede `jev_verify`; `hold` trattiene dopo una failure concreta, o dopo il
278
+ tentativo bounded se restano check mancanti. Nessun check pertinente non è un
279
+ fallimento del codice e non produce hold. Le modifiche rilevate restano solo
280
+ quelle di `edit`/`write`: una modifica shell non viene attribuita al modello.
234
281
 
235
282
  ## Configurazione
236
283
 
@@ -69,10 +69,11 @@
69
69
  "enabled": false,
70
70
  "mode": "warn",
71
71
  "manifestPath": ".pi/jev-verification.json",
72
+ "defaultProfile": "common",
72
73
  "maxRepairs": 1,
73
74
  "commandTimeoutMs": 120000,
74
75
  "outputChars": 6000,
75
- "maxCommands": 3
76
+ "maxCommands": 4
76
77
  },
77
78
  "exfilCheck": {
78
79
  "enabled": true,
@@ -68,16 +68,16 @@ import type { PostPublishCheckFn, PostPublishCheckResult } from "../src/automati
68
68
  import {
69
69
  buildVerificationStatus,
70
70
  createVerificationState,
71
- isVerificationManifestPath,
71
+ isVerificationPolicyPath,
72
72
  decideVerificationAction,
73
- loadVerificationManifest,
73
+ loadVerificationPolicy,
74
74
  manifestCheckForCommand,
75
75
  normalizeObservedPath,
76
76
  recordObservedModification,
77
77
  recordVerificationRun,
78
78
  selectVerificationChecks,
79
- trustVerificationManifest,
80
- trustedVerificationManifest,
79
+ trustVerificationPolicy,
80
+ trustedVerificationPolicy,
81
81
  type VerificationManifestTrust,
82
82
  type VerificationStatus,
83
83
  } from "../src/verification.ts";
@@ -225,18 +225,18 @@ export default function (pi: ExtensionAPI) {
225
225
 
226
226
  function captureVerificationManifest(cwd: string): VerificationManifestTrust {
227
227
  verificationCwd = cwd;
228
- verificationManifestTrust = trustVerificationManifest(cwd, config.verification.manifestPath);
228
+ verificationManifestTrust = trustVerificationPolicy(cwd, config.verification);
229
229
  return verificationManifestTrust;
230
230
  }
231
231
 
232
232
  function verificationSnapshot(cwd: string): {
233
- loaded: ReturnType<typeof loadVerificationManifest>;
233
+ loaded: ReturnType<typeof loadVerificationPolicy>;
234
234
  status: VerificationStatus;
235
235
  } {
236
- const loaded = trustedVerificationManifest(
236
+ const loaded = trustedVerificationPolicy(
237
237
  verificationManifestTrust,
238
238
  cwd,
239
- config.verification.manifestPath,
239
+ config.verification,
240
240
  );
241
241
  return { loaded, status: buildVerificationStatus(verificationState, loaded) };
242
242
  }
@@ -269,8 +269,8 @@ export default function (pi: ExtensionAPI) {
269
269
  }
270
270
 
271
271
  /**
272
- * Esegue SOLO check estratti dal manifest locale validato. Questa funzione
273
- * serve sia al tool del modello sia al comando utente /jev verify.
272
+ * Esegue SOLO check estratti dalla policy acquisita (locale, globale o
273
+ * profilo integrato). Serve sia al tool del modello sia a /jev verify.
274
274
  */
275
275
  async function runProjectVerification(args: {
276
276
  cwd: string;
@@ -405,7 +405,7 @@ export default function (pi: ExtensionAPI) {
405
405
  refs: TwinRef[],
406
406
  registry: ModelRegistry,
407
407
  persist: boolean,
408
- ): { ok: boolean; error?: string; twins?: string[] } {
408
+ ): { ok: boolean; error?: string; stack?: string; twins?: string[] } {
409
409
  const byProvider = new Map<string, string[]>();
410
410
  for (const ref of refs) {
411
411
  const list = byProvider.get(ref.provider) ?? [];
@@ -443,6 +443,7 @@ export default function (pi: ExtensionAPI) {
443
443
  return {
444
444
  ok: false,
445
445
  error: error instanceof Error ? error.message : String(error),
446
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
446
447
  };
447
448
  }
448
449
  overlays.set(providerId, { twinIds: [...twinIds], live });
@@ -466,6 +467,7 @@ export default function (pi: ExtensionAPI) {
466
467
  return {
467
468
  ok: false,
468
469
  error: error instanceof Error ? error.message : String(error),
470
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
469
471
  };
470
472
  }
471
473
  }
@@ -483,7 +485,7 @@ export default function (pi: ExtensionAPI) {
483
485
  );
484
486
  refs.push({ provider: providerId, model: modelId });
485
487
  const res = applyTwins(refs, registry, true);
486
- if (!res.ok) return { ok: false, error: res.error };
488
+ if (!res.ok) return { ok: false, error: res.error, ...(res.stack ? { stack: res.stack } : {}) };
487
489
  return { ok: true, twin: `${providerId}/${twinIdFor(modelId)}` };
488
490
  }
489
491
 
@@ -616,7 +618,10 @@ export default function (pi: ExtensionAPI) {
616
618
  enabled: config.verification.enabled,
617
619
  mode: config.verification.mode,
618
620
  manifestPath: config.verification.manifestPath,
619
- ...(loaded.ok ? { manifestLoaded: true } : { manifestLoaded: false, manifestError: loaded.error }),
621
+ defaultProfile: config.verification.defaultProfile,
622
+ ...(loaded.ok
623
+ ? { manifestLoaded: true, manifestSource: loaded.source, manifestSourceLabel: loaded.sourceLabel }
624
+ : { manifestLoaded: false, manifestError: loaded.error }),
620
625
  changedFiles: status.changedFiles,
621
626
  checks: status.checks.map((item) => ({ id: item.check.id, state: item.state })),
622
627
  };
@@ -630,12 +635,16 @@ export default function (pi: ExtensionAPI) {
630
635
  next.verification.mode = mode;
631
636
  }
632
637
  saveConfig(next);
633
- return refresh();
638
+ const refreshed = refresh();
639
+ // L'attivazione è un'azione utente esplicita: acquisisce gli input
640
+ // correnti del fallback invece di riusare una snapshot presa quando era off.
641
+ if (mode !== "off" && verificationCwd) captureVerificationManifest(verificationCwd);
642
+ return refreshed;
634
643
  },
635
644
  reloadVerificationManifest: (cwd) => {
636
645
  const trusted = captureVerificationManifest(cwd);
637
646
  return trusted.ok
638
- ? { ok: true, path: trusted.path }
647
+ ? { ok: true, path: trusted.path, sourceLabel: trusted.sourceLabel }
639
648
  : { ok: false, error: trusted.error };
640
649
  },
641
650
  runVerification: ({ cwd, selection, signal, trusted }) =>
@@ -702,6 +711,14 @@ export default function (pi: ExtensionAPI) {
702
711
  if (overlays.size === 0) {
703
712
  const restored = refreshUpstream(ctx.modelRegistry);
704
713
  if (!restored.ok) {
714
+ try {
715
+ pi.appendEntry("jev-refresh-upstream-error", {
716
+ error: restored.error,
717
+ ...(restored.stack ? { stack: String(restored.stack).slice(0, 4000) } : {}),
718
+ });
719
+ } catch {
720
+ // solo diagnostica
721
+ }
705
722
  if (ctx.hasUI) {
706
723
  ctx.ui.notify(`Jev automatic senza guarded: ${restored.error} (diagnostica sola).`, "warning");
707
724
  }
@@ -982,18 +999,34 @@ export default function (pi: ExtensionAPI) {
982
999
 
983
1000
  if (event.toolName === "write" || event.toolName === "edit") {
984
1001
  const input = event.input as { path?: unknown };
985
- if (isVerificationManifestPath(input.path, ctx.cwd, config.verification.manifestPath)) {
1002
+ const profileInput = verificationManifestTrust?.ok && verificationManifestTrust.source === "global-profile";
1003
+ if (
1004
+ isVerificationPolicyPath(input.path, ctx.cwd, verificationManifestTrust) &&
1005
+ // Il profilo comune è opt-in: non rendere package.json immutabile
1006
+ // nelle installazioni che non hanno attivato la verifica progetto.
1007
+ (!profileInput || config.verification.enabled)
1008
+ ) {
1009
+ const ruleId = profileInput
1010
+ ? "verification-profile-input-immutable"
1011
+ : "verification-manifest-immutable";
986
1012
  pi.appendEntry("jev-tool-block", {
987
1013
  tool: event.toolName,
988
- ruleId: "verification-manifest-immutable",
1014
+ ruleId,
989
1015
  revision: TOOL_POLICY_REVISION,
990
1016
  });
991
1017
  if (ctx.hasUI) {
992
- ctx.ui.notify("Jev tool policy: il modello non può modificare il manifest che autorizza i suoi check.", "warning");
1018
+ ctx.ui.notify(
1019
+ profileInput
1020
+ ? "Jev tool policy: il modello non può modificare un file che decide i check del profilo globale acquisito."
1021
+ : "Jev tool policy: il modello non può modificare il manifest che autorizza i suoi check.",
1022
+ "warning",
1023
+ );
993
1024
  }
994
1025
  return {
995
1026
  block: true,
996
- reason: "Bloccato da jev tool policy (verification-manifest-immutable): modifica il manifest manualmente e poi usa /jev verification reload.",
1027
+ reason: profileInput
1028
+ ? "Bloccato da jev tool policy (verification-profile-input-immutable): modifica manualmente la configurazione del progetto e poi usa /jev verification reload."
1029
+ : "Bloccato da jev tool policy (verification-manifest-immutable): modifica il manifest manualmente e poi usa /jev verification reload.",
997
1030
  };
998
1031
  }
999
1032
  const verdict = checkFilePath(input.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-jev-guard",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "description": "Jev validation guard for pi: on-demand tool + automatic provider gate. Dual backend: TypeSafe direct + OpenRouter.",
6
6
  "keywords": [