pi-jev-guard 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/README.md +142 -0
- package/config/jev-config.example.json +61 -0
- package/extensions/index.ts +692 -0
- package/package.json +39 -0
- package/skills/jev-review/SKILL.md +25 -0
- package/src/adapters/openrouter.ts +289 -0
- package/src/adapters/typesafe.ts +244 -0
- package/src/ask.ts +189 -0
- package/src/automatic/gate.ts +45 -0
- package/src/automatic/guardian.ts +320 -0
- package/src/automatic/overlay.ts +247 -0
- package/src/automatic/recovery.ts +32 -0
- package/src/automatic/serialize.ts +278 -0
- package/src/cache.ts +56 -0
- package/src/commands.ts +408 -0
- package/src/config.ts +385 -0
- package/src/exfil.ts +180 -0
- package/src/factory.ts +140 -0
- package/src/markdown.ts +26 -0
- package/src/metrics.ts +69 -0
- package/src/output-judge.ts +176 -0
- package/src/policy.ts +32 -0
- package/src/reviewer.ts +350 -0
- package/src/tools-policy.ts +128 -0
- package/src/tools.ts +43 -0
package/src/commands.ts
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync, realpathSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
import type { ExtensionAPI, ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { JevConfig } from "./config.ts";
|
|
5
|
+
import { defaultConfigPath, expandHome, parseUpstreamRef, resolveBackend, saveConfig } from "./config.ts";
|
|
6
|
+
import type { ReviewFn } from "./reviewer.ts";
|
|
7
|
+
import { reviewWithCoverage } from "./reviewer.ts";
|
|
8
|
+
import type { Metrics } from "./metrics.ts";
|
|
9
|
+
import type { OutputVerdict } from "./output-judge.ts";
|
|
10
|
+
import type { GateVerdictReport } from "./automatic/guardian.ts";
|
|
11
|
+
|
|
12
|
+
export interface GateStatusView {
|
|
13
|
+
active: boolean;
|
|
14
|
+
twin?: string;
|
|
15
|
+
upstream?: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UpstreamResult {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
twin?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface JevCommandDeps {
|
|
26
|
+
config: () => JevConfig;
|
|
27
|
+
setMode: (mode: "on-demand" | "automatic") => JevConfig;
|
|
28
|
+
reload: () => JevConfig;
|
|
29
|
+
review: () => ReviewFn;
|
|
30
|
+
reviewerInfo: () => { backend: string; model: string };
|
|
31
|
+
metrics: Metrics;
|
|
32
|
+
gateStatus: () => GateStatusView;
|
|
33
|
+
setUpstream: (
|
|
34
|
+
registry: ModelRegistry,
|
|
35
|
+
providerId: string,
|
|
36
|
+
modelId: string,
|
|
37
|
+
) => UpstreamResult;
|
|
38
|
+
refreshUpstream: (registry: ModelRegistry) => UpstreamResult;
|
|
39
|
+
removeGuarded: () => { ok: boolean; error?: string };
|
|
40
|
+
switchToGuarded: (registry: ModelRegistry) => Promise<{
|
|
41
|
+
ok: boolean;
|
|
42
|
+
error?: string;
|
|
43
|
+
model?: string;
|
|
44
|
+
}>;
|
|
45
|
+
getLastOutput: () => { tool: string; verdict: OutputVerdict; at: number } | undefined;
|
|
46
|
+
getLastGate: () => { report: GateVerdictReport; at: number } | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const JEV_SUBCOMMANDS = [
|
|
50
|
+
"status",
|
|
51
|
+
"mode",
|
|
52
|
+
"models",
|
|
53
|
+
"upstream",
|
|
54
|
+
"off",
|
|
55
|
+
"refresh-upstream",
|
|
56
|
+
"check",
|
|
57
|
+
"stats",
|
|
58
|
+
"reload",
|
|
59
|
+
"output",
|
|
60
|
+
"last",
|
|
61
|
+
"save-key",
|
|
62
|
+
"help",
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
export const JEV_HELP_TEXT = [
|
|
66
|
+
"Jev guard — verifica risposte con TypeSafe Jev (via OpenRouter o diretto).",
|
|
67
|
+
"",
|
|
68
|
+
" /jev status Stato: modalità, backend, policy, gate, auth",
|
|
69
|
+
" /jev mode on-demand Solo verifiche esplicite (default, zero costi impliciti)",
|
|
70
|
+
" /jev mode automatic Enforcement: seleziona il twin guarded + verifica",
|
|
71
|
+
" /jev models [filtro] Modelli pi (● twin guarded attivo, ★ ultimo salvato)",
|
|
72
|
+
" /jev upstream P M Crea twin guarded M__jev dentro il provider P (salvato)",
|
|
73
|
+
" /jev upstream P/M Stessa cosa in forma compatta",
|
|
74
|
+
" /jev upstream Riapplica l'ultimo upstream salvato",
|
|
75
|
+
" /jev off Rimuove il twin (torna on-demand)",
|
|
76
|
+
" /jev refresh-upstream Ricrea il twin dal registry (dopo refresh cataloghi)",
|
|
77
|
+
" /jev check <file> [req] Verifica un file subito, senza cambiare modalità",
|
|
78
|
+
" /jev stats Conteggi pass/block/review/unavailable + latenze",
|
|
79
|
+
" /jev output Ultimo output giudicato: leak + classe errore",
|
|
80
|
+
" /jev last Ultimo verdetto del gate (twin): esito e controlli",
|
|
81
|
+
" /jev save-key Salva la chiave Jev su file (permessi 600)",
|
|
82
|
+
" /jev reload Ricarica jev-config.json da disco",
|
|
83
|
+
" /jev help Questo aiuto",
|
|
84
|
+
"",
|
|
85
|
+
"Flusso tipico: /jev upstream deepseek deepseek-flash → /model (scegli __jev)",
|
|
86
|
+
"→ /jev mode automatic. Il twin usa la STESSA auth del provider: nessun login extra.",
|
|
87
|
+
"Torna normale con: /model + /jev mode on-demand.",
|
|
88
|
+
"Env JEV_AUTO_UPSTREAM=P/M: crea il twin all'avvio (default: nessuno;",
|
|
89
|
+
"con pi --print solo deepseek/deepseek-flash).",
|
|
90
|
+
].join("\n");
|
|
91
|
+
|
|
92
|
+
export function registerJevCommand(pi: ExtensionAPI, deps: JevCommandDeps) {
|
|
93
|
+
pi.registerCommand("jev", {
|
|
94
|
+
description:
|
|
95
|
+
"Jev guard: verifica con TypeSafe Jev. Sottocomandi: status, mode, models, upstream, off, check, stats, help.",
|
|
96
|
+
getArgumentCompletions: (argumentPrefix) => {
|
|
97
|
+
const prefix = argumentPrefix.trim().toLowerCase();
|
|
98
|
+
return JEV_SUBCOMMANDS.filter((s) => s.startsWith(prefix)).map(
|
|
99
|
+
(s) => ({ value: s, label: `/jev ${s}` }),
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
handler: async (args, ctx) => {
|
|
103
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
104
|
+
const sub = parts[0] ?? "status";
|
|
105
|
+
|
|
106
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
107
|
+
ctx.ui.notify(JEV_HELP_TEXT, "info");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (sub === "status") {
|
|
112
|
+
const cfg = deps.config();
|
|
113
|
+
const resolved = resolveBackend(cfg);
|
|
114
|
+
const info = deps.reviewerInfo();
|
|
115
|
+
const gate = deps.gateStatus();
|
|
116
|
+
const authProvider = gate.upstream?.split("/")[0] ?? cfg.automatic.upstreamProvider;
|
|
117
|
+
let guardedAuth = "—";
|
|
118
|
+
if (gate.active) {
|
|
119
|
+
try {
|
|
120
|
+
const st = ctx.modelRegistry.getProviderAuthStatus(authProvider);
|
|
121
|
+
guardedAuth = st.configured
|
|
122
|
+
? `pronto (${st.source ?? "configurato"}, condivisa col provider)`
|
|
123
|
+
: `non configurato — /login ${authProvider}`;
|
|
124
|
+
} catch {
|
|
125
|
+
guardedAuth = "sconosciuto";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
ctx.ui.notify(
|
|
129
|
+
[
|
|
130
|
+
`Modalità: ${cfg.mode}`,
|
|
131
|
+
`Backend: ${info.backend} (setting=${cfg.jev.backend}, ${resolved.reason})`,
|
|
132
|
+
`Modello: ${info.model}`,
|
|
133
|
+
`Verificatore timeout: ${resolved.timeoutMs}ms`,
|
|
134
|
+
`Policy: ${cfg.policy.revision} pass<=${cfg.policy.passMaxFlawProbability} block>=${cfg.policy.blockMinFlawProbability}`,
|
|
135
|
+
`Chiave presente: ${resolved.apiKeyPresent ? "sì" : "no"} (${resolved.keySource})`,
|
|
136
|
+
`Gate: ${gate.active ? `attivo (twin ${gate.twin}, stessa auth di ${authProvider})` : `non attivo (${gate.reason})`}`,
|
|
137
|
+
`Ultimo upstream salvato: ${cfg.automatic.upstreamProvider}/${cfg.automatic.upstreamModel}`,
|
|
138
|
+
`Auth guarded: ${guardedAuth}`,
|
|
139
|
+
`Tool policy: ${cfg.toolsPolicy.enabled ? "attiva in automatic (bash/powershell/write/edit)" : "disattiva (config)"}`,
|
|
140
|
+
`Exfil check: ${cfg.exfilCheck.enabled ? "attivo in automatic (confirm in TUI, fail-open)" : "disattivo (config)"}`,
|
|
141
|
+
`Output judge: ${cfg.outputJudge.enabled ? `attivo in automatic (tool: ${cfg.outputJudge.tools.join(",") || "—"}, fail-open)` : "disattivo (config)"}`,
|
|
142
|
+
cfg.mode === "automatic"
|
|
143
|
+
? "ATTENZIONE: in automatic testo/codice vengono inviati al verificatore. Crea il twin con /jev upstream e seleziona il modello __jev per enforcement."
|
|
144
|
+
: "on-demand: nessuna verifica automatica.",
|
|
145
|
+
].join("\n"),
|
|
146
|
+
"info",
|
|
147
|
+
);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (sub === "mode") {
|
|
152
|
+
const value = parts[1];
|
|
153
|
+
if (value !== "on-demand" && value !== "automatic") {
|
|
154
|
+
ctx.ui.notify("Uso: /jev mode on-demand|automatic", "warning");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
deps.setMode(value);
|
|
158
|
+
if (value === "automatic") {
|
|
159
|
+
const sw = await deps.switchToGuarded(ctx.modelRegistry);
|
|
160
|
+
ctx.ui.notify(
|
|
161
|
+
`Modalità automatic attivata. Testo/codice saranno inviati a ${deps.reviewerInfo().backend}. ` +
|
|
162
|
+
(sw.ok
|
|
163
|
+
? `Modello selezionato: ${sw.model} (enforcement attivo).`
|
|
164
|
+
: `ATTENZIONE: ${sw.error} (solo diagnostica, nessun enforcement).`),
|
|
165
|
+
sw.ok ? "info" : "warning",
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
ctx.ui.notify("Modalità on-demand attivata. Nessuna verifica automatica.", "info");
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (sub === "models") {
|
|
174
|
+
const cfg = deps.config();
|
|
175
|
+
const filter = parts.slice(1).join(" ").toLowerCase();
|
|
176
|
+
let all: Array<{ provider: string; id: string; api: string }>;
|
|
177
|
+
try {
|
|
178
|
+
all = ctx.modelRegistry.getAll().map((m) => ({
|
|
179
|
+
provider: m.provider,
|
|
180
|
+
id: m.id,
|
|
181
|
+
api: m.api,
|
|
182
|
+
}));
|
|
183
|
+
} catch {
|
|
184
|
+
ctx.ui.notify("Elenco modelli non disponibile.", "error");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const gate = deps.gateStatus();
|
|
188
|
+
const activeTwin = gate.active ? gate.twin ?? "" : "";
|
|
189
|
+
const saved = `${cfg.automatic.upstreamProvider}/${cfg.automatic.upstreamModel}`;
|
|
190
|
+
const matched = all.filter((m) => {
|
|
191
|
+
if (!filter) return true;
|
|
192
|
+
return `${m.provider}/${m.id}`.toLowerCase().includes(filter);
|
|
193
|
+
});
|
|
194
|
+
const lines = matched.slice(0, 40).map((m) => {
|
|
195
|
+
const ref = `${m.provider}/${m.id}`;
|
|
196
|
+
const mark = ref === activeTwin ? "● " : ref === saved ? "★ " : " ";
|
|
197
|
+
return `${mark}${ref} [${m.api}]`;
|
|
198
|
+
});
|
|
199
|
+
const header =
|
|
200
|
+
`Twin guarded: ${activeTwin ? `● ${activeTwin}` : "nessuno — /jev upstream <provider> <model>"}` +
|
|
201
|
+
`\nUltimo salvato: ★ ${saved}`;
|
|
202
|
+
const more = matched.length > 40 ? `\n… +${matched.length - 40} (raffina il filtro)` : "";
|
|
203
|
+
ctx.ui.notify(
|
|
204
|
+
[header, `Modelli: ${matched.length}/${all.length}`, "", ...lines].join("\n") + more,
|
|
205
|
+
"info",
|
|
206
|
+
);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (sub === "upstream") {
|
|
211
|
+
// Senza argomenti: riapplica l'ultimo upstream salvato.
|
|
212
|
+
if (parts.length === 1) {
|
|
213
|
+
const res = deps.refreshUpstream(ctx.modelRegistry);
|
|
214
|
+
ctx.ui.notify(
|
|
215
|
+
res.ok
|
|
216
|
+
? `Twin ripristinato: ${res.twin}. Selezionalo con /model.`
|
|
217
|
+
: `Ripristino fallito: ${res.error}`,
|
|
218
|
+
res.ok ? "info" : "error",
|
|
219
|
+
);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const parsed = parseUpstreamRef(parts.slice(1));
|
|
223
|
+
if ("error" in parsed) {
|
|
224
|
+
ctx.ui.notify(parsed.error, "warning");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const res = deps.setUpstream(ctx.modelRegistry, parsed.provider, parsed.model);
|
|
228
|
+
if (!res.ok) {
|
|
229
|
+
ctx.ui.notify(`Upstream non cambiato: ${res.error}`, "error");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
ctx.ui.notify(
|
|
233
|
+
`Twin creato: ${res.twin} da ${parsed.provider}/${parsed.model} (salvato, stessa auth del provider). ` +
|
|
234
|
+
`Selezionalo con /model per enforcement in automatic.`,
|
|
235
|
+
"info",
|
|
236
|
+
);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (sub === "off") {
|
|
241
|
+
const res = deps.removeGuarded();
|
|
242
|
+
if (res.ok) {
|
|
243
|
+
// Senza twin, automatic è contraddittorio: torna on-demand.
|
|
244
|
+
deps.setMode("on-demand");
|
|
245
|
+
}
|
|
246
|
+
ctx.ui.notify(
|
|
247
|
+
res.ok
|
|
248
|
+
? "Twin rimosso (provider originale ripristinato), modalità on-demand."
|
|
249
|
+
: `Niente da rimuovere: ${res.error}`,
|
|
250
|
+
res.ok ? "info" : "warning",
|
|
251
|
+
);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (sub === "refresh-upstream") {
|
|
256
|
+
const res = deps.refreshUpstream(ctx.modelRegistry);
|
|
257
|
+
ctx.ui.notify(
|
|
258
|
+
res.ok
|
|
259
|
+
? `Twin riallineato: ${res.twin}`
|
|
260
|
+
: `Refresh fallito: ${res.error}`,
|
|
261
|
+
res.ok ? "info" : "error",
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (sub === "last") {
|
|
267
|
+
const last = deps.getLastGate();
|
|
268
|
+
if (!last) {
|
|
269
|
+
ctx.ui.notify("Jev: nessun verdetto del gate ancora (serve un turno sul twin in automatic).", "info");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const v = last.report.verdict;
|
|
273
|
+
const top = [...v.checks].sort((a, b) => b.pFlaw - a.pFlaw)[0];
|
|
274
|
+
ctx.ui.notify(
|
|
275
|
+
[
|
|
276
|
+
`Gate: ${last.report.outcome === "published" ? "pubblicato" : "trattenuto"} dopo ${last.report.attempts} tentativ${last.report.attempts === 1 ? "o" : "i"}`,
|
|
277
|
+
`stato: ${v.status}${top ? ` — ${top.ruleId} p=${top.pFlaw.toFixed(2)}` : ""}`,
|
|
278
|
+
v.errorCode ? `errore: ${v.errorCode}` : `backend: ${v.backend ?? "?"} · modello: ${v.model ?? "?"}`,
|
|
279
|
+
v.truncated ? `troncato: ${JSON.stringify(v.truncated)}` : "copertura intera",
|
|
280
|
+
`tempo verifica: ${Math.round(v.elapsedMs)}ms`,
|
|
281
|
+
].join("\n"),
|
|
282
|
+
"info",
|
|
283
|
+
);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (sub === "output") {
|
|
288
|
+
const last = deps.getLastOutput();
|
|
289
|
+
if (!last) {
|
|
290
|
+
ctx.ui.notify("Jev: nessun output giudicato ancora.", "info");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const v = last.verdict;
|
|
294
|
+
ctx.ui.notify(
|
|
295
|
+
[
|
|
296
|
+
`Output: ${last.tool} — ${v.kind}`,
|
|
297
|
+
`leak p=${v.leaksSecret.toFixed(2)}`,
|
|
298
|
+
`classe: ${v.failureClass ?? "n/a"} (conf ${v.classConfidence?.toFixed(2) ?? "n/a"})`,
|
|
299
|
+
v.notice ? `avviso: ${v.notice}` : "nessun avviso",
|
|
300
|
+
`modello: ${v.model ?? "?"} · ${Math.round(v.elapsedMs)}ms`,
|
|
301
|
+
].join("\n"),
|
|
302
|
+
"info",
|
|
303
|
+
);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (sub === "reload") {
|
|
308
|
+
const cfg = deps.reload();
|
|
309
|
+
ctx.ui.notify(`Config ricaricata. mode=${cfg.mode} backend=${cfg.jev.backend}`, "info");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (sub === "save-key") {
|
|
314
|
+
const cfg = deps.config();
|
|
315
|
+
const target = cfg.jev.apiKeyFile?.trim()
|
|
316
|
+
? expandHome(cfg.jev.apiKeyFile.trim())
|
|
317
|
+
: resolve(dirname(defaultConfigPath()), "jev-api-key.txt");
|
|
318
|
+
let key: string | undefined;
|
|
319
|
+
try {
|
|
320
|
+
key = await ctx.ui.input(
|
|
321
|
+
"Jev API key",
|
|
322
|
+
"Incolla la chiave (potrebbe fare echo nel terminale)",
|
|
323
|
+
);
|
|
324
|
+
} catch {
|
|
325
|
+
ctx.ui.notify("Jev: input non disponibile qui — usa la shell: `echo KEY > ~/.pi/agent/jev-api-key.txt && chmod 600 ~/.pi/agent/jev-api-key.txt`.", "warning");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (!key || !key.trim()) {
|
|
329
|
+
ctx.ui.notify("Jev: chiave vuota, niente da salvare.", "warning");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
334
|
+
writeFileSync(target, `${key.trim()}\n`, { mode: 0o600 });
|
|
335
|
+
chmodSync(target, 0o600);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
ctx.ui.notify(`Jev: salvataggio fallito: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// Punta la config al file e ricarica gli handle col nuovo stato.
|
|
341
|
+
cfg.jev.apiKeyFile = target;
|
|
342
|
+
try {
|
|
343
|
+
saveConfig(cfg);
|
|
344
|
+
} catch {
|
|
345
|
+
// Resta in memoria anche se il salvataggio fallisce.
|
|
346
|
+
}
|
|
347
|
+
const reloaded = deps.reload();
|
|
348
|
+
const resolved = resolveBackend(reloaded);
|
|
349
|
+
ctx.ui.notify(
|
|
350
|
+
resolved.apiKeyPresent
|
|
351
|
+
? `Jev: chiave salvata in ${target} (600). Sorgente: ${resolved.keySource}.`
|
|
352
|
+
: `Jev: file scritto ma chiave non rilevata (${resolved.keySource}).`,
|
|
353
|
+
resolved.apiKeyPresent ? "info" : "warning",
|
|
354
|
+
);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (sub === "stats") {
|
|
359
|
+
ctx.ui.notify(deps.metrics.summary(), "info");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (sub === "check") {
|
|
364
|
+
const target = parts[1];
|
|
365
|
+
if (!target) {
|
|
366
|
+
ctx.ui.notify("Uso: /jev check <file> [requirements...]", "warning");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const abs = resolve(ctx.cwd, target);
|
|
370
|
+
if (!existsSync(abs)) {
|
|
371
|
+
ctx.ui.notify(`File non trovato: ${target}`, "error");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const st = statSync(abs);
|
|
375
|
+
const cfg = deps.config();
|
|
376
|
+
if (st.size > cfg.limits.maxCandidateChars) {
|
|
377
|
+
ctx.ui.notify(`File troppo grande (${st.size} bytes)`, "error");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
// Risolvi symlink e verifica che resti un file regolare.
|
|
381
|
+
const real = realpathSync(abs);
|
|
382
|
+
const realStat = statSync(real);
|
|
383
|
+
if (!realStat.isFile()) {
|
|
384
|
+
ctx.ui.notify("Target non è un file regolare", "error");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const content = readFileSync(real, "utf8");
|
|
388
|
+
const requirements =
|
|
389
|
+
parts.slice(2).join(" ") ||
|
|
390
|
+
"Verifica difetti evidenti rispetto alle best practice e ai requisiti nel file.";
|
|
391
|
+
const result = await reviewWithCoverage(
|
|
392
|
+
deps.review(),
|
|
393
|
+
{ requirements, candidate: content },
|
|
394
|
+
{ limits: cfg.limits, requirementsSensitive: true },
|
|
395
|
+
ctx.signal ?? undefined,
|
|
396
|
+
);
|
|
397
|
+
deps.metrics.record(result);
|
|
398
|
+
ctx.ui.notify(`Jev check ${target}: ${JSON.stringify(result)}`, "info");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
ctx.ui.notify(
|
|
403
|
+
`Sottocomando sconosciuto: ${sub}\n\n${JEV_HELP_TEXT}`,
|
|
404
|
+
"warning",
|
|
405
|
+
);
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|