pi-jev-guard 0.7.0 → 0.7.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 +2 -1
- package/package.json +1 -1
- package/src/grounding.ts +1 -1
- package/src/verification.ts +69 -7
package/README.md
CHANGED
|
@@ -190,10 +190,11 @@ comandi fissi dell'estensione quando trova segnali convenzionali:
|
|
|
190
190
|
| `package.json` con script | `npm run typecheck`, `npm run lint`, uno tra `npm run test:unit`, `npm run test:ci`, `npm test` |
|
|
191
191
|
| `Cargo.toml` | `cargo check`, `cargo test` |
|
|
192
192
|
| `go.mod` | `go test ./...` |
|
|
193
|
+
| `*.sln` / `*.csproj` (root o 2 livelli) | `dotnet build`, `dotnet test` |
|
|
193
194
|
| configurazione pytest | `python -m pytest` |
|
|
194
195
|
| repository Git | `git diff --check` |
|
|
195
196
|
|
|
196
|
-
Il profilo sceglie un solo ecosistema (Node → Rust → Go → Python) e aggiunge
|
|
197
|
+
Il profilo sceglie un solo ecosistema (Node → Rust → Go → .NET → Python) e aggiunge
|
|
197
198
|
il check Git quando applicabile. I comandi sono bounded da
|
|
198
199
|
`verification.maxCommands` (default `4`) e timeout. Se nessun segnale è
|
|
199
200
|
rilevato, non esegue nulla e il footer dichiara la limitazione.
|
package/package.json
CHANGED
package/src/grounding.ts
CHANGED
|
@@ -296,7 +296,7 @@ export interface GroundingDecision {
|
|
|
296
296
|
|
|
297
297
|
/** Nota accodata al testo pubblicato quando il grounding scatta senza hold. */
|
|
298
298
|
export function groundingNotice(reasons: string[]): string {
|
|
299
|
-
return `\n\n> [jev] grounding: affermazioni sulla sessione non supportate dall'evidenza (${reasons.join(", ")}) —
|
|
299
|
+
return `\n\n> [jev] grounding: affermazioni sulla sessione non supportate dall'evidenza (${reasons.join(", ")}) — controlla i file/comandi citati prima di darli per verificati.`;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
/** Feedback per la rigenerazione: correggere, non accusare. */
|
package/src/verification.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* dati, non dal testo del modello, quindi non può inventare test superati.
|
|
8
8
|
*/
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
|
-
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
13
|
|
|
@@ -340,6 +340,14 @@ interface ProfileInput {
|
|
|
340
340
|
text: string;
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
/** Selezione del profilo: comandi fissi + input che decidono se applicarsi. */
|
|
344
|
+
interface ProfileSelection {
|
|
345
|
+
checks: VerificationCheck[];
|
|
346
|
+
inputs: ProfileInput[];
|
|
347
|
+
/** Solo nomi dei marker (es. .sln/.csproj): il contenuto non sceglie comandi. */
|
|
348
|
+
markers?: string[];
|
|
349
|
+
}
|
|
350
|
+
|
|
343
351
|
function readProfileInput(path: string): ProfileInput | { error: string } {
|
|
344
352
|
try {
|
|
345
353
|
if (statSync(path).size > MAX_PROFILE_INPUT_BYTES) {
|
|
@@ -370,7 +378,7 @@ function findGitRoot(cwd: string): string | undefined {
|
|
|
370
378
|
}
|
|
371
379
|
|
|
372
380
|
function nodeProfile(root: string):
|
|
373
|
-
|
|
|
381
|
+
| ProfileSelection
|
|
374
382
|
| { error: string }
|
|
375
383
|
| undefined {
|
|
376
384
|
const path = join(root, "package.json");
|
|
@@ -408,7 +416,7 @@ function markerProfile(args: {
|
|
|
408
416
|
root: string;
|
|
409
417
|
marker: string;
|
|
410
418
|
checks: VerificationCheck[];
|
|
411
|
-
}):
|
|
419
|
+
}): ProfileSelection | { error: string } | undefined {
|
|
412
420
|
const path = join(args.root, args.marker);
|
|
413
421
|
if (!existsSync(path)) return undefined;
|
|
414
422
|
const input = readProfileInput(path);
|
|
@@ -416,8 +424,59 @@ function markerProfile(args: {
|
|
|
416
424
|
return { checks: args.checks, inputs: [input] };
|
|
417
425
|
}
|
|
418
426
|
|
|
427
|
+
const DOTNET_MARKER_EXT = [".sln", ".csproj", ".fsproj", ".vbproj"];
|
|
428
|
+
const DOTNET_SKIP_DIRS = new Set(["node_modules", ".git", "bin", "obj"]);
|
|
429
|
+
const DOTNET_FILES = [
|
|
430
|
+
"**/*.cs", "**/*.fs", "**/*.vb",
|
|
431
|
+
"**/*.csproj", "**/*.fsproj", "**/*.vbproj", "**/*.sln",
|
|
432
|
+
"**/*.props", "**/*.targets", "global.json",
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Marker .NET in root e due livelli di sottocartelle (bounded: max 40):
|
|
437
|
+
* solo nomi, il contenuto dei progetti non seleziona comandi — `dotnet
|
|
438
|
+
* build`/`dotnet test` sono fissi, quindi niente superficie di iniezione.
|
|
439
|
+
*/
|
|
440
|
+
function findDotnetMarkers(root: string): string[] {
|
|
441
|
+
const found: string[] = [];
|
|
442
|
+
const visit = (dir: string, depth: number): void => {
|
|
443
|
+
if (found.length >= 40 || depth > 2) return;
|
|
444
|
+
let entries;
|
|
445
|
+
try {
|
|
446
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
447
|
+
} catch {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
451
|
+
if (found.length >= 40) return;
|
|
452
|
+
const full = join(dir, entry.name);
|
|
453
|
+
if (entry.isDirectory()) {
|
|
454
|
+
if (entry.name.startsWith(".") || DOTNET_SKIP_DIRS.has(entry.name)) continue;
|
|
455
|
+
visit(full, depth + 1);
|
|
456
|
+
} else if (DOTNET_MARKER_EXT.some((ext) => entry.name.endsWith(ext))) {
|
|
457
|
+
found.push(relative(root, full).split(sep).join("/"));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
visit(root, 0);
|
|
462
|
+
return found.sort();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function dotnetProfile(root: string): ProfileSelection | undefined {
|
|
466
|
+
const markers = findDotnetMarkers(root);
|
|
467
|
+
if (markers.length === 0) return undefined;
|
|
468
|
+
return {
|
|
469
|
+
checks: [
|
|
470
|
+
{ id: "dotnet-build", command: "dotnet build", description: ".NET build", files: DOTNET_FILES },
|
|
471
|
+
{ id: "dotnet-test", command: "dotnet test", description: ".NET tests", files: [...DOTNET_FILES, "**/tests/**"] },
|
|
472
|
+
],
|
|
473
|
+
inputs: [],
|
|
474
|
+
markers,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
419
478
|
function pythonProfile(root: string):
|
|
420
|
-
|
|
|
479
|
+
| ProfileSelection
|
|
421
480
|
| { error: string }
|
|
422
481
|
| undefined {
|
|
423
482
|
const candidates = ["pytest.ini", "pyproject.toml", "setup.cfg"];
|
|
@@ -470,9 +529,10 @@ export function loadCommonVerificationProfile(cwd: string): LoadedVerificationMa
|
|
|
470
529
|
checks: [{ id: "go-test", command: "go test ./...", description: "Go tests", files: ["**/*.go", "go.mod", "go.sum"] }],
|
|
471
530
|
});
|
|
472
531
|
if (go && "error" in go) return { ok: false, error: go.error };
|
|
473
|
-
const
|
|
532
|
+
const dotnet = node || cargo || go ? undefined : dotnetProfile(root);
|
|
533
|
+
const python = node || cargo || go || dotnet ? undefined : pythonProfile(root);
|
|
474
534
|
if (python && "error" in python) return { ok: false, error: python.error };
|
|
475
|
-
const selected = node ?? cargo ?? go ?? python;
|
|
535
|
+
const selected = node ?? cargo ?? go ?? dotnet ?? python;
|
|
476
536
|
const checks = selected ? [...selected.checks] : [];
|
|
477
537
|
const gitRoot = findGitRoot(root);
|
|
478
538
|
if (gitRoot) {
|
|
@@ -481,16 +541,18 @@ export function loadCommonVerificationProfile(cwd: string): LoadedVerificationMa
|
|
|
481
541
|
if (checks.length === 0) {
|
|
482
542
|
return {
|
|
483
543
|
ok: false,
|
|
484
|
-
error: "nessun manifest locale e profilo globale common non applicabile (nessun progetto Node/Rust/Go/Python o repository Git rilevato)",
|
|
544
|
+
error: "nessun manifest locale e profilo globale common non applicabile (nessun progetto Node/Rust/Go/.NET/Python o repository Git rilevato)",
|
|
485
545
|
};
|
|
486
546
|
}
|
|
487
547
|
const inputs = selected?.inputs ?? [];
|
|
548
|
+
const markers = selected?.markers ?? [];
|
|
488
549
|
const manifest: VerificationManifest = { version: 1, checks };
|
|
489
550
|
const fingerprint = fingerprintText(JSON.stringify({
|
|
490
551
|
profile: "common-v1",
|
|
491
552
|
root,
|
|
492
553
|
gitRoot: gitRoot ?? null,
|
|
493
554
|
inputs: inputs.map((input) => ({ path: input.path, text: input.text })),
|
|
555
|
+
markers,
|
|
494
556
|
manifest,
|
|
495
557
|
}));
|
|
496
558
|
return {
|