veriskit 0.2.0 → 0.4.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/CHANGELOG.md +21 -0
- package/README.md +163 -109
- package/dist/index.js +1028 -80
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -8,6 +8,20 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/version.ts
|
|
12
|
+
import { readFileSync } from "fs";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var pkgUrl, VERSION;
|
|
15
|
+
var init_version = __esm({
|
|
16
|
+
"src/version.ts"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
pkgUrl = new URL("../package.json", import.meta.url);
|
|
19
|
+
VERSION = JSON.parse(
|
|
20
|
+
readFileSync(fileURLToPath(pkgUrl), "utf8")
|
|
21
|
+
).version;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
11
25
|
// src/util/fs-safe.ts
|
|
12
26
|
import { existsSync } from "fs";
|
|
13
27
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -57,6 +71,7 @@ async function detectProject(root) {
|
|
|
57
71
|
];
|
|
58
72
|
return {
|
|
59
73
|
root,
|
|
74
|
+
name: pkg.name ?? void 0,
|
|
60
75
|
packageManager: detectPackageManager(root),
|
|
61
76
|
frameworks,
|
|
62
77
|
languages,
|
|
@@ -153,7 +168,7 @@ function renderDoctor(project, env) {
|
|
|
153
168
|
const ok = (s) => plain ? s : pc.green(s);
|
|
154
169
|
const dim = (s) => plain ? s : pc.dim(s);
|
|
155
170
|
const lines = [];
|
|
156
|
-
lines.push("
|
|
171
|
+
lines.push("VerisKit doctor");
|
|
157
172
|
lines.push("");
|
|
158
173
|
lines.push(`Package manager ${project.packageManager}`);
|
|
159
174
|
lines.push(`Node ${env.node}`);
|
|
@@ -189,8 +204,80 @@ var init_doctor = __esm({
|
|
|
189
204
|
}
|
|
190
205
|
});
|
|
191
206
|
|
|
207
|
+
// src/evidence/record.ts
|
|
208
|
+
import { createHash } from "crypto";
|
|
209
|
+
import { basename } from "path";
|
|
210
|
+
function canonicalize(value) {
|
|
211
|
+
return JSON.stringify(sortValue(value));
|
|
212
|
+
}
|
|
213
|
+
function sortValue(value) {
|
|
214
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
215
|
+
if (value && typeof value === "object") {
|
|
216
|
+
const src = value;
|
|
217
|
+
const out = {};
|
|
218
|
+
for (const key of Object.keys(src).sort()) {
|
|
219
|
+
if (src[key] !== void 0) out[key] = sortValue(src[key]);
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
function sha256(text) {
|
|
226
|
+
return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
|
|
227
|
+
}
|
|
228
|
+
function computeDigest(record) {
|
|
229
|
+
const { digest: _omit, ...rest } = record;
|
|
230
|
+
return sha256(canonicalize(rest));
|
|
231
|
+
}
|
|
232
|
+
function buildRecord(run, git, logDigests, toolVersion) {
|
|
233
|
+
const runnerOf = new Map(
|
|
234
|
+
run.project.capabilities.map((c) => [c.id, c.runner])
|
|
235
|
+
);
|
|
236
|
+
const checks = run.results.map((r) => {
|
|
237
|
+
const check = {
|
|
238
|
+
id: r.checkId,
|
|
239
|
+
status: r.status,
|
|
240
|
+
durationMs: r.durationMs,
|
|
241
|
+
summary: r.summary
|
|
242
|
+
};
|
|
243
|
+
const runner = runnerOf.get(r.checkId);
|
|
244
|
+
if (runner) check.runner = runner;
|
|
245
|
+
if (r.counts) check.counts = r.counts;
|
|
246
|
+
const digest = logDigests[r.checkId];
|
|
247
|
+
if (digest) check.logDigest = digest;
|
|
248
|
+
return check;
|
|
249
|
+
});
|
|
250
|
+
const scope = run.scope ? { kind: run.scope.kind, changedCount: run.scope.changedCount } : { kind: "full", changedCount: 0 };
|
|
251
|
+
const base = {
|
|
252
|
+
schema: EVIDENCE_SCHEMA,
|
|
253
|
+
id: run.id,
|
|
254
|
+
startedAt: run.startedAt,
|
|
255
|
+
tool: { name: "veriskit", version: toolVersion },
|
|
256
|
+
git,
|
|
257
|
+
env: run.env,
|
|
258
|
+
project: {
|
|
259
|
+
name: run.project.name ?? basename(run.project.root),
|
|
260
|
+
packageManager: run.project.packageManager,
|
|
261
|
+
frameworks: run.project.frameworks,
|
|
262
|
+
languages: run.project.languages
|
|
263
|
+
},
|
|
264
|
+
scope,
|
|
265
|
+
checks,
|
|
266
|
+
verdict: run.verdict
|
|
267
|
+
};
|
|
268
|
+
return { ...base, digest: computeDigest(base) };
|
|
269
|
+
}
|
|
270
|
+
var EVIDENCE_SCHEMA;
|
|
271
|
+
var init_record = __esm({
|
|
272
|
+
"src/evidence/record.ts"() {
|
|
273
|
+
"use strict";
|
|
274
|
+
EVIDENCE_SCHEMA = "veriskit/evidence@1";
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
192
278
|
// src/evidence/store.ts
|
|
193
|
-
import {
|
|
279
|
+
import { readdirSync } from "fs";
|
|
280
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
194
281
|
import { join as join2 } from "path";
|
|
195
282
|
function newRunId() {
|
|
196
283
|
counter += 1;
|
|
@@ -207,11 +294,6 @@ async function writeLog(runDir, checkId, content) {
|
|
|
207
294
|
await writeFile2(ref, content, "utf8");
|
|
208
295
|
return ref;
|
|
209
296
|
}
|
|
210
|
-
async function writeMetadata(runDir, run) {
|
|
211
|
-
const ref = join2(runDir, "metadata.json");
|
|
212
|
-
await writeFile2(ref, JSON.stringify(run, null, 2), "utf8");
|
|
213
|
-
return ref;
|
|
214
|
-
}
|
|
215
297
|
async function writeReport(root, id, markdown) {
|
|
216
298
|
const dir = join2(root, ".veris", "reports");
|
|
217
299
|
await ensureDir(dir);
|
|
@@ -219,11 +301,54 @@ async function writeReport(root, id, markdown) {
|
|
|
219
301
|
await writeFile2(ref, markdown, "utf8");
|
|
220
302
|
return ref;
|
|
221
303
|
}
|
|
304
|
+
async function writeEvidence(runDir, record) {
|
|
305
|
+
const ref = join2(runDir, "evidence.json");
|
|
306
|
+
await writeFile2(ref, `${JSON.stringify(record, null, 2)}
|
|
307
|
+
`, "utf8");
|
|
308
|
+
return ref;
|
|
309
|
+
}
|
|
310
|
+
async function digestLogs(run) {
|
|
311
|
+
const out = {};
|
|
312
|
+
for (const r of run.results) {
|
|
313
|
+
if (!r.logRef) continue;
|
|
314
|
+
try {
|
|
315
|
+
out[r.checkId] = sha256(await readFile2(r.logRef, "utf8"));
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
async function readRunLogs(runDir, checkIds) {
|
|
322
|
+
const out = {};
|
|
323
|
+
for (const id of checkIds) {
|
|
324
|
+
try {
|
|
325
|
+
out[id] = await readFile2(join2(runDir, `${id}.log`), "utf8");
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
function latestRunDir(root) {
|
|
332
|
+
const runs = join2(root, ".veris", "runs");
|
|
333
|
+
try {
|
|
334
|
+
const dirs = readdirSync(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name).sort();
|
|
335
|
+
const latest = dirs.at(-1);
|
|
336
|
+
return latest ? join2(runs, latest) : null;
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function ensureEvidenceDir(root) {
|
|
342
|
+
const dir = join2(root, ".veris", "evidence");
|
|
343
|
+
await ensureDir(dir);
|
|
344
|
+
return dir;
|
|
345
|
+
}
|
|
222
346
|
var counter;
|
|
223
347
|
var init_store = __esm({
|
|
224
348
|
"src/evidence/store.ts"() {
|
|
225
349
|
"use strict";
|
|
226
350
|
init_fs_safe();
|
|
351
|
+
init_record();
|
|
227
352
|
counter = 0;
|
|
228
353
|
}
|
|
229
354
|
});
|
|
@@ -231,7 +356,7 @@ var init_store = __esm({
|
|
|
231
356
|
// src/util/exec.ts
|
|
232
357
|
import { spawn } from "child_process";
|
|
233
358
|
function exec(cmd, args, opts = {}) {
|
|
234
|
-
return new Promise((
|
|
359
|
+
return new Promise((resolve2) => {
|
|
235
360
|
const start = performance.now();
|
|
236
361
|
const child = spawn(cmd, args, {
|
|
237
362
|
cwd: opts.cwd,
|
|
@@ -249,7 +374,7 @@ function exec(cmd, args, opts = {}) {
|
|
|
249
374
|
child.stderr.on("data", (d) => stderr += d.toString());
|
|
250
375
|
child.on("close", (code) => {
|
|
251
376
|
if (timer) clearTimeout(timer);
|
|
252
|
-
|
|
377
|
+
resolve2({
|
|
253
378
|
code: code ?? 1,
|
|
254
379
|
stdout,
|
|
255
380
|
stderr,
|
|
@@ -259,7 +384,7 @@ function exec(cmd, args, opts = {}) {
|
|
|
259
384
|
});
|
|
260
385
|
child.on("error", () => {
|
|
261
386
|
if (timer) clearTimeout(timer);
|
|
262
|
-
|
|
387
|
+
resolve2({
|
|
263
388
|
code: 127,
|
|
264
389
|
stdout,
|
|
265
390
|
stderr: stderr || `failed to spawn ${cmd}`,
|
|
@@ -375,13 +500,13 @@ var init_jest = __esm({
|
|
|
375
500
|
init_base();
|
|
376
501
|
jestRunner = {
|
|
377
502
|
id: "jest",
|
|
378
|
-
toCheck(project, _cap) {
|
|
503
|
+
toCheck(project, _cap, opts) {
|
|
379
504
|
return {
|
|
380
505
|
id: "unit",
|
|
381
506
|
title: "Unit tests",
|
|
382
507
|
runner: "jest",
|
|
383
508
|
cmd: localBin(project.root, "jest"),
|
|
384
|
-
args: ["--ci"]
|
|
509
|
+
args: ["--ci", ...opts?.targetFiles ?? []]
|
|
385
510
|
};
|
|
386
511
|
},
|
|
387
512
|
run(check, ctx) {
|
|
@@ -459,13 +584,14 @@ var init_vitest = __esm({
|
|
|
459
584
|
init_base();
|
|
460
585
|
vitestRunner = {
|
|
461
586
|
id: "vitest",
|
|
462
|
-
toCheck(project, _cap) {
|
|
587
|
+
toCheck(project, _cap, opts) {
|
|
588
|
+
const files = opts?.targetFiles ?? [];
|
|
463
589
|
return {
|
|
464
590
|
id: "unit",
|
|
465
591
|
title: "Unit tests",
|
|
466
592
|
runner: "vitest",
|
|
467
593
|
cmd: localBin(project.root, "vitest"),
|
|
468
|
-
args: ["run", "--reporter=json"]
|
|
594
|
+
args: ["run", "--reporter=json", ...files]
|
|
469
595
|
};
|
|
470
596
|
},
|
|
471
597
|
run(check, ctx) {
|
|
@@ -544,7 +670,7 @@ var init_verdict = __esm({
|
|
|
544
670
|
});
|
|
545
671
|
|
|
546
672
|
// src/core/orchestrator.ts
|
|
547
|
-
async function runChecks(project, ids, root) {
|
|
673
|
+
async function runChecks(project, ids, root, opts = {}) {
|
|
548
674
|
const known = new Set(project.capabilities.map((c) => c.id));
|
|
549
675
|
const unknown = ids.filter((id2) => !known.has(id2));
|
|
550
676
|
if (unknown.length > 0) {
|
|
@@ -565,7 +691,9 @@ async function runChecks(project, ids, root) {
|
|
|
565
691
|
summary
|
|
566
692
|
};
|
|
567
693
|
}
|
|
568
|
-
const check = runner.toCheck(project, cap
|
|
694
|
+
const check = runner.toCheck(project, cap, {
|
|
695
|
+
targetFiles: opts.targetFiles?.[capId]
|
|
696
|
+
});
|
|
569
697
|
return runner.run(check, ctx);
|
|
570
698
|
});
|
|
571
699
|
const results = await Promise.all(tasks);
|
|
@@ -608,18 +736,18 @@ function glyph(status, plain) {
|
|
|
608
736
|
function secs(ms) {
|
|
609
737
|
return ms === 0 ? "" : `${(ms / 1e3).toFixed(1)}s`;
|
|
610
738
|
}
|
|
611
|
-
function renderRun(run) {
|
|
739
|
+
function renderRun(run, record) {
|
|
612
740
|
const plain = isPlain();
|
|
613
741
|
const bold = (s) => plain ? s : pc2.bold(s);
|
|
614
742
|
const dim = (s) => plain ? s : pc2.dim(s);
|
|
615
743
|
const lines = [];
|
|
616
744
|
const scoped = run.scope?.kind;
|
|
617
745
|
if (scoped) {
|
|
618
|
-
lines.push(bold(`
|
|
746
|
+
lines.push(bold(`VerisKit \u2014 ${scoped}`));
|
|
619
747
|
lines.push("");
|
|
620
748
|
lines.push(`Scope ${run.scope?.changedCount ?? 0} changed file(s)`);
|
|
621
749
|
} else {
|
|
622
|
-
lines.push(bold("
|
|
750
|
+
lines.push(bold("VerisKit"));
|
|
623
751
|
lines.push("");
|
|
624
752
|
lines.push(
|
|
625
753
|
`Project ${run.project.root.split("/").pop() ?? run.project.root}`
|
|
@@ -643,6 +771,13 @@ function renderRun(run) {
|
|
|
643
771
|
const label = run.verdict.state === "verified" ? scoped ? "\u2713 Affected checks passed" : "\u2713 Verified" : run.verdict.state === "failed" ? scoped ? "\u2717 Affected checks failed" : "\u2717 Failed" : scoped ? "\u25B2 Affected: partial" : "\u25B2 Partial";
|
|
644
772
|
const color = run.verdict.state === "verified" ? pc2.green : run.verdict.state === "failed" ? pc2.red : pc2.yellow;
|
|
645
773
|
lines.push(` ${plain ? label : color(label)}`);
|
|
774
|
+
if (record?.git) {
|
|
775
|
+
const g = record.git;
|
|
776
|
+
lines.push("");
|
|
777
|
+
lines.push(
|
|
778
|
+
`Commit ${g.commit.slice(0, 7)} ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
646
781
|
if (run.reportRef) {
|
|
647
782
|
lines.push("");
|
|
648
783
|
lines.push("Report");
|
|
@@ -698,7 +833,7 @@ async function runInit(root) {
|
|
|
698
833
|
await writeIfAbsent(join4(dir, ".gitignore"), GITIGNORE);
|
|
699
834
|
process.stdout.write(
|
|
700
835
|
wroteConfig ? `Veris initialized. Detected checks: ${defaultChecks.join(", ") || "none"}.
|
|
701
|
-
` : "
|
|
836
|
+
` : "VerisKit already initialized (.veris/config.json exists). Nothing overwritten.\n"
|
|
702
837
|
);
|
|
703
838
|
return 0;
|
|
704
839
|
}
|
|
@@ -708,7 +843,14 @@ var init_init = __esm({
|
|
|
708
843
|
"use strict";
|
|
709
844
|
init_detect();
|
|
710
845
|
init_fs_safe();
|
|
711
|
-
GITIGNORE = [
|
|
846
|
+
GITIGNORE = [
|
|
847
|
+
"runs/",
|
|
848
|
+
"reports/",
|
|
849
|
+
"cache/",
|
|
850
|
+
"graph.json",
|
|
851
|
+
"evidence/",
|
|
852
|
+
""
|
|
853
|
+
].join("\n");
|
|
712
854
|
}
|
|
713
855
|
});
|
|
714
856
|
|
|
@@ -724,15 +866,60 @@ var init_load = __esm({
|
|
|
724
866
|
}
|
|
725
867
|
});
|
|
726
868
|
|
|
869
|
+
// src/git/changes.ts
|
|
870
|
+
function collect(set, out) {
|
|
871
|
+
for (const line of out.split("\n")) {
|
|
872
|
+
const f = line.trim();
|
|
873
|
+
if (f) set.add(f);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
async function changedFiles(root, opts = {}) {
|
|
877
|
+
const base = opts.base ?? null;
|
|
878
|
+
const files = /* @__PURE__ */ new Set();
|
|
879
|
+
const diffArgs = base ? ["diff", "--name-only", base] : ["diff", "--name-only", "HEAD"];
|
|
880
|
+
const diff = await exec("git", diffArgs, { cwd: root });
|
|
881
|
+
if (diff.code === 0) collect(files, diff.stdout);
|
|
882
|
+
const untracked = await exec(
|
|
883
|
+
"git",
|
|
884
|
+
["ls-files", "--others", "--exclude-standard"],
|
|
885
|
+
{ cwd: root }
|
|
886
|
+
);
|
|
887
|
+
if (untracked.code === 0) collect(files, untracked.stdout);
|
|
888
|
+
return { files: [...files].sort(), base };
|
|
889
|
+
}
|
|
890
|
+
async function gitAnchor(root) {
|
|
891
|
+
const head = await exec("git", ["rev-parse", "HEAD"], { cwd: root });
|
|
892
|
+
if (head.code !== 0) return null;
|
|
893
|
+
const commit = head.stdout.trim();
|
|
894
|
+
const branchRes = await exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
895
|
+
cwd: root
|
|
896
|
+
});
|
|
897
|
+
const branch = branchRes.code === 0 ? branchRes.stdout.trim() : "HEAD";
|
|
898
|
+
const status = await exec("git", ["status", "--porcelain"], { cwd: root });
|
|
899
|
+
const lines = status.code === 0 ? status.stdout.split("\n").map((l) => l.trim()).filter(Boolean) : [];
|
|
900
|
+
return {
|
|
901
|
+
commit,
|
|
902
|
+
branch,
|
|
903
|
+
dirty: lines.length > 0,
|
|
904
|
+
changedFiles: lines.length
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
var init_changes = __esm({
|
|
908
|
+
"src/git/changes.ts"() {
|
|
909
|
+
"use strict";
|
|
910
|
+
init_exec();
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
|
|
727
914
|
// src/reporters/markdown.ts
|
|
728
915
|
import { relative } from "path";
|
|
729
916
|
function cell(value) {
|
|
730
917
|
return value.replace(/\|/g, "\\|");
|
|
731
918
|
}
|
|
732
|
-
function renderMarkdown(run) {
|
|
919
|
+
function renderMarkdown(run, record) {
|
|
733
920
|
const root = run.project.root;
|
|
734
921
|
const lines = [];
|
|
735
|
-
lines.push("#
|
|
922
|
+
lines.push("# VerisKit Verification Report");
|
|
736
923
|
if (run.scope?.kind) {
|
|
737
924
|
lines.push("");
|
|
738
925
|
lines.push(
|
|
@@ -745,6 +932,13 @@ function renderMarkdown(run) {
|
|
|
745
932
|
lines.push(
|
|
746
933
|
`**Node:** ${run.env.node} \xB7 **OS:** ${run.env.os} \xB7 **PM:** ${run.env.pm} \xB7 **CI:** ${run.env.ci}`
|
|
747
934
|
);
|
|
935
|
+
if (record?.git) {
|
|
936
|
+
const g = record.git;
|
|
937
|
+
const tree = g.dirty ? `tree dirty, ${g.changedFiles} uncommitted file(s)` : "tree clean";
|
|
938
|
+
lines.push(`**Commit:** ${g.commit.slice(0, 7)} (${g.branch}) \xB7 ${tree}`);
|
|
939
|
+
} else if (record) {
|
|
940
|
+
lines.push("**Commit:** no git anchor available");
|
|
941
|
+
}
|
|
748
942
|
lines.push("");
|
|
749
943
|
lines.push("## Checks");
|
|
750
944
|
lines.push("");
|
|
@@ -776,9 +970,16 @@ function renderMarkdown(run) {
|
|
|
776
970
|
lines.push("```");
|
|
777
971
|
}
|
|
778
972
|
}
|
|
973
|
+
if (record) {
|
|
974
|
+
lines.push("");
|
|
975
|
+
lines.push(`**Evidence digest:** \`${record.digest}\``);
|
|
976
|
+
lines.push(
|
|
977
|
+
"_Integrity digest over the canonical record. Detects edits and corruption; it is not forgery-proof on its own. Publish the digest separately or sign it to prove authorship._"
|
|
978
|
+
);
|
|
979
|
+
}
|
|
779
980
|
lines.push("");
|
|
780
981
|
lines.push(
|
|
781
|
-
"_Generated by
|
|
982
|
+
"_Generated by VerisKit. A `Partial` verdict means one or more capabilities did not run \u2014 it is not a pass._"
|
|
782
983
|
);
|
|
783
984
|
lines.push("");
|
|
784
985
|
return lines.join("\n");
|
|
@@ -805,11 +1006,18 @@ async function runVerify(root, opts = {}) {
|
|
|
805
1006
|
const config = await loadConfig(root);
|
|
806
1007
|
const checks = config?.checks?.length ? config.checks : DEFAULT_CHECKS;
|
|
807
1008
|
const run = await runChecks(project, checks, root);
|
|
808
|
-
const
|
|
1009
|
+
const git = await gitAnchor(root);
|
|
1010
|
+
const logDigests = await digestLogs(run);
|
|
1011
|
+
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1012
|
+
const reportRef = await writeReport(
|
|
1013
|
+
root,
|
|
1014
|
+
run.id,
|
|
1015
|
+
renderMarkdown(run, record)
|
|
1016
|
+
);
|
|
809
1017
|
run.reportRef = reportRef;
|
|
810
1018
|
const runDir = await createRunDir(root, run.id);
|
|
811
|
-
await
|
|
812
|
-
process.stdout.write(`${renderRun(run)}
|
|
1019
|
+
await writeEvidence(runDir, record);
|
|
1020
|
+
process.stdout.write(`${renderRun(run, record)}
|
|
813
1021
|
`);
|
|
814
1022
|
return verdictExitCode(run.verdict, opts);
|
|
815
1023
|
}
|
|
@@ -821,9 +1029,12 @@ var init_verify = __esm({
|
|
|
821
1029
|
init_load();
|
|
822
1030
|
init_orchestrator();
|
|
823
1031
|
init_verdict();
|
|
1032
|
+
init_record();
|
|
824
1033
|
init_store();
|
|
1034
|
+
init_changes();
|
|
825
1035
|
init_markdown();
|
|
826
1036
|
init_terminal();
|
|
1037
|
+
init_version();
|
|
827
1038
|
DEFAULT_CHECKS = ["types", "lint", "unit"];
|
|
828
1039
|
}
|
|
829
1040
|
});
|
|
@@ -833,13 +1044,13 @@ var report_exports = {};
|
|
|
833
1044
|
__export(report_exports, {
|
|
834
1045
|
runReport: () => runReport
|
|
835
1046
|
});
|
|
836
|
-
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
1047
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
837
1048
|
import { join as join6 } from "path";
|
|
838
1049
|
async function runReport(root) {
|
|
839
1050
|
const dir = join6(root, ".veris", "reports");
|
|
840
1051
|
let files;
|
|
841
1052
|
try {
|
|
842
|
-
files =
|
|
1053
|
+
files = readdirSync2(dir).filter((f) => f.endsWith(".md")).sort();
|
|
843
1054
|
} catch {
|
|
844
1055
|
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
845
1056
|
return 0;
|
|
@@ -912,31 +1123,335 @@ var init_gate = __esm({
|
|
|
912
1123
|
}
|
|
913
1124
|
});
|
|
914
1125
|
|
|
915
|
-
// src/
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1126
|
+
// src/project-graph/discover.ts
|
|
1127
|
+
import { readdirSync as readdirSync3, statSync } from "fs";
|
|
1128
|
+
import { join as join7, relative as relative2, sep } from "path";
|
|
1129
|
+
function toPosix(p) {
|
|
1130
|
+
return sep === "/" ? p : p.split(sep).join("/");
|
|
1131
|
+
}
|
|
1132
|
+
function classify(rel) {
|
|
1133
|
+
if (TEST_RE2.test(rel)) return "test";
|
|
1134
|
+
if (CONFIG_RE2.test(rel)) return "config";
|
|
1135
|
+
return "source";
|
|
1136
|
+
}
|
|
1137
|
+
function discoverFiles(root) {
|
|
1138
|
+
const out = [];
|
|
1139
|
+
const walk = (dir) => {
|
|
1140
|
+
let entries;
|
|
1141
|
+
try {
|
|
1142
|
+
entries = readdirSync3(dir);
|
|
1143
|
+
} catch {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
for (const name of entries) {
|
|
1147
|
+
const abs = join7(dir, name);
|
|
1148
|
+
const rel = toPosix(relative2(root, abs));
|
|
1149
|
+
if (IGNORE.test(rel)) continue;
|
|
1150
|
+
let st;
|
|
1151
|
+
try {
|
|
1152
|
+
st = statSync(abs);
|
|
1153
|
+
} catch {
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (st.isDirectory()) {
|
|
1157
|
+
walk(abs);
|
|
1158
|
+
} else if (CODE_RE.test(rel)) {
|
|
1159
|
+
out.push({ file: rel, kind: classify(rel) });
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
walk(root);
|
|
1164
|
+
return out.sort((a, b) => a.file.localeCompare(b.file));
|
|
1165
|
+
}
|
|
1166
|
+
var IGNORE, CODE_RE, TEST_RE2, CONFIG_RE2;
|
|
1167
|
+
var init_discover = __esm({
|
|
1168
|
+
"src/project-graph/discover.ts"() {
|
|
1169
|
+
"use strict";
|
|
1170
|
+
IGNORE = /(^|\/)(\.git|\.claude|\.veris|\.agentloop|\.agentflight|node_modules|dist|coverage|build|fixtures|__fixtures__)(\/|$)/;
|
|
1171
|
+
CODE_RE = /\.[cm]?[jt]sx?$/;
|
|
1172
|
+
TEST_RE2 = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|tests|__tests__)\//;
|
|
1173
|
+
CONFIG_RE2 = /(^|\/)([^/]+\.config\.[cm]?[jt]sx?)$/;
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
// src/project-graph/scanner-resolver.ts
|
|
1178
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
1179
|
+
import { dirname, join as join8, relative as relative3, resolve } from "path";
|
|
1180
|
+
function extractSpecifiers(text) {
|
|
1181
|
+
const specs = [];
|
|
1182
|
+
SPEC_RE.lastIndex = 0;
|
|
1183
|
+
let m;
|
|
1184
|
+
m = SPEC_RE.exec(text);
|
|
1185
|
+
while (m !== null) {
|
|
1186
|
+
const s = m[1] ?? m[2] ?? m[3] ?? m[4];
|
|
1187
|
+
if (s) specs.push(s);
|
|
1188
|
+
m = SPEC_RE.exec(text);
|
|
920
1189
|
}
|
|
1190
|
+
return specs;
|
|
921
1191
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const untracked = await exec(
|
|
929
|
-
"git",
|
|
930
|
-
["ls-files", "--others", "--exclude-standard"],
|
|
931
|
-
{ cwd: root }
|
|
932
|
-
);
|
|
933
|
-
if (untracked.code === 0) collect(files, untracked.stdout);
|
|
934
|
-
return { files: [...files].sort(), base };
|
|
1192
|
+
function isFile(p) {
|
|
1193
|
+
try {
|
|
1194
|
+
return statSync2(p).isFile();
|
|
1195
|
+
} catch {
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
935
1198
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1199
|
+
function resolveRelative(fromDir, spec) {
|
|
1200
|
+
const base = resolve(fromDir, spec);
|
|
1201
|
+
const candidates = [base];
|
|
1202
|
+
for (const ext of EXTS) candidates.push(base + ext);
|
|
1203
|
+
const extMatch = base.match(/\.[cm]?[jt]sx?$/);
|
|
1204
|
+
if (extMatch) {
|
|
1205
|
+
const noExt = base.slice(0, -extMatch[0].length);
|
|
1206
|
+
for (const ext of EXTS) candidates.push(noExt + ext);
|
|
1207
|
+
}
|
|
1208
|
+
for (const ext of EXTS) candidates.push(join8(base, `index${ext}`));
|
|
1209
|
+
for (const c of candidates) {
|
|
1210
|
+
if (existsSync3(c) && isFile(c)) return c;
|
|
1211
|
+
}
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
function scannerImports(root, file) {
|
|
1215
|
+
let text;
|
|
1216
|
+
try {
|
|
1217
|
+
text = readFileSync3(join8(root, file), "utf8");
|
|
1218
|
+
} catch {
|
|
1219
|
+
return [];
|
|
1220
|
+
}
|
|
1221
|
+
const dir = dirname(join8(root, file));
|
|
1222
|
+
const out = /* @__PURE__ */ new Set();
|
|
1223
|
+
for (const spec of extractSpecifiers(text)) {
|
|
1224
|
+
if (!spec.startsWith(".")) continue;
|
|
1225
|
+
const resolved = resolveRelative(dir, spec);
|
|
1226
|
+
if (resolved) out.add(toPosix(relative3(root, resolved)));
|
|
1227
|
+
}
|
|
1228
|
+
return [...out];
|
|
1229
|
+
}
|
|
1230
|
+
var EXTS, SPEC_RE;
|
|
1231
|
+
var init_scanner_resolver = __esm({
|
|
1232
|
+
"src/project-graph/scanner-resolver.ts"() {
|
|
1233
|
+
"use strict";
|
|
1234
|
+
init_discover();
|
|
1235
|
+
EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1236
|
+
SPEC_RE = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]|(?:^|[^.\w])import\s*['"]([^'"]+)['"]|\bimport\(\s*['"]([^'"]+)['"]\s*\)|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
// src/project-graph/ts-resolver.ts
|
|
1241
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
1242
|
+
import { createRequire } from "module";
|
|
1243
|
+
import { join as join9, relative as relative4, sep as sep2 } from "path";
|
|
1244
|
+
function hasClassicApi(mod) {
|
|
1245
|
+
const m = mod;
|
|
1246
|
+
return !!m && typeof m.preProcessFile === "function" && typeof m.resolveModuleName === "function" && typeof m.readConfigFile === "function" && typeof m.parseJsonConfigFileContent === "function" && typeof m.sys === "object" && m.sys !== null;
|
|
1247
|
+
}
|
|
1248
|
+
function loadTypeScript(root) {
|
|
1249
|
+
try {
|
|
1250
|
+
const require2 = createRequire(join9(root, "__veris__.js"));
|
|
1251
|
+
const tsPath = require2.resolve("typescript");
|
|
1252
|
+
const mod = require2(tsPath);
|
|
1253
|
+
return hasClassicApi(mod) ? mod : null;
|
|
1254
|
+
} catch {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
function loadCompilerOptions(tsmod, root) {
|
|
1259
|
+
const configPath = join9(root, "tsconfig.json");
|
|
1260
|
+
const read = tsmod.readConfigFile(configPath, tsmod.sys.readFile);
|
|
1261
|
+
if (read.error || !read.config) return {};
|
|
1262
|
+
const parsed = tsmod.parseJsonConfigFileContent(read.config, tsmod.sys, root);
|
|
1263
|
+
return parsed.options;
|
|
1264
|
+
}
|
|
1265
|
+
function tsImports(tsmod, root, options, file) {
|
|
1266
|
+
let text;
|
|
1267
|
+
try {
|
|
1268
|
+
text = readFileSync4(join9(root, file), "utf8");
|
|
1269
|
+
} catch {
|
|
1270
|
+
return [];
|
|
1271
|
+
}
|
|
1272
|
+
const abs = join9(root, file);
|
|
1273
|
+
const out = /* @__PURE__ */ new Set();
|
|
1274
|
+
try {
|
|
1275
|
+
const pre = tsmod.preProcessFile(text, true, true);
|
|
1276
|
+
for (const imp of pre.importedFiles) {
|
|
1277
|
+
const res = tsmod.resolveModuleName(
|
|
1278
|
+
imp.fileName,
|
|
1279
|
+
abs,
|
|
1280
|
+
options,
|
|
1281
|
+
tsmod.sys
|
|
1282
|
+
);
|
|
1283
|
+
const resolved = res.resolvedModule?.resolvedFileName;
|
|
1284
|
+
if (resolved && !resolved.includes("node_modules") && resolved.startsWith(root + sep2)) {
|
|
1285
|
+
out.add(toPosix(relative4(root, resolved)));
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
return [...out];
|
|
1291
|
+
}
|
|
1292
|
+
function selectResolver(root) {
|
|
1293
|
+
const tsmod = existsSync4(join9(root, "tsconfig.json")) ? loadTypeScript(root) : null;
|
|
1294
|
+
if (tsmod) {
|
|
1295
|
+
try {
|
|
1296
|
+
const options = loadCompilerOptions(tsmod, root);
|
|
1297
|
+
return {
|
|
1298
|
+
resolver: "typescript",
|
|
1299
|
+
importsOf: (file) => tsImports(tsmod, root, options, file)
|
|
1300
|
+
};
|
|
1301
|
+
} catch {
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
resolver: "scanner",
|
|
1306
|
+
importsOf: (file) => scannerImports(root, file)
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
var init_ts_resolver = __esm({
|
|
1310
|
+
"src/project-graph/ts-resolver.ts"() {
|
|
1311
|
+
"use strict";
|
|
1312
|
+
init_discover();
|
|
1313
|
+
init_scanner_resolver();
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
// src/project-graph/graph.ts
|
|
1318
|
+
var graph_exports = {};
|
|
1319
|
+
__export(graph_exports, {
|
|
1320
|
+
buildGraph: () => buildGraph
|
|
1321
|
+
});
|
|
1322
|
+
async function buildGraph(project) {
|
|
1323
|
+
const root = project.root;
|
|
1324
|
+
const files = discoverFiles(root);
|
|
1325
|
+
const known = new Set(files.map((f) => f.file));
|
|
1326
|
+
const { resolver, importsOf } = selectResolver(root);
|
|
1327
|
+
const nodes = {};
|
|
1328
|
+
for (const f of files) {
|
|
1329
|
+
nodes[f.file] = { file: f.file, kind: f.kind, imports: [], importedBy: [] };
|
|
1330
|
+
}
|
|
1331
|
+
for (const f of files) {
|
|
1332
|
+
const imps = importsOf(f.file).filter((i) => known.has(i) && i !== f.file);
|
|
1333
|
+
nodes[f.file].imports = imps;
|
|
1334
|
+
for (const i of imps) nodes[i]?.importedBy.push(f.file);
|
|
1335
|
+
}
|
|
1336
|
+
return {
|
|
1337
|
+
root,
|
|
1338
|
+
resolver,
|
|
1339
|
+
nodes,
|
|
1340
|
+
sourceFiles: files.filter((f) => f.kind === "source").map((f) => f.file),
|
|
1341
|
+
testFiles: files.filter((f) => f.kind === "test").map((f) => f.file)
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
var init_graph = __esm({
|
|
1345
|
+
"src/project-graph/graph.ts"() {
|
|
1346
|
+
"use strict";
|
|
1347
|
+
init_discover();
|
|
1348
|
+
init_ts_resolver();
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
// src/project-graph/analyze.ts
|
|
1353
|
+
function transitiveDependents(graph, file) {
|
|
1354
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1355
|
+
const stack = [...graph.nodes[file]?.importedBy ?? []];
|
|
1356
|
+
while (stack.length) {
|
|
1357
|
+
const f = stack.pop();
|
|
1358
|
+
if (!f || seen.has(f)) continue;
|
|
1359
|
+
seen.add(f);
|
|
1360
|
+
for (const d of graph.nodes[f]?.importedBy ?? []) stack.push(d);
|
|
1361
|
+
}
|
|
1362
|
+
return seen;
|
|
1363
|
+
}
|
|
1364
|
+
function reachableFromTests(graph) {
|
|
1365
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1366
|
+
const stack = [...graph.testFiles];
|
|
1367
|
+
while (stack.length) {
|
|
1368
|
+
const f = stack.pop();
|
|
1369
|
+
if (!f || seen.has(f)) continue;
|
|
1370
|
+
seen.add(f);
|
|
1371
|
+
for (const i of graph.nodes[f]?.imports ?? []) stack.push(i);
|
|
1372
|
+
}
|
|
1373
|
+
return seen;
|
|
1374
|
+
}
|
|
1375
|
+
function analyze(graph, changed = []) {
|
|
1376
|
+
const blastRadius = {};
|
|
1377
|
+
for (const file of Object.keys(graph.nodes)) {
|
|
1378
|
+
blastRadius[file] = transitiveDependents(graph, file).size;
|
|
1379
|
+
}
|
|
1380
|
+
const reached = reachableFromTests(graph);
|
|
1381
|
+
const byBlast = (a, b) => (blastRadius[b] ?? 0) - (blastRadius[a] ?? 0);
|
|
1382
|
+
const untested = graph.sourceFiles.filter((f) => !reached.has(f)).sort(byBlast);
|
|
1383
|
+
const changedSet = new Set(changed);
|
|
1384
|
+
const untestedSet = new Set(untested);
|
|
1385
|
+
const risky = graph.sourceFiles.filter(
|
|
1386
|
+
(f) => (blastRadius[f] ?? 0) > 0 && (untestedSet.has(f) || changedSet.has(f))
|
|
1387
|
+
).sort(byBlast);
|
|
1388
|
+
return { untested, blastRadius, risky };
|
|
1389
|
+
}
|
|
1390
|
+
var init_analyze = __esm({
|
|
1391
|
+
"src/project-graph/analyze.ts"() {
|
|
938
1392
|
"use strict";
|
|
939
|
-
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
// src/affected/select.ts
|
|
1397
|
+
var select_exports = {};
|
|
1398
|
+
__export(select_exports, {
|
|
1399
|
+
selectAffectedTests: () => selectAffectedTests
|
|
1400
|
+
});
|
|
1401
|
+
function selectAffectedTests(graph, changed) {
|
|
1402
|
+
if (graph.resolver !== "typescript") {
|
|
1403
|
+
return {
|
|
1404
|
+
mode: "full",
|
|
1405
|
+
testFiles: [],
|
|
1406
|
+
reason: "scanner graph can miss aliased/subpath imports \u2014 running the full suite"
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
for (const f of changed) {
|
|
1410
|
+
if (GLOBAL_RE.test(f)) {
|
|
1411
|
+
return {
|
|
1412
|
+
mode: "full",
|
|
1413
|
+
testFiles: [],
|
|
1414
|
+
reason: `global/config change (${f})`
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
if (!(f in graph.nodes)) {
|
|
1418
|
+
return {
|
|
1419
|
+
mode: "full",
|
|
1420
|
+
testFiles: [],
|
|
1421
|
+
reason: `unresolved changed file (${f})`
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
const tests = /* @__PURE__ */ new Set();
|
|
1426
|
+
for (const f of changed) {
|
|
1427
|
+
if (graph.nodes[f]?.kind === "test") tests.add(f);
|
|
1428
|
+
for (const dep of transitiveDependents(graph, f)) {
|
|
1429
|
+
if (graph.nodes[dep]?.kind === "test") tests.add(dep);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (tests.size === 0) {
|
|
1433
|
+
return {
|
|
1434
|
+
mode: "full",
|
|
1435
|
+
testFiles: [],
|
|
1436
|
+
reason: "no tests reach the changed files"
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
const selected = [...tests].sort();
|
|
1440
|
+
if (selected.some((t) => t.startsWith("-"))) {
|
|
1441
|
+
return {
|
|
1442
|
+
mode: "full",
|
|
1443
|
+
testFiles: [],
|
|
1444
|
+
reason: "a reaching test path starts with '-' (unsafe as a CLI argument)"
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
return { mode: "graph", testFiles: selected, reason: "" };
|
|
1448
|
+
}
|
|
1449
|
+
var GLOBAL_RE;
|
|
1450
|
+
var init_select = __esm({
|
|
1451
|
+
"src/affected/select.ts"() {
|
|
1452
|
+
"use strict";
|
|
1453
|
+
init_analyze();
|
|
1454
|
+
GLOBAL_RE = /(^|\/)(tsconfig[^/]*\.json|package\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|veris\.config\.[^/]+|[^/]+\.config\.[cm]?[jt]sx?|[^/]+\.setup\.[cm]?[jt]sx?)$/;
|
|
940
1455
|
}
|
|
941
1456
|
});
|
|
942
1457
|
|
|
@@ -956,7 +1471,25 @@ async function runAffected(root, opts = {}) {
|
|
|
956
1471
|
);
|
|
957
1472
|
return 0;
|
|
958
1473
|
}
|
|
959
|
-
|
|
1474
|
+
let targetFiles;
|
|
1475
|
+
let narrowedNote = "";
|
|
1476
|
+
const unitRunner = project.capabilities.find((c) => c.id === "unit")?.runner;
|
|
1477
|
+
const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
|
|
1478
|
+
if (plan.checks.includes("unit") && canNarrow) {
|
|
1479
|
+
const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
|
|
1480
|
+
const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
|
|
1481
|
+
const graph = await buildGraph2(project);
|
|
1482
|
+
const sel = selectAffectedTests2(graph, files);
|
|
1483
|
+
if (sel.mode === "graph") {
|
|
1484
|
+
targetFiles = { unit: sel.testFiles };
|
|
1485
|
+
narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
|
|
1486
|
+
} else {
|
|
1487
|
+
narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
|
|
1488
|
+
}
|
|
1489
|
+
} else if (plan.checks.includes("unit") && unitRunner) {
|
|
1490
|
+
narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
|
|
1491
|
+
}
|
|
1492
|
+
const run = await runChecks(project, plan.checks, root, { targetFiles });
|
|
960
1493
|
run.scope = { kind: "affected", changedCount: files.length };
|
|
961
1494
|
const affected = new Set(plan.checks);
|
|
962
1495
|
for (const cap of project.capabilities) {
|
|
@@ -969,11 +1502,20 @@ async function runAffected(root, opts = {}) {
|
|
|
969
1502
|
});
|
|
970
1503
|
}
|
|
971
1504
|
}
|
|
972
|
-
const
|
|
1505
|
+
const git = await gitAnchor(root);
|
|
1506
|
+
const logDigests = await digestLogs(run);
|
|
1507
|
+
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1508
|
+
const reportRef = await writeReport(
|
|
1509
|
+
root,
|
|
1510
|
+
run.id,
|
|
1511
|
+
renderMarkdown(run, record)
|
|
1512
|
+
);
|
|
973
1513
|
run.reportRef = reportRef;
|
|
974
1514
|
const runDir = await createRunDir(root, run.id);
|
|
975
|
-
await
|
|
976
|
-
process.stdout.write(`${renderRun(run)}
|
|
1515
|
+
await writeEvidence(runDir, record);
|
|
1516
|
+
process.stdout.write(`${renderRun(run, record)}
|
|
1517
|
+
`);
|
|
1518
|
+
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
977
1519
|
`);
|
|
978
1520
|
return verdictExitCode(run.verdict, opts);
|
|
979
1521
|
}
|
|
@@ -984,23 +1526,25 @@ var init_affected = __esm({
|
|
|
984
1526
|
init_detect();
|
|
985
1527
|
init_orchestrator();
|
|
986
1528
|
init_verdict();
|
|
1529
|
+
init_record();
|
|
987
1530
|
init_store();
|
|
988
1531
|
init_changes();
|
|
989
1532
|
init_markdown();
|
|
990
1533
|
init_terminal();
|
|
1534
|
+
init_version();
|
|
991
1535
|
}
|
|
992
1536
|
});
|
|
993
1537
|
|
|
994
1538
|
// src/watch/watcher.ts
|
|
995
1539
|
import {
|
|
996
1540
|
watch as fsWatch,
|
|
997
|
-
readdirSync as
|
|
998
|
-
statSync
|
|
1541
|
+
readdirSync as readdirSync4,
|
|
1542
|
+
statSync as statSync3
|
|
999
1543
|
} from "fs";
|
|
1000
|
-
import { basename, join as
|
|
1544
|
+
import { basename as basename2, join as join10, relative as relative5 } from "path";
|
|
1001
1545
|
function watch(root, opts, onBatch) {
|
|
1002
1546
|
const debounceMs = opts.debounceMs ?? 150;
|
|
1003
|
-
const rootBase =
|
|
1547
|
+
const rootBase = basename2(root);
|
|
1004
1548
|
let pending = /* @__PURE__ */ new Set();
|
|
1005
1549
|
let timer = null;
|
|
1006
1550
|
const flush = () => {
|
|
@@ -1010,7 +1554,7 @@ function watch(root, opts, onBatch) {
|
|
|
1010
1554
|
if (batch.length) onBatch(batch);
|
|
1011
1555
|
};
|
|
1012
1556
|
const schedule = (rel) => {
|
|
1013
|
-
if (!rel || rel === rootBase ||
|
|
1557
|
+
if (!rel || rel === rootBase || IGNORE2.test(rel)) return;
|
|
1014
1558
|
pending.add(rel);
|
|
1015
1559
|
if (timer) clearTimeout(timer);
|
|
1016
1560
|
timer = setTimeout(flush, debounceMs);
|
|
@@ -1051,17 +1595,17 @@ function scanMtimes(root) {
|
|
|
1051
1595
|
const walk = (dir) => {
|
|
1052
1596
|
let entries;
|
|
1053
1597
|
try {
|
|
1054
|
-
entries =
|
|
1598
|
+
entries = readdirSync4(dir);
|
|
1055
1599
|
} catch {
|
|
1056
1600
|
return;
|
|
1057
1601
|
}
|
|
1058
1602
|
for (const name of entries) {
|
|
1059
|
-
const abs =
|
|
1060
|
-
const rel =
|
|
1061
|
-
if (
|
|
1603
|
+
const abs = join10(dir, name);
|
|
1604
|
+
const rel = relative5(root, abs);
|
|
1605
|
+
if (IGNORE2.test(rel)) continue;
|
|
1062
1606
|
let st;
|
|
1063
1607
|
try {
|
|
1064
|
-
st =
|
|
1608
|
+
st = statSync3(abs);
|
|
1065
1609
|
} catch {
|
|
1066
1610
|
continue;
|
|
1067
1611
|
}
|
|
@@ -1072,11 +1616,11 @@ function scanMtimes(root) {
|
|
|
1072
1616
|
walk(root);
|
|
1073
1617
|
return out;
|
|
1074
1618
|
}
|
|
1075
|
-
var
|
|
1619
|
+
var IGNORE2;
|
|
1076
1620
|
var init_watcher = __esm({
|
|
1077
1621
|
"src/watch/watcher.ts"() {
|
|
1078
1622
|
"use strict";
|
|
1079
|
-
|
|
1623
|
+
IGNORE2 = /(^|\/)(\.git|\.veris|\.agentloop|\.agentflight|node_modules|dist)(\/|$)/;
|
|
1080
1624
|
}
|
|
1081
1625
|
});
|
|
1082
1626
|
|
|
@@ -1122,9 +1666,29 @@ async function runWatch(root, opts = {}) {
|
|
|
1122
1666
|
const project = await detectProject(root);
|
|
1123
1667
|
const { files } = await changedFiles(root);
|
|
1124
1668
|
const checks = initial ? availableIds(project) : affectedChecks(files, project).checks;
|
|
1669
|
+
let targetFiles;
|
|
1670
|
+
let narrowedNote = "";
|
|
1671
|
+
const unitRunner = project.capabilities.find(
|
|
1672
|
+
(c) => c.id === "unit"
|
|
1673
|
+
)?.runner;
|
|
1674
|
+
const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
|
|
1675
|
+
if (!initial && checks.includes("unit") && canNarrow) {
|
|
1676
|
+
const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
|
|
1677
|
+
const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
|
|
1678
|
+
const graph = await buildGraph2(project);
|
|
1679
|
+
const sel = selectAffectedTests2(graph, files);
|
|
1680
|
+
if (sel.mode === "graph") {
|
|
1681
|
+
targetFiles = { unit: sel.testFiles };
|
|
1682
|
+
narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
|
|
1683
|
+
} else {
|
|
1684
|
+
narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
|
|
1685
|
+
}
|
|
1686
|
+
} else if (!initial && checks.includes("unit") && unitRunner) {
|
|
1687
|
+
narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
|
|
1688
|
+
}
|
|
1125
1689
|
let fresh = [];
|
|
1126
1690
|
if (checks.length) {
|
|
1127
|
-
const r = await runChecks(project, checks, root);
|
|
1691
|
+
const r = await runChecks(project, checks, root, { targetFiles });
|
|
1128
1692
|
fresh = r.results;
|
|
1129
1693
|
for (const result of fresh) cache.set(result.checkId, { ...result });
|
|
1130
1694
|
}
|
|
@@ -1149,6 +1713,8 @@ async function runWatch(root, opts = {}) {
|
|
|
1149
1713
|
scope: { kind: "watch", changedCount: files.length }
|
|
1150
1714
|
};
|
|
1151
1715
|
process.stdout.write(`${renderRun(run)}
|
|
1716
|
+
`);
|
|
1717
|
+
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1152
1718
|
`);
|
|
1153
1719
|
} finally {
|
|
1154
1720
|
running = false;
|
|
@@ -1156,7 +1722,7 @@ async function runWatch(root, opts = {}) {
|
|
|
1156
1722
|
};
|
|
1157
1723
|
process.stdout.write("veris watch \u2014 press Ctrl-C to stop\n");
|
|
1158
1724
|
await tick(true);
|
|
1159
|
-
return await new Promise((
|
|
1725
|
+
return await new Promise((resolve2) => {
|
|
1160
1726
|
let stop;
|
|
1161
1727
|
try {
|
|
1162
1728
|
stop = watch(root, { poll: opts.poll }, () => {
|
|
@@ -1167,13 +1733,13 @@ async function runWatch(root, opts = {}) {
|
|
|
1167
1733
|
`veris: ${err instanceof Error ? err.message : String(err)}
|
|
1168
1734
|
`
|
|
1169
1735
|
);
|
|
1170
|
-
|
|
1736
|
+
resolve2(1);
|
|
1171
1737
|
return;
|
|
1172
1738
|
}
|
|
1173
1739
|
process.on("SIGINT", () => {
|
|
1174
1740
|
stop();
|
|
1175
1741
|
process.stdout.write("\nStopped.\n");
|
|
1176
|
-
|
|
1742
|
+
resolve2(0);
|
|
1177
1743
|
});
|
|
1178
1744
|
});
|
|
1179
1745
|
}
|
|
@@ -1190,21 +1756,364 @@ var init_watch = __esm({
|
|
|
1190
1756
|
}
|
|
1191
1757
|
});
|
|
1192
1758
|
|
|
1759
|
+
// src/cli/commands/scan.ts
|
|
1760
|
+
var scan_exports = {};
|
|
1761
|
+
__export(scan_exports, {
|
|
1762
|
+
renderScan: () => renderScan,
|
|
1763
|
+
runScan: () => runScan
|
|
1764
|
+
});
|
|
1765
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
1766
|
+
import { join as join11 } from "path";
|
|
1767
|
+
import pc3 from "picocolors";
|
|
1768
|
+
function renderScan(graph, analysis) {
|
|
1769
|
+
const plain = isPlain();
|
|
1770
|
+
const bold = (s) => plain ? s : pc3.bold(s);
|
|
1771
|
+
const dim = (s) => plain ? s : pc3.dim(s);
|
|
1772
|
+
const warn = (s) => plain ? s : pc3.yellow(s);
|
|
1773
|
+
const lines = [];
|
|
1774
|
+
lines.push(bold("VerisKit \u2014 scan"));
|
|
1775
|
+
lines.push("");
|
|
1776
|
+
lines.push(
|
|
1777
|
+
`Resolver ${graph.resolver}${graph.resolver === "scanner" ? dim(" (no TypeScript found \u2014 relative imports only)") : ""}`
|
|
1778
|
+
);
|
|
1779
|
+
lines.push(
|
|
1780
|
+
`Modules ${Object.keys(graph.nodes).length} \xB7 Source ${graph.sourceFiles.length} \xB7 Tests ${graph.testFiles.length}`
|
|
1781
|
+
);
|
|
1782
|
+
lines.push("");
|
|
1783
|
+
lines.push("Untested (top by impact)");
|
|
1784
|
+
const top = analysis.untested.slice(0, 10);
|
|
1785
|
+
if (top.length === 0)
|
|
1786
|
+
lines.push(dim(" none \u2014 every source file is reached by a test"));
|
|
1787
|
+
for (const f of top) {
|
|
1788
|
+
lines.push(
|
|
1789
|
+
` ${warn("!")} ${f} ${dim(`${analysis.blastRadius[f] ?? 0} dependents`)}`
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
return lines.join("\n");
|
|
1793
|
+
}
|
|
1794
|
+
async function runScan(root) {
|
|
1795
|
+
const project = await detectProject(root);
|
|
1796
|
+
const graph = await buildGraph(project);
|
|
1797
|
+
const analysis = analyze(graph);
|
|
1798
|
+
const dir = join11(root, ".veris");
|
|
1799
|
+
await ensureDir(dir);
|
|
1800
|
+
await writeFile3(
|
|
1801
|
+
join11(dir, "graph.json"),
|
|
1802
|
+
JSON.stringify({ resolver: graph.resolver, nodes: graph.nodes }, null, 2),
|
|
1803
|
+
"utf8"
|
|
1804
|
+
);
|
|
1805
|
+
process.stdout.write(`${renderScan(graph, analysis)}
|
|
1806
|
+
`);
|
|
1807
|
+
process.stdout.write(`
|
|
1808
|
+
Graph ${join11(".veris", "graph.json")}
|
|
1809
|
+
`);
|
|
1810
|
+
return 0;
|
|
1811
|
+
}
|
|
1812
|
+
var init_scan = __esm({
|
|
1813
|
+
"src/cli/commands/scan.ts"() {
|
|
1814
|
+
"use strict";
|
|
1815
|
+
init_detect();
|
|
1816
|
+
init_analyze();
|
|
1817
|
+
init_graph();
|
|
1818
|
+
init_fs_safe();
|
|
1819
|
+
init_tty();
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
// src/cli/commands/plan.ts
|
|
1824
|
+
var plan_exports = {};
|
|
1825
|
+
__export(plan_exports, {
|
|
1826
|
+
renderPlan: () => renderPlan,
|
|
1827
|
+
runPlan: () => runPlan
|
|
1828
|
+
});
|
|
1829
|
+
import pc4 from "picocolors";
|
|
1830
|
+
function renderPlan(project, graph, analysis, changed) {
|
|
1831
|
+
const plain = isPlain();
|
|
1832
|
+
const bold = (s) => plain ? s : pc4.bold(s);
|
|
1833
|
+
const dim = (s) => plain ? s : pc4.dim(s);
|
|
1834
|
+
const warn = (s) => plain ? s : pc4.yellow(s);
|
|
1835
|
+
const lines = [];
|
|
1836
|
+
lines.push(bold("VerisKit \u2014 plan"));
|
|
1837
|
+
lines.push(dim(`(graph via ${graph.resolver})`));
|
|
1838
|
+
lines.push("");
|
|
1839
|
+
lines.push("Test these first (high impact, untested)");
|
|
1840
|
+
const top = analysis.untested.slice(0, 5);
|
|
1841
|
+
if (top.length === 0)
|
|
1842
|
+
lines.push(
|
|
1843
|
+
dim(
|
|
1844
|
+
" none \u2014 untested files have no dependents or all files are covered"
|
|
1845
|
+
)
|
|
1846
|
+
);
|
|
1847
|
+
top.forEach((f, i) => {
|
|
1848
|
+
lines.push(
|
|
1849
|
+
` ${i + 1}. ${f} ${dim(`(${analysis.blastRadius[f] ?? 0} dependents, no test reaches it)`)}`
|
|
1850
|
+
);
|
|
1851
|
+
});
|
|
1852
|
+
lines.push("");
|
|
1853
|
+
lines.push("Verification setup");
|
|
1854
|
+
const weak = project.capabilities.filter(
|
|
1855
|
+
(c) => !c.available && c.id !== "browser"
|
|
1856
|
+
);
|
|
1857
|
+
if (weak.length === 0)
|
|
1858
|
+
lines.push(dim(" \u2713 types, lint, and unit are all configured"));
|
|
1859
|
+
for (const c of weak)
|
|
1860
|
+
lines.push(
|
|
1861
|
+
` ${warn("\u26A0")} ${c.id} capability unavailable \u2014 ${c.reason ?? "not configured"}`
|
|
1862
|
+
);
|
|
1863
|
+
if (changed.length) {
|
|
1864
|
+
lines.push("");
|
|
1865
|
+
lines.push(`Risky changes (${changed.length} changed file(s))`);
|
|
1866
|
+
if (analysis.risky.length === 0) lines.push(dim(" none flagged"));
|
|
1867
|
+
for (const f of analysis.risky.slice(0, 8)) {
|
|
1868
|
+
const untested = analysis.untested.includes(f) ? ", untested" : "";
|
|
1869
|
+
lines.push(
|
|
1870
|
+
` ${warn("!")} ${f} ${dim(`${analysis.blastRadius[f] ?? 0} dependents${untested}`)}`
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
lines.push("");
|
|
1875
|
+
lines.push(
|
|
1876
|
+
dim("Next: `veris affected` runs only the tests that reach your changes.")
|
|
1877
|
+
);
|
|
1878
|
+
return lines.join("\n");
|
|
1879
|
+
}
|
|
1880
|
+
async function runPlan(root, opts = {}) {
|
|
1881
|
+
const project = await detectProject(root);
|
|
1882
|
+
const graph = await buildGraph(project);
|
|
1883
|
+
const { files } = await changedFiles(root, { base: opts.base });
|
|
1884
|
+
const analysis = analyze(graph, files);
|
|
1885
|
+
process.stdout.write(`${renderPlan(project, graph, analysis, files)}
|
|
1886
|
+
`);
|
|
1887
|
+
return 0;
|
|
1888
|
+
}
|
|
1889
|
+
var init_plan = __esm({
|
|
1890
|
+
"src/cli/commands/plan.ts"() {
|
|
1891
|
+
"use strict";
|
|
1892
|
+
init_detect();
|
|
1893
|
+
init_changes();
|
|
1894
|
+
init_analyze();
|
|
1895
|
+
init_graph();
|
|
1896
|
+
init_tty();
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
// src/evidence/verify-evidence.ts
|
|
1901
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1902
|
+
function verifyRecord(record) {
|
|
1903
|
+
const recomputed = computeDigest(
|
|
1904
|
+
record
|
|
1905
|
+
);
|
|
1906
|
+
const ok = recomputed === record.digest;
|
|
1907
|
+
const checks = [
|
|
1908
|
+
{
|
|
1909
|
+
name: "record digest",
|
|
1910
|
+
ok,
|
|
1911
|
+
detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
|
|
1912
|
+
}
|
|
1913
|
+
];
|
|
1914
|
+
return { ok, kind: "record", record, checks };
|
|
1915
|
+
}
|
|
1916
|
+
async function verifyEvidenceFile(path) {
|
|
1917
|
+
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
1918
|
+
if (parsed?.schema === "veriskit/bundle@1") {
|
|
1919
|
+
const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
1920
|
+
return verifyBundle2(parsed);
|
|
1921
|
+
}
|
|
1922
|
+
return verifyRecord(parsed);
|
|
1923
|
+
}
|
|
1924
|
+
var init_verify_evidence = __esm({
|
|
1925
|
+
"src/evidence/verify-evidence.ts"() {
|
|
1926
|
+
"use strict";
|
|
1927
|
+
init_record();
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
// src/evidence/bundle.ts
|
|
1932
|
+
var bundle_exports = {};
|
|
1933
|
+
__export(bundle_exports, {
|
|
1934
|
+
BUNDLE_SCHEMA: () => BUNDLE_SCHEMA,
|
|
1935
|
+
buildBundle: () => buildBundle,
|
|
1936
|
+
verifyBundle: () => verifyBundle
|
|
1937
|
+
});
|
|
1938
|
+
function buildBundle(record, report, logs) {
|
|
1939
|
+
const manifest = {
|
|
1940
|
+
record: record.digest,
|
|
1941
|
+
report: sha256(report),
|
|
1942
|
+
logs: Object.fromEntries(
|
|
1943
|
+
Object.entries(logs).map(([k, v]) => [k, sha256(v)])
|
|
1944
|
+
)
|
|
1945
|
+
};
|
|
1946
|
+
const base = { schema: BUNDLE_SCHEMA, record, report, logs, manifest };
|
|
1947
|
+
return { ...base, bundleDigest: sha256(canonicalize(base)) };
|
|
1948
|
+
}
|
|
1949
|
+
function verifyBundle(bundle) {
|
|
1950
|
+
const checks = [];
|
|
1951
|
+
const recordResult = verifyRecord(bundle.record);
|
|
1952
|
+
checks.push(...recordResult.checks);
|
|
1953
|
+
const reportDigest = sha256(bundle.report);
|
|
1954
|
+
checks.push({
|
|
1955
|
+
name: "report",
|
|
1956
|
+
ok: reportDigest === bundle.manifest.report,
|
|
1957
|
+
detail: reportDigest === bundle.manifest.report ? "matches manifest" : "mismatch"
|
|
1958
|
+
});
|
|
1959
|
+
for (const [id, body] of Object.entries(bundle.logs)) {
|
|
1960
|
+
const d = sha256(body);
|
|
1961
|
+
checks.push({
|
|
1962
|
+
name: `log:${id}`,
|
|
1963
|
+
ok: d === bundle.manifest.logs[id],
|
|
1964
|
+
detail: d === bundle.manifest.logs[id] ? "matches manifest" : "mismatch"
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
const { bundleDigest: _omit, ...rest } = bundle;
|
|
1968
|
+
const recomputed = sha256(canonicalize(rest));
|
|
1969
|
+
checks.push({
|
|
1970
|
+
name: "bundle digest",
|
|
1971
|
+
ok: recomputed === bundle.bundleDigest,
|
|
1972
|
+
detail: recomputed === bundle.bundleDigest ? "matches" : "mismatch"
|
|
1973
|
+
});
|
|
1974
|
+
return {
|
|
1975
|
+
ok: checks.every((c) => c.ok),
|
|
1976
|
+
kind: "bundle",
|
|
1977
|
+
record: bundle.record,
|
|
1978
|
+
checks
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
var BUNDLE_SCHEMA;
|
|
1982
|
+
var init_bundle = __esm({
|
|
1983
|
+
"src/evidence/bundle.ts"() {
|
|
1984
|
+
"use strict";
|
|
1985
|
+
init_record();
|
|
1986
|
+
init_verify_evidence();
|
|
1987
|
+
BUNDLE_SCHEMA = "veriskit/bundle@1";
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
// src/cli/commands/evidence.ts
|
|
1992
|
+
var evidence_exports = {};
|
|
1993
|
+
__export(evidence_exports, {
|
|
1994
|
+
runEvidenceBundle: () => runEvidenceBundle,
|
|
1995
|
+
runEvidenceShow: () => runEvidenceShow,
|
|
1996
|
+
runEvidenceVerify: () => runEvidenceVerify
|
|
1997
|
+
});
|
|
1998
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1999
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
2000
|
+
import { basename as basename3, join as join12 } from "path";
|
|
2001
|
+
import pc5 from "picocolors";
|
|
2002
|
+
async function runEvidenceVerify(path) {
|
|
2003
|
+
let result;
|
|
2004
|
+
try {
|
|
2005
|
+
result = await verifyEvidenceFile(path);
|
|
2006
|
+
} catch (err) {
|
|
2007
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2008
|
+
process.stderr.write(`veris: cannot read evidence at ${path}: ${msg}
|
|
2009
|
+
`);
|
|
2010
|
+
return 1;
|
|
2011
|
+
}
|
|
2012
|
+
const plain = isPlain();
|
|
2013
|
+
const mark = (ok) => plain ? ok ? "ok" : "FAIL" : ok ? pc5.green("\u2713") : pc5.red("\u2717");
|
|
2014
|
+
for (const check of result.checks) {
|
|
2015
|
+
process.stdout.write(
|
|
2016
|
+
` ${mark(check.ok)} ${check.name}: ${check.detail}
|
|
2017
|
+
`
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
const g = result.record.git;
|
|
2021
|
+
const anchor = g ? `commit ${g.commit.slice(0, 7)} \xB7 ${g.dirty ? "tree dirty" : "tree clean"}` : "no git anchor";
|
|
2022
|
+
process.stdout.write(
|
|
2023
|
+
`
|
|
2024
|
+
${result.ok ? "digest OK" : "TAMPERED"} \xB7 verdict ${result.record.verdict.state} \xB7 ${anchor}
|
|
2025
|
+
`
|
|
2026
|
+
);
|
|
2027
|
+
process.stdout.write(`
|
|
2028
|
+
${HONESTY}
|
|
2029
|
+
`);
|
|
2030
|
+
return result.ok ? 0 : 1;
|
|
2031
|
+
}
|
|
2032
|
+
async function runEvidenceBundle(root, opts = {}) {
|
|
2033
|
+
const runDir = latestRunDir(root);
|
|
2034
|
+
if (!runDir) {
|
|
2035
|
+
process.stdout.write("No evidence yet. Run `veris verify` first.\n");
|
|
2036
|
+
return 1;
|
|
2037
|
+
}
|
|
2038
|
+
let record;
|
|
2039
|
+
try {
|
|
2040
|
+
record = JSON.parse(
|
|
2041
|
+
readFileSync5(join12(runDir, "evidence.json"), "utf8")
|
|
2042
|
+
);
|
|
2043
|
+
} catch {
|
|
2044
|
+
process.stderr.write(`veris: no evidence.json in ${runDir}
|
|
2045
|
+
`);
|
|
2046
|
+
return 1;
|
|
2047
|
+
}
|
|
2048
|
+
let report = "";
|
|
2049
|
+
try {
|
|
2050
|
+
report = readFileSync5(
|
|
2051
|
+
join12(root, ".veris", "reports", `verify-${record.id}.md`),
|
|
2052
|
+
"utf8"
|
|
2053
|
+
);
|
|
2054
|
+
} catch {
|
|
2055
|
+
}
|
|
2056
|
+
const logIds = record.checks.filter((c) => c.logDigest).map((c) => c.id);
|
|
2057
|
+
const logs = await readRunLogs(runDir, logIds);
|
|
2058
|
+
const bundle = buildBundle(record, report, logs);
|
|
2059
|
+
const outDir = await ensureEvidenceDir(root);
|
|
2060
|
+
const out = opts.out ?? join12(outDir, `${record.id}.bundle.json`);
|
|
2061
|
+
await writeFile4(out, `${JSON.stringify(bundle, null, 2)}
|
|
2062
|
+
`, "utf8");
|
|
2063
|
+
process.stdout.write(`Wrote portable evidence bundle: ${out}
|
|
2064
|
+
`);
|
|
2065
|
+
return 0;
|
|
2066
|
+
}
|
|
2067
|
+
async function runEvidenceShow(root, path) {
|
|
2068
|
+
const target = path ?? (() => {
|
|
2069
|
+
const dir = latestRunDir(root);
|
|
2070
|
+
return dir ? join12(dir, "evidence.json") : null;
|
|
2071
|
+
})();
|
|
2072
|
+
if (!target) {
|
|
2073
|
+
process.stdout.write("No evidence yet. Run `veris verify` first.\n");
|
|
2074
|
+
return 0;
|
|
2075
|
+
}
|
|
2076
|
+
let record;
|
|
2077
|
+
try {
|
|
2078
|
+
record = JSON.parse(readFileSync5(target, "utf8"));
|
|
2079
|
+
} catch {
|
|
2080
|
+
process.stderr.write(`veris: cannot read evidence at ${target}
|
|
2081
|
+
`);
|
|
2082
|
+
return 1;
|
|
2083
|
+
}
|
|
2084
|
+
const g = record.git;
|
|
2085
|
+
process.stdout.write(
|
|
2086
|
+
[
|
|
2087
|
+
`Evidence ${basename3(target)}`,
|
|
2088
|
+
`Verdict ${record.verdict.state}`,
|
|
2089
|
+
`Project ${record.project.name}`,
|
|
2090
|
+
`Scope ${record.scope.kind} (${record.scope.changedCount} changed)`,
|
|
2091
|
+
`Commit ${g ? `${g.commit.slice(0, 7)} (${g.branch}) ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}` : "no git anchor"}`,
|
|
2092
|
+
`Checks ${record.checks.map((c) => `${c.id}:${c.status}`).join(", ")}`,
|
|
2093
|
+
`Digest ${record.digest}`,
|
|
2094
|
+
""
|
|
2095
|
+
].join("\n")
|
|
2096
|
+
);
|
|
2097
|
+
return 0;
|
|
2098
|
+
}
|
|
2099
|
+
var HONESTY;
|
|
2100
|
+
var init_evidence = __esm({
|
|
2101
|
+
"src/cli/commands/evidence.ts"() {
|
|
2102
|
+
"use strict";
|
|
2103
|
+
init_bundle();
|
|
2104
|
+
init_store();
|
|
2105
|
+
init_verify_evidence();
|
|
2106
|
+
init_tty();
|
|
2107
|
+
HONESTY = "An integrity digest confirms the record was not edited or corrupted since it was written.\nIt is not forgery-proof on its own: publish the digest separately (CI log, PR) or sign it (planned) to prove authorship.";
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
|
|
1193
2111
|
// src/cli/index.ts
|
|
2112
|
+
init_version();
|
|
1194
2113
|
import { realpathSync } from "fs";
|
|
1195
2114
|
import { argv } from "process";
|
|
1196
2115
|
import { pathToFileURL } from "url";
|
|
1197
2116
|
import cac from "cac";
|
|
1198
|
-
|
|
1199
|
-
// src/version.ts
|
|
1200
|
-
import { readFileSync } from "fs";
|
|
1201
|
-
import { fileURLToPath } from "url";
|
|
1202
|
-
var pkgUrl = new URL("../package.json", import.meta.url);
|
|
1203
|
-
var VERSION = JSON.parse(
|
|
1204
|
-
readFileSync(fileURLToPath(pkgUrl), "utf8")
|
|
1205
|
-
).version;
|
|
1206
|
-
|
|
1207
|
-
// src/cli/index.ts
|
|
1208
2117
|
function buildCli() {
|
|
1209
2118
|
const cli = cac("veris");
|
|
1210
2119
|
cli.version(VERSION);
|
|
@@ -1242,6 +2151,45 @@ function buildCli() {
|
|
|
1242
2151
|
const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
1243
2152
|
process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
|
|
1244
2153
|
});
|
|
2154
|
+
cli.command("scan", "Map the import graph and untested areas (read-only)").action(async () => {
|
|
2155
|
+
const { runScan: runScan2 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
|
|
2156
|
+
process.exitCode = await runScan2(process.cwd());
|
|
2157
|
+
});
|
|
2158
|
+
cli.command(
|
|
2159
|
+
"plan",
|
|
2160
|
+
"Recommend what to test, from the import graph (read-only)"
|
|
2161
|
+
).option("--base <ref>", "Also factor in changes vs a git ref").action(async (opts) => {
|
|
2162
|
+
const { runPlan: runPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
|
|
2163
|
+
process.exitCode = await runPlan2(process.cwd(), { base: opts.base });
|
|
2164
|
+
});
|
|
2165
|
+
cli.command(
|
|
2166
|
+
"evidence <action> [file]",
|
|
2167
|
+
"Evidence tools: verify <file> | bundle | show [file]"
|
|
2168
|
+
).option("--out <file>", "For bundle: write to a specific path").action(
|
|
2169
|
+
async (action, file, opts) => {
|
|
2170
|
+
const mod = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
2171
|
+
if (action === "verify") {
|
|
2172
|
+
if (!file) {
|
|
2173
|
+
process.stderr.write("veris: evidence verify needs a <file>\n");
|
|
2174
|
+
process.exitCode = 1;
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
process.exitCode = await mod.runEvidenceVerify(file);
|
|
2178
|
+
} else if (action === "bundle") {
|
|
2179
|
+
process.exitCode = await mod.runEvidenceBundle(process.cwd(), {
|
|
2180
|
+
out: opts.out
|
|
2181
|
+
});
|
|
2182
|
+
} else if (action === "show") {
|
|
2183
|
+
process.exitCode = await mod.runEvidenceShow(process.cwd(), file);
|
|
2184
|
+
} else {
|
|
2185
|
+
process.stderr.write(
|
|
2186
|
+
`veris: unknown evidence action '${action}' (use verify | bundle | show)
|
|
2187
|
+
`
|
|
2188
|
+
);
|
|
2189
|
+
process.exitCode = 1;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
);
|
|
1245
2193
|
return { raw: cli, version: VERSION };
|
|
1246
2194
|
}
|
|
1247
2195
|
async function main(argv2) {
|