veriskit 0.5.1 → 0.6.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/dist/cli.js ADDED
@@ -0,0 +1,2982 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
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
+
25
+ // src/util/fs-safe.ts
26
+ import { existsSync } from "fs";
27
+ import { mkdir, readFile, writeFile } from "fs/promises";
28
+ async function ensureDir(path) {
29
+ await mkdir(path, { recursive: true });
30
+ }
31
+ async function writeIfAbsent(path, content) {
32
+ if (existsSync(path)) return false;
33
+ await writeFile(path, content, "utf8");
34
+ return true;
35
+ }
36
+ async function readJsonIfExists(path) {
37
+ if (!existsSync(path)) return null;
38
+ try {
39
+ return JSON.parse(await readFile(path, "utf8"));
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ var init_fs_safe = __esm({
45
+ "src/util/fs-safe.ts"() {
46
+ "use strict";
47
+ }
48
+ });
49
+
50
+ // src/config/detect.ts
51
+ import { existsSync as existsSync2 } from "fs";
52
+ import { join } from "path";
53
+ function detectPackageManager(root) {
54
+ if (existsSync2(join(root, "pnpm-lock.yaml"))) return "pnpm";
55
+ if (existsSync2(join(root, "yarn.lock"))) return "yarn";
56
+ if (existsSync2(join(root, "bun.lockb"))) return "bun";
57
+ return "npm";
58
+ }
59
+ async function detectProject(root) {
60
+ const pkg = await readJsonIfExists(join(root, "package.json")) ?? {};
61
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
62
+ const has = (name) => name in deps;
63
+ const scripts = pkg.scripts ?? {};
64
+ const languages = existsSync2(join(root, "tsconfig.json")) ? ["typescript", "javascript"] : ["javascript"];
65
+ const frameworks = ["next", "vite", "react"].filter(has);
66
+ const capabilities = [
67
+ detectTypes(root, has),
68
+ detectUnit(has, scripts),
69
+ detectLint(root, has),
70
+ detectBrowser(root, has)
71
+ ];
72
+ return {
73
+ root,
74
+ name: pkg.name ?? void 0,
75
+ packageManager: detectPackageManager(root),
76
+ frameworks,
77
+ languages,
78
+ scripts,
79
+ capabilities
80
+ };
81
+ }
82
+ function detectTypes(root, has) {
83
+ if (existsSync2(join(root, "tsconfig.json")) && has("typescript"))
84
+ return { id: "types", available: true, runner: "tsc" };
85
+ return {
86
+ id: "types",
87
+ available: false,
88
+ reason: "no tsconfig.json + typescript dependency"
89
+ };
90
+ }
91
+ function detectUnit(has, scripts) {
92
+ if (has("vitest")) return { id: "unit", available: true, runner: "vitest" };
93
+ if (has("jest")) return { id: "unit", available: true, runner: "jest" };
94
+ if (Object.values(scripts).some((s) => s.includes("node --test")))
95
+ return { id: "unit", available: true, runner: "node-test" };
96
+ return { id: "unit", available: false, reason: "no test runner detected" };
97
+ }
98
+ function detectLint(root, has) {
99
+ if (existsSync2(join(root, "biome.json")) || has("@biomejs/biome"))
100
+ return { id: "lint", available: true, runner: "biome" };
101
+ if (has("eslint") || [".eslintrc", ".eslintrc.json", ".eslintrc.cjs", "eslint.config.js"].some(
102
+ (f) => existsSync2(join(root, f))
103
+ ))
104
+ return { id: "lint", available: true, runner: "eslint" };
105
+ return { id: "lint", available: false, reason: "no linter configured" };
106
+ }
107
+ function detectBrowser(root, has) {
108
+ if (has("@playwright/test") || existsSync2(join(root, "playwright.config.ts")) || existsSync2(join(root, "playwright.config.js"))) {
109
+ return { id: "browser", available: true, runner: "playwright" };
110
+ }
111
+ return {
112
+ id: "browser",
113
+ available: false,
114
+ reason: "no browser runner configured"
115
+ };
116
+ }
117
+ var init_detect = __esm({
118
+ "src/config/detect.ts"() {
119
+ "use strict";
120
+ init_fs_safe();
121
+ }
122
+ });
123
+
124
+ // src/util/env.ts
125
+ import { platform } from "os";
126
+ function detectCI() {
127
+ return process.env.CI === "true" || process.env.CI === "1";
128
+ }
129
+ function getEnvironmentInfo(pm) {
130
+ return {
131
+ os: platform(),
132
+ node: process.version,
133
+ pm,
134
+ ci: detectCI(),
135
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
136
+ };
137
+ }
138
+ var init_env = __esm({
139
+ "src/util/env.ts"() {
140
+ "use strict";
141
+ }
142
+ });
143
+
144
+ // src/cli/tty.ts
145
+ function isPlain() {
146
+ return detectCI() || !process.stdout.isTTY;
147
+ }
148
+ var init_tty = __esm({
149
+ "src/cli/tty.ts"() {
150
+ "use strict";
151
+ init_env();
152
+ }
153
+ });
154
+
155
+ // src/cli/commands/doctor.ts
156
+ var doctor_exports = {};
157
+ __export(doctor_exports, {
158
+ renderDoctor: () => renderDoctor,
159
+ runDoctor: () => runDoctor
160
+ });
161
+ import pc from "picocolors";
162
+ function renderDoctor(project, env) {
163
+ const plain = isPlain();
164
+ const ok = (s) => plain ? s : pc.green(s);
165
+ const dim = (s) => plain ? s : pc.dim(s);
166
+ const lines = [];
167
+ lines.push("VerisKit doctor");
168
+ lines.push("");
169
+ lines.push(`Package manager ${project.packageManager}`);
170
+ lines.push(`Node ${env.node}`);
171
+ lines.push(`Languages ${project.languages.join(", ")}`);
172
+ if (project.frameworks.length)
173
+ lines.push(`Frameworks ${project.frameworks.join(", ")}`);
174
+ lines.push("");
175
+ lines.push("Capabilities");
176
+ for (const c of project.capabilities) {
177
+ if (c.available) {
178
+ lines.push(` ${ok("\u2713")} ${c.id.padEnd(8)} ${dim(`via ${c.runner}`)}`);
179
+ } else {
180
+ lines.push(
181
+ ` ${dim("\u2298")} ${c.id.padEnd(8)} ${dim(`skipped \u2014 ${c.reason}`)}`
182
+ );
183
+ }
184
+ }
185
+ return lines.join("\n");
186
+ }
187
+ async function runDoctor(root) {
188
+ const project = await detectProject(root);
189
+ const env = getEnvironmentInfo(project.packageManager);
190
+ process.stdout.write(`${renderDoctor(project, env)}
191
+ `);
192
+ return 0;
193
+ }
194
+ var init_doctor = __esm({
195
+ "src/cli/commands/doctor.ts"() {
196
+ "use strict";
197
+ init_detect();
198
+ init_env();
199
+ init_tty();
200
+ }
201
+ });
202
+
203
+ // src/evidence/record.ts
204
+ import { createHash } from "crypto";
205
+ import { basename } from "path";
206
+ function canonicalize(value) {
207
+ return JSON.stringify(sortValue(value));
208
+ }
209
+ function sortValue(value) {
210
+ if (Array.isArray(value)) return value.map(sortValue);
211
+ if (value && typeof value === "object") {
212
+ const src = value;
213
+ const out = {};
214
+ for (const key of Object.keys(src).sort()) {
215
+ if (src[key] !== void 0) out[key] = sortValue(src[key]);
216
+ }
217
+ return out;
218
+ }
219
+ return value;
220
+ }
221
+ function sha256(text) {
222
+ return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
223
+ }
224
+ function computeDigest(record) {
225
+ const { digest: _omit, ...rest } = record;
226
+ return sha256(canonicalize(rest));
227
+ }
228
+ function buildRecord(run, git, logDigests, toolVersion) {
229
+ const runnerOf = new Map(
230
+ run.project.capabilities.map((c) => [c.id, c.runner])
231
+ );
232
+ const checks = run.results.map((r) => {
233
+ const check = {
234
+ id: r.checkId,
235
+ status: r.status,
236
+ durationMs: r.durationMs,
237
+ summary: r.summary
238
+ };
239
+ const runner = runnerOf.get(r.checkId);
240
+ if (runner) check.runner = runner;
241
+ if (r.counts) check.counts = r.counts;
242
+ const digest = logDigests[r.checkId];
243
+ if (digest) check.logDigest = digest;
244
+ return check;
245
+ });
246
+ const scope = run.scope ? { kind: run.scope.kind, changedCount: run.scope.changedCount } : { kind: "full", changedCount: 0 };
247
+ const base = {
248
+ schema: EVIDENCE_SCHEMA,
249
+ id: run.id,
250
+ startedAt: run.startedAt,
251
+ tool: { name: "veriskit", version: toolVersion },
252
+ git,
253
+ env: run.env,
254
+ project: {
255
+ name: run.project.name ?? basename(run.project.root),
256
+ packageManager: run.project.packageManager,
257
+ frameworks: run.project.frameworks,
258
+ languages: run.project.languages
259
+ },
260
+ scope,
261
+ checks,
262
+ verdict: run.verdict
263
+ };
264
+ return { ...base, digest: computeDigest(base) };
265
+ }
266
+ var EVIDENCE_SCHEMA;
267
+ var init_record = __esm({
268
+ "src/evidence/record.ts"() {
269
+ "use strict";
270
+ EVIDENCE_SCHEMA = "veriskit/evidence@1";
271
+ }
272
+ });
273
+
274
+ // src/evidence/store.ts
275
+ import { readdirSync } from "fs";
276
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
277
+ import { join as join2 } from "path";
278
+ function newRunId() {
279
+ counter += 1;
280
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
281
+ return `${stamp}-${counter}`;
282
+ }
283
+ async function createRunDir(root, id) {
284
+ const dir = join2(root, ".veris", "runs", id);
285
+ await ensureDir(dir);
286
+ return dir;
287
+ }
288
+ async function writeLog(runDir, checkId, content) {
289
+ const ref = join2(runDir, `${checkId}.log`);
290
+ await writeFile2(ref, content, "utf8");
291
+ return ref;
292
+ }
293
+ async function writeReport(root, id, markdown) {
294
+ const dir = join2(root, ".veris", "reports");
295
+ await ensureDir(dir);
296
+ const ref = join2(dir, `verify-${id}.md`);
297
+ await writeFile2(ref, markdown, "utf8");
298
+ return ref;
299
+ }
300
+ async function writeEvidence(runDir, record) {
301
+ const ref = join2(runDir, "evidence.json");
302
+ await writeFile2(ref, `${JSON.stringify(record, null, 2)}
303
+ `, "utf8");
304
+ return ref;
305
+ }
306
+ async function digestLogs(run) {
307
+ const out = {};
308
+ for (const r of run.results) {
309
+ if (!r.logRef) continue;
310
+ try {
311
+ out[r.checkId] = sha256(await readFile2(r.logRef, "utf8"));
312
+ } catch {
313
+ }
314
+ }
315
+ return out;
316
+ }
317
+ async function readRunLogs(runDir, checkIds) {
318
+ const out = {};
319
+ for (const id of checkIds) {
320
+ try {
321
+ out[id] = await readFile2(join2(runDir, `${id}.log`), "utf8");
322
+ } catch {
323
+ }
324
+ }
325
+ return out;
326
+ }
327
+ function latestRunDir(root) {
328
+ const runs = join2(root, ".veris", "runs");
329
+ try {
330
+ const dirs = readdirSync(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name).sort();
331
+ const latest = dirs.at(-1);
332
+ return latest ? join2(runs, latest) : null;
333
+ } catch {
334
+ return null;
335
+ }
336
+ }
337
+ async function ensureEvidenceDir(root) {
338
+ const dir = join2(root, ".veris", "evidence");
339
+ await ensureDir(dir);
340
+ return dir;
341
+ }
342
+ var counter;
343
+ var init_store = __esm({
344
+ "src/evidence/store.ts"() {
345
+ "use strict";
346
+ init_fs_safe();
347
+ init_record();
348
+ counter = 0;
349
+ }
350
+ });
351
+
352
+ // src/util/exec.ts
353
+ import { spawn } from "child_process";
354
+ function exec(cmd, args, opts = {}) {
355
+ return new Promise((resolve2) => {
356
+ const start = performance.now();
357
+ const child = spawn(cmd, args, {
358
+ cwd: opts.cwd,
359
+ env: opts.env ?? process.env,
360
+ shell: false
361
+ });
362
+ let stdout = "";
363
+ let stderr = "";
364
+ let timedOut = false;
365
+ const timer = opts.timeoutMs ? setTimeout(() => {
366
+ timedOut = true;
367
+ child.kill("SIGKILL");
368
+ }, opts.timeoutMs) : null;
369
+ child.stdout.on("data", (d) => stdout += d.toString());
370
+ child.stderr.on("data", (d) => stderr += d.toString());
371
+ child.on("close", (code) => {
372
+ if (timer) clearTimeout(timer);
373
+ resolve2({
374
+ code: code ?? 1,
375
+ stdout,
376
+ stderr,
377
+ durationMs: Math.round(performance.now() - start),
378
+ timedOut
379
+ });
380
+ });
381
+ child.on("error", () => {
382
+ if (timer) clearTimeout(timer);
383
+ resolve2({
384
+ code: 127,
385
+ stdout,
386
+ stderr: stderr || `failed to spawn ${cmd}`,
387
+ durationMs: Math.round(performance.now() - start),
388
+ timedOut
389
+ });
390
+ });
391
+ });
392
+ }
393
+ var init_exec = __esm({
394
+ "src/util/exec.ts"() {
395
+ "use strict";
396
+ }
397
+ });
398
+
399
+ // src/runners/base.ts
400
+ import { join as join3 } from "path";
401
+ function localBin(root, name) {
402
+ return join3(root, "node_modules", ".bin", name);
403
+ }
404
+ async function runViaExec(check, ctx, opts) {
405
+ const r = await exec(check.cmd, check.args, {
406
+ cwd: ctx.root,
407
+ timeoutMs: opts.timeoutMs
408
+ });
409
+ const status = r.code === 0 ? "passed" : r.timedOut ? "unknown" : "failed";
410
+ const output = [r.stdout, r.stderr].filter(Boolean).join("\n").trim();
411
+ const logRef = await writeLog(ctx.runDir, check.id, `${output}
412
+ `);
413
+ const result = {
414
+ checkId: check.id,
415
+ status,
416
+ durationMs: r.durationMs,
417
+ summary: status === "passed" ? opts.pass : r.timedOut ? "timed out" : opts.fail,
418
+ logRef
419
+ };
420
+ if (status !== "passed" && output) {
421
+ result.outputTail = output.split("\n").slice(-TAIL_LINES).join("\n");
422
+ }
423
+ return result;
424
+ }
425
+ var TAIL_LINES;
426
+ var init_base = __esm({
427
+ "src/runners/base.ts"() {
428
+ "use strict";
429
+ init_store();
430
+ init_exec();
431
+ TAIL_LINES = 20;
432
+ }
433
+ });
434
+
435
+ // src/runners/biome.ts
436
+ var biomeRunner;
437
+ var init_biome = __esm({
438
+ "src/runners/biome.ts"() {
439
+ "use strict";
440
+ init_base();
441
+ biomeRunner = {
442
+ id: "biome",
443
+ toCheck(project, _cap) {
444
+ return {
445
+ id: "lint",
446
+ title: "Lint",
447
+ runner: "biome",
448
+ cmd: localBin(project.root, "biome"),
449
+ args: ["check", "."]
450
+ };
451
+ },
452
+ run(check, ctx) {
453
+ return runViaExec(check, ctx, {
454
+ pass: "no lint errors",
455
+ fail: "lint errors found",
456
+ timeoutMs: 3 * 6e4
457
+ });
458
+ }
459
+ };
460
+ }
461
+ });
462
+
463
+ // src/runners/eslint.ts
464
+ var eslintRunner;
465
+ var init_eslint = __esm({
466
+ "src/runners/eslint.ts"() {
467
+ "use strict";
468
+ init_base();
469
+ eslintRunner = {
470
+ id: "eslint",
471
+ toCheck(project, _cap) {
472
+ return {
473
+ id: "lint",
474
+ title: "Lint",
475
+ runner: "eslint",
476
+ cmd: localBin(project.root, "eslint"),
477
+ args: ["."]
478
+ };
479
+ },
480
+ run(check, ctx) {
481
+ return runViaExec(check, ctx, {
482
+ pass: "no lint errors",
483
+ fail: "lint errors found",
484
+ timeoutMs: 3 * 6e4
485
+ });
486
+ }
487
+ };
488
+ }
489
+ });
490
+
491
+ // src/runners/jest.ts
492
+ var jestRunner;
493
+ var init_jest = __esm({
494
+ "src/runners/jest.ts"() {
495
+ "use strict";
496
+ init_base();
497
+ jestRunner = {
498
+ id: "jest",
499
+ toCheck(project, _cap, opts) {
500
+ return {
501
+ id: "unit",
502
+ title: "Unit tests",
503
+ runner: "jest",
504
+ cmd: localBin(project.root, "jest"),
505
+ args: ["--ci", ...opts?.targetFiles ?? []]
506
+ };
507
+ },
508
+ run(check, ctx) {
509
+ return runViaExec(check, ctx, {
510
+ pass: "unit tests passed",
511
+ fail: "unit tests failed",
512
+ timeoutMs: 10 * 6e4
513
+ });
514
+ }
515
+ };
516
+ }
517
+ });
518
+
519
+ // src/runners/node-test.ts
520
+ var nodeTestRunner;
521
+ var init_node_test = __esm({
522
+ "src/runners/node-test.ts"() {
523
+ "use strict";
524
+ init_base();
525
+ nodeTestRunner = {
526
+ id: "node-test",
527
+ toCheck(_project, _cap) {
528
+ return {
529
+ id: "unit",
530
+ title: "Unit tests",
531
+ runner: "node-test",
532
+ cmd: process.execPath,
533
+ args: ["--test"]
534
+ };
535
+ },
536
+ run(check, ctx) {
537
+ return runViaExec(check, ctx, {
538
+ pass: "unit tests passed",
539
+ fail: "unit tests failed",
540
+ timeoutMs: 10 * 6e4
541
+ });
542
+ }
543
+ };
544
+ }
545
+ });
546
+
547
+ // src/runners/playwright.ts
548
+ function parsePlaywrightStats(stdout) {
549
+ try {
550
+ const json = JSON.parse(stdout);
551
+ return json.stats ?? null;
552
+ } catch {
553
+ return null;
554
+ }
555
+ }
556
+ var TAIL_LINES2, playwrightRunner;
557
+ var init_playwright = __esm({
558
+ "src/runners/playwright.ts"() {
559
+ "use strict";
560
+ init_store();
561
+ init_exec();
562
+ init_base();
563
+ TAIL_LINES2 = 20;
564
+ playwrightRunner = {
565
+ id: "playwright",
566
+ toCheck(project, _cap) {
567
+ return {
568
+ id: "browser",
569
+ title: "Browser tests",
570
+ runner: "playwright",
571
+ cmd: localBin(project.root, "playwright"),
572
+ args: ["test", "--reporter=json"]
573
+ };
574
+ },
575
+ async run(check, ctx) {
576
+ const r = await exec(check.cmd, check.args, {
577
+ cwd: ctx.root,
578
+ timeoutMs: 15 * 6e4
579
+ });
580
+ const output = [r.stdout, r.stderr].filter(Boolean).join("\n").trim();
581
+ const logRef = await writeLog(ctx.runDir, check.id, `${output}
582
+ `);
583
+ const stats = parsePlaywrightStats(r.stdout);
584
+ let status;
585
+ if (r.timedOut) {
586
+ status = "unknown";
587
+ } else if (stats) {
588
+ status = r.code === 0 && (stats.unexpected ?? 0) === 0 ? "passed" : "failed";
589
+ } else {
590
+ status = r.code === 0 ? "unknown" : "failed";
591
+ }
592
+ const result = {
593
+ checkId: "browser",
594
+ status,
595
+ durationMs: r.durationMs,
596
+ summary: status === "passed" ? "browser tests passed" : r.timedOut ? "timed out" : status === "unknown" ? "browser tests ran but the results could not be parsed" : "browser tests failed",
597
+ logRef
598
+ };
599
+ if (stats) {
600
+ const passed = stats.expected ?? 0;
601
+ const failed = stats.unexpected ?? 0;
602
+ result.counts = {
603
+ passed,
604
+ failed,
605
+ total: passed + failed + (stats.flaky ?? 0) + (stats.skipped ?? 0)
606
+ };
607
+ }
608
+ if (status !== "passed" && output) {
609
+ result.outputTail = output.split("\n").slice(-TAIL_LINES2).join("\n");
610
+ }
611
+ return result;
612
+ }
613
+ };
614
+ }
615
+ });
616
+
617
+ // src/runners/tsc.ts
618
+ var tscRunner;
619
+ var init_tsc = __esm({
620
+ "src/runners/tsc.ts"() {
621
+ "use strict";
622
+ init_base();
623
+ tscRunner = {
624
+ id: "tsc",
625
+ toCheck(project, _cap) {
626
+ return {
627
+ id: "types",
628
+ title: "Types",
629
+ runner: "tsc",
630
+ cmd: localBin(project.root, "tsc"),
631
+ args: ["--noEmit", "--pretty", "false"]
632
+ };
633
+ },
634
+ run(check, ctx) {
635
+ return runViaExec(check, ctx, {
636
+ pass: "no type errors",
637
+ fail: "type errors found",
638
+ timeoutMs: 5 * 6e4
639
+ });
640
+ }
641
+ };
642
+ }
643
+ });
644
+
645
+ // src/runners/vitest.ts
646
+ var vitestRunner;
647
+ var init_vitest = __esm({
648
+ "src/runners/vitest.ts"() {
649
+ "use strict";
650
+ init_base();
651
+ vitestRunner = {
652
+ id: "vitest",
653
+ toCheck(project, _cap, opts) {
654
+ const files = opts?.targetFiles ?? [];
655
+ return {
656
+ id: "unit",
657
+ title: "Unit tests",
658
+ runner: "vitest",
659
+ cmd: localBin(project.root, "vitest"),
660
+ args: ["run", "--reporter=json", ...files]
661
+ };
662
+ },
663
+ run(check, ctx) {
664
+ return runViaExec(check, ctx, {
665
+ pass: "unit tests passed",
666
+ fail: "unit tests failed",
667
+ timeoutMs: 10 * 6e4
668
+ });
669
+ }
670
+ };
671
+ }
672
+ });
673
+
674
+ // src/runners/index.ts
675
+ var runners;
676
+ var init_runners = __esm({
677
+ "src/runners/index.ts"() {
678
+ "use strict";
679
+ init_biome();
680
+ init_eslint();
681
+ init_jest();
682
+ init_node_test();
683
+ init_playwright();
684
+ init_tsc();
685
+ init_vitest();
686
+ init_base();
687
+ runners = {
688
+ tsc: tscRunner,
689
+ vitest: vitestRunner,
690
+ jest: jestRunner,
691
+ "node-test": nodeTestRunner,
692
+ eslint: eslintRunner,
693
+ biome: biomeRunner,
694
+ playwright: playwrightRunner
695
+ };
696
+ }
697
+ });
698
+
699
+ // src/core/verdict.ts
700
+ function computeVerdict(results, capabilities) {
701
+ const reasons = [];
702
+ const skipped = [];
703
+ const verifiedCapabilities = [];
704
+ const anyFailed = results.some((r) => r.status === "failed");
705
+ for (const cap of capabilities) {
706
+ const result = results.find((r) => r.checkId === cap.id);
707
+ if (!cap.available) {
708
+ skipped.push(cap.id);
709
+ reasons.push(`${cap.id} skipped \u2014 ${cap.reason ?? "not configured"}`);
710
+ continue;
711
+ }
712
+ if (result?.status === "passed") {
713
+ verifiedCapabilities.push(cap.id);
714
+ continue;
715
+ }
716
+ if (result?.status === "failed") {
717
+ reasons.push(`${cap.id} failed \u2014 ${result.summary}`);
718
+ continue;
719
+ }
720
+ skipped.push(cap.id);
721
+ reasons.push(`${cap.id} skipped \u2014 ${result?.summary ?? "did not run"}`);
722
+ }
723
+ let state;
724
+ if (anyFailed) state = "failed";
725
+ else if (skipped.length > 0) state = "partial";
726
+ else state = "verified";
727
+ return { state, verifiedCapabilities, skipped, reasons };
728
+ }
729
+ function verdictExitCode(verdict, opts = {}) {
730
+ if (verdict.state === "failed") return 1;
731
+ if (verdict.state === "partial") return opts.partialOk ? 0 : 2;
732
+ return 0;
733
+ }
734
+ var init_verdict = __esm({
735
+ "src/core/verdict.ts"() {
736
+ "use strict";
737
+ }
738
+ });
739
+
740
+ // src/core/orchestrator.ts
741
+ async function runChecks(project, ids, root, opts = {}) {
742
+ const known = new Set(project.capabilities.map((c) => c.id));
743
+ const unknown = ids.filter((id2) => !known.has(id2));
744
+ if (unknown.length > 0) {
745
+ throw new Error(`Unknown check id(s): ${unknown.join(", ")}`);
746
+ }
747
+ const id = newRunId();
748
+ const runDir = await createRunDir(root, id);
749
+ const ctx = { root, runDir };
750
+ const tasks = ids.map(async (capId) => {
751
+ const cap = project.capabilities.find((c) => c.id === capId);
752
+ const runner = cap?.runner ? runners[cap.runner] : void 0;
753
+ if (!cap?.available || !runner) {
754
+ const summary = !cap?.available ? cap?.reason ?? "not configured" : `no runner registered for ${cap.runner}`;
755
+ return {
756
+ checkId: capId,
757
+ status: "skipped",
758
+ durationMs: 0,
759
+ summary
760
+ };
761
+ }
762
+ const check = runner.toCheck(project, cap, {
763
+ targetFiles: opts.targetFiles?.[capId]
764
+ });
765
+ return runner.run(check, ctx);
766
+ });
767
+ const results = await Promise.all(tasks);
768
+ const requested = project.capabilities.filter((c) => ids.includes(c.id));
769
+ const verdict = computeVerdict(results, requested);
770
+ return {
771
+ id,
772
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
773
+ project,
774
+ results,
775
+ verdict,
776
+ env: getEnvironmentInfo(project.packageManager)
777
+ };
778
+ }
779
+ var init_orchestrator = __esm({
780
+ "src/core/orchestrator.ts"() {
781
+ "use strict";
782
+ init_store();
783
+ init_runners();
784
+ init_env();
785
+ init_verdict();
786
+ }
787
+ });
788
+
789
+ // src/reporters/terminal.ts
790
+ import pc2 from "picocolors";
791
+ function glyph(status, plain) {
792
+ const map = {
793
+ passed: "\u2713",
794
+ failed: "\u2717",
795
+ skipped: "\u2298",
796
+ unknown: "?"
797
+ };
798
+ const g = map[status];
799
+ if (plain) return g;
800
+ if (status === "passed") return pc2.green(g);
801
+ if (status === "failed") return pc2.red(g);
802
+ return pc2.dim(g);
803
+ }
804
+ function secs(ms) {
805
+ return ms === 0 ? "" : `${(ms / 1e3).toFixed(1)}s`;
806
+ }
807
+ function renderRun(run, record) {
808
+ const plain = isPlain();
809
+ const bold = (s) => plain ? s : pc2.bold(s);
810
+ const dim = (s) => plain ? s : pc2.dim(s);
811
+ const lines = [];
812
+ const scoped = run.scope?.kind;
813
+ if (scoped) {
814
+ lines.push(bold(`VerisKit \u2014 ${scoped}`));
815
+ lines.push("");
816
+ lines.push(`Scope ${run.scope?.changedCount ?? 0} changed file(s)`);
817
+ } else {
818
+ lines.push(bold("VerisKit"));
819
+ lines.push("");
820
+ lines.push(
821
+ `Project ${run.project.root.split("/").pop() ?? run.project.root}`
822
+ );
823
+ lines.push(`Risk ${dim("\u2014")}`);
824
+ }
825
+ lines.push("");
826
+ lines.push("Checks");
827
+ for (const r of run.results) {
828
+ const g = glyph(r.status, plain);
829
+ const detail = r.status === "skipped" ? dim(`skipped \u2014 ${r.summary}`) : r.cached ? dim(`\u27F3 cached \xB7 ${secs(r.durationMs) || "\u2014"}`) : secs(r.durationMs);
830
+ lines.push(` ${g} ${r.checkId.padEnd(14)} ${detail}`);
831
+ if (r.outputTail) {
832
+ for (const tail of r.outputTail.split("\n")) {
833
+ lines.push(dim(` ${tail}`));
834
+ }
835
+ }
836
+ }
837
+ lines.push("");
838
+ lines.push("Result");
839
+ 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";
840
+ const color = run.verdict.state === "verified" ? pc2.green : run.verdict.state === "failed" ? pc2.red : pc2.yellow;
841
+ lines.push(` ${plain ? label : color(label)}`);
842
+ if (record?.git) {
843
+ const g = record.git;
844
+ lines.push("");
845
+ lines.push(
846
+ `Commit ${g.commit.slice(0, 7)} ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}`
847
+ );
848
+ }
849
+ if (run.reportRef) {
850
+ lines.push("");
851
+ lines.push("Report");
852
+ lines.push(` ${run.reportRef}`);
853
+ }
854
+ return lines.join("\n");
855
+ }
856
+ var init_terminal = __esm({
857
+ "src/reporters/terminal.ts"() {
858
+ "use strict";
859
+ init_tty();
860
+ }
861
+ });
862
+
863
+ // src/cli/commands/test.ts
864
+ var test_exports = {};
865
+ __export(test_exports, {
866
+ runTest: () => runTest
867
+ });
868
+ async function runTest(root) {
869
+ const project = await detectProject(root);
870
+ const run = await runChecks(project, ["unit"], root);
871
+ process.stdout.write(`${renderRun(run)}
872
+ `);
873
+ return verdictExitCode(run.verdict);
874
+ }
875
+ var init_test = __esm({
876
+ "src/cli/commands/test.ts"() {
877
+ "use strict";
878
+ init_detect();
879
+ init_orchestrator();
880
+ init_verdict();
881
+ init_terminal();
882
+ }
883
+ });
884
+
885
+ // src/cli/commands/init.ts
886
+ var init_exports = {};
887
+ __export(init_exports, {
888
+ runInit: () => runInit
889
+ });
890
+ import { join as join4 } from "path";
891
+ async function runInit(root) {
892
+ const project = await detectProject(root);
893
+ const dir = join4(root, ".veris");
894
+ await ensureDir(dir);
895
+ const defaultChecks = project.capabilities.filter((c) => c.available && c.id !== "browser").map((c) => c.id);
896
+ const wroteConfig = await writeIfAbsent(
897
+ join4(dir, "config.json"),
898
+ `${JSON.stringify({ checks: defaultChecks }, null, 2)}
899
+ `
900
+ );
901
+ await writeIfAbsent(join4(dir, ".gitignore"), GITIGNORE);
902
+ process.stdout.write(
903
+ wroteConfig ? `Veris initialized. Detected checks: ${defaultChecks.join(", ") || "none"}.
904
+ ` : "VerisKit already initialized (.veris/config.json exists). Nothing overwritten.\n"
905
+ );
906
+ return 0;
907
+ }
908
+ var GITIGNORE;
909
+ var init_init = __esm({
910
+ "src/cli/commands/init.ts"() {
911
+ "use strict";
912
+ init_detect();
913
+ init_fs_safe();
914
+ GITIGNORE = [
915
+ "runs/",
916
+ "reports/",
917
+ "cache/",
918
+ "graph.json",
919
+ "evidence/",
920
+ "keys/",
921
+ ""
922
+ ].join("\n");
923
+ }
924
+ });
925
+
926
+ // src/affected/gate.ts
927
+ function affectedChecks(files, project) {
928
+ const available = new Set(
929
+ project.capabilities.filter((c) => c.available).map((c) => c.id)
930
+ );
931
+ const wanted = /* @__PURE__ */ new Set();
932
+ const reasonByCheck = {};
933
+ const want = (id, reason) => {
934
+ if (available.has(id) && !wanted.has(id)) {
935
+ wanted.add(id);
936
+ reasonByCheck[id] = reason;
937
+ }
938
+ };
939
+ const wantAll = (reason) => {
940
+ for (const id of available) want(id, reason);
941
+ };
942
+ for (const f of files) {
943
+ if (CONFIG_RE.test(f)) {
944
+ wantAll(`config changed (${f})`);
945
+ } else if (DOC_RE.test(f)) {
946
+ } else if (TEST_RE.test(f)) {
947
+ want("unit", "test file changed");
948
+ want("lint", "test file changed");
949
+ } else if (TS_RE.test(f)) {
950
+ want("types", "TypeScript changed");
951
+ want("lint", "TypeScript changed");
952
+ want("unit", "TypeScript changed");
953
+ } else if (JS_RE.test(f)) {
954
+ want("lint", "JavaScript changed");
955
+ want("unit", "JavaScript changed");
956
+ } else {
957
+ wantAll(`unrecognized file changed (${f})`);
958
+ }
959
+ }
960
+ return {
961
+ checks: ORDER.filter((id) => wanted.has(id)),
962
+ reasonByCheck,
963
+ changedCount: files.length
964
+ };
965
+ }
966
+ var ORDER, CONFIG_RE, TEST_RE, TS_RE, JS_RE, DOC_RE;
967
+ var init_gate = __esm({
968
+ "src/affected/gate.ts"() {
969
+ "use strict";
970
+ ORDER = ["types", "lint", "unit", "browser"];
971
+ CONFIG_RE = /(^|\/)(tsconfig[^/]*\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|package\.json|veris\.config\.[^/]+)$/;
972
+ TEST_RE = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|__tests__)\//;
973
+ TS_RE = /\.[cm]?tsx?$/;
974
+ JS_RE = /\.[cm]?jsx?$/;
975
+ DOC_RE = /(\.(md|mdx|markdown|txt|png|jpe?g|gif|svg|webp|ico)$)|((^|\/)LICENSE$)/i;
976
+ }
977
+ });
978
+
979
+ // src/config/load.ts
980
+ import { join as join5 } from "path";
981
+ async function loadConfig(root) {
982
+ return readJsonIfExists(join5(root, ".veris", "config.json"));
983
+ }
984
+ var init_load = __esm({
985
+ "src/config/load.ts"() {
986
+ "use strict";
987
+ init_fs_safe();
988
+ }
989
+ });
990
+
991
+ // src/git/changes.ts
992
+ function collect(set, out) {
993
+ for (const line of out.split("\n")) {
994
+ const f = line.trim();
995
+ if (f) set.add(f);
996
+ }
997
+ }
998
+ async function changedFiles(root, opts = {}) {
999
+ const base = opts.base ?? null;
1000
+ const files = /* @__PURE__ */ new Set();
1001
+ const diffArgs = base ? ["diff", "--name-only", base] : ["diff", "--name-only", "HEAD"];
1002
+ const diff = await exec("git", diffArgs, { cwd: root });
1003
+ if (diff.code === 0) collect(files, diff.stdout);
1004
+ const untracked = await exec(
1005
+ "git",
1006
+ ["ls-files", "--others", "--exclude-standard"],
1007
+ { cwd: root }
1008
+ );
1009
+ if (untracked.code === 0) collect(files, untracked.stdout);
1010
+ return { files: [...files].sort(), base };
1011
+ }
1012
+ async function gitAnchor(root) {
1013
+ const head = await exec("git", ["rev-parse", "HEAD"], { cwd: root });
1014
+ if (head.code !== 0) return null;
1015
+ const commit = head.stdout.trim();
1016
+ const branchRes = await exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
1017
+ cwd: root
1018
+ });
1019
+ const branch = branchRes.code === 0 ? branchRes.stdout.trim() : "HEAD";
1020
+ const status = await exec("git", ["status", "--porcelain"], { cwd: root });
1021
+ const lines = status.code === 0 ? status.stdout.split("\n").map((l) => l.trim()).filter(Boolean) : [];
1022
+ return {
1023
+ commit,
1024
+ branch,
1025
+ dirty: lines.length > 0,
1026
+ changedFiles: lines.length
1027
+ };
1028
+ }
1029
+ var init_changes = __esm({
1030
+ "src/git/changes.ts"() {
1031
+ "use strict";
1032
+ init_exec();
1033
+ }
1034
+ });
1035
+
1036
+ // src/reporters/markdown.ts
1037
+ import { relative } from "path";
1038
+ function cell(value) {
1039
+ return value.replace(/\|/g, "\\|");
1040
+ }
1041
+ function renderMarkdown(run, record) {
1042
+ const root = run.project.root;
1043
+ const lines = [];
1044
+ lines.push("# VerisKit Verification Report");
1045
+ if (run.scope?.kind) {
1046
+ lines.push("");
1047
+ lines.push(
1048
+ `> **Scope:** ${run.scope.kind} \u2014 ${run.scope.changedCount} changed file(s). Only affected checks ran; this is not a full verification.`
1049
+ );
1050
+ }
1051
+ lines.push("");
1052
+ lines.push(`**Verdict:** ${STATE_LABEL[run.verdict.state]}`);
1053
+ lines.push(`**When:** ${run.startedAt}`);
1054
+ lines.push(
1055
+ `**Node:** ${run.env.node} \xB7 **OS:** ${run.env.os} \xB7 **PM:** ${run.env.pm} \xB7 **CI:** ${run.env.ci}`
1056
+ );
1057
+ if (record?.git) {
1058
+ const g = record.git;
1059
+ const tree = g.dirty ? `tree dirty, ${g.changedFiles} uncommitted file(s)` : "tree clean";
1060
+ lines.push(`**Commit:** ${g.commit.slice(0, 7)} (${g.branch}) \xB7 ${tree}`);
1061
+ } else if (record) {
1062
+ lines.push("**Commit:** no git anchor available");
1063
+ }
1064
+ lines.push("");
1065
+ lines.push("## Checks");
1066
+ lines.push("");
1067
+ lines.push("| Check | Status | Duration | Summary | Log |");
1068
+ lines.push("| --- | --- | --- | --- | --- |");
1069
+ for (const r of run.results) {
1070
+ const dur = r.durationMs ? `${(r.durationMs / 1e3).toFixed(1)}s` : "\u2014";
1071
+ const log = r.logRef ? cell(relative(root, r.logRef)) : "\u2014";
1072
+ lines.push(
1073
+ `| ${r.checkId} | ${r.status} | ${dur} | ${cell(r.summary)} | ${log} |`
1074
+ );
1075
+ }
1076
+ if (run.verdict.skipped.length) {
1077
+ lines.push("");
1078
+ lines.push("## Skipped");
1079
+ lines.push("");
1080
+ for (const reason of run.verdict.reasons) lines.push(`- ${reason}`);
1081
+ }
1082
+ const failures = run.results.filter((r) => r.outputTail);
1083
+ if (failures.length) {
1084
+ lines.push("");
1085
+ lines.push("## Failure output");
1086
+ for (const r of failures) {
1087
+ lines.push("");
1088
+ lines.push(`### ${r.checkId}`);
1089
+ lines.push("");
1090
+ lines.push("```");
1091
+ lines.push(r.outputTail ?? "");
1092
+ lines.push("```");
1093
+ }
1094
+ }
1095
+ if (record) {
1096
+ lines.push("");
1097
+ lines.push(`**Evidence digest:** \`${record.digest}\``);
1098
+ lines.push(
1099
+ "_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._"
1100
+ );
1101
+ }
1102
+ lines.push("");
1103
+ lines.push(
1104
+ "_Generated by VerisKit. A `Partial` verdict means one or more capabilities did not run \u2014 it is not a pass._"
1105
+ );
1106
+ lines.push("");
1107
+ return lines.join("\n");
1108
+ }
1109
+ var STATE_LABEL;
1110
+ var init_markdown = __esm({
1111
+ "src/reporters/markdown.ts"() {
1112
+ "use strict";
1113
+ STATE_LABEL = {
1114
+ verified: "Verified",
1115
+ failed: "Failed",
1116
+ partial: "Partial"
1117
+ };
1118
+ }
1119
+ });
1120
+
1121
+ // src/project-graph/discover.ts
1122
+ import { readdirSync as readdirSync2, statSync } from "fs";
1123
+ import { join as join6, relative as relative2, sep } from "path";
1124
+ function toPosix(p) {
1125
+ return sep === "/" ? p : p.split(sep).join("/");
1126
+ }
1127
+ function classify(rel) {
1128
+ if (TEST_RE2.test(rel)) return "test";
1129
+ if (CONFIG_RE2.test(rel)) return "config";
1130
+ return "source";
1131
+ }
1132
+ function discoverFiles(root) {
1133
+ const out = [];
1134
+ const walk = (dir) => {
1135
+ let entries;
1136
+ try {
1137
+ entries = readdirSync2(dir);
1138
+ } catch {
1139
+ return;
1140
+ }
1141
+ for (const name of entries) {
1142
+ const abs = join6(dir, name);
1143
+ const rel = toPosix(relative2(root, abs));
1144
+ if (IGNORE.test(rel)) continue;
1145
+ let st;
1146
+ try {
1147
+ st = statSync(abs);
1148
+ } catch {
1149
+ continue;
1150
+ }
1151
+ if (st.isDirectory()) {
1152
+ walk(abs);
1153
+ } else if (CODE_RE.test(rel)) {
1154
+ out.push({ file: rel, kind: classify(rel) });
1155
+ }
1156
+ }
1157
+ };
1158
+ walk(root);
1159
+ return out.sort((a, b) => a.file.localeCompare(b.file));
1160
+ }
1161
+ var IGNORE, CODE_RE, TEST_RE2, CONFIG_RE2;
1162
+ var init_discover = __esm({
1163
+ "src/project-graph/discover.ts"() {
1164
+ "use strict";
1165
+ IGNORE = /(^|\/)(\.git|\.claude|\.veris|\.agentloop|\.agentflight|node_modules|dist|coverage|build|fixtures|__fixtures__)(\/|$)/;
1166
+ CODE_RE = /\.[cm]?[jt]sx?$/;
1167
+ TEST_RE2 = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|tests|__tests__)\//;
1168
+ CONFIG_RE2 = /(^|\/)([^/]+\.config\.[cm]?[jt]sx?)$/;
1169
+ }
1170
+ });
1171
+
1172
+ // src/project-graph/scanner-resolver.ts
1173
+ import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1174
+ import { dirname, join as join7, relative as relative3, resolve } from "path";
1175
+ function extractSpecifiers(text) {
1176
+ const specs = [];
1177
+ SPEC_RE.lastIndex = 0;
1178
+ let m;
1179
+ m = SPEC_RE.exec(text);
1180
+ while (m !== null) {
1181
+ const s = m[1] ?? m[2] ?? m[3] ?? m[4];
1182
+ if (s) specs.push(s);
1183
+ m = SPEC_RE.exec(text);
1184
+ }
1185
+ return specs;
1186
+ }
1187
+ function isFile(p) {
1188
+ try {
1189
+ return statSync2(p).isFile();
1190
+ } catch {
1191
+ return false;
1192
+ }
1193
+ }
1194
+ function resolveRelative(fromDir, spec) {
1195
+ const base = resolve(fromDir, spec);
1196
+ const candidates = [base];
1197
+ for (const ext of EXTS) candidates.push(base + ext);
1198
+ const extMatch = base.match(/\.[cm]?[jt]sx?$/);
1199
+ if (extMatch) {
1200
+ const noExt = base.slice(0, -extMatch[0].length);
1201
+ for (const ext of EXTS) candidates.push(noExt + ext);
1202
+ }
1203
+ for (const ext of EXTS) candidates.push(join7(base, `index${ext}`));
1204
+ for (const c of candidates) {
1205
+ if (existsSync3(c) && isFile(c)) return c;
1206
+ }
1207
+ return null;
1208
+ }
1209
+ function scannerImports(root, file) {
1210
+ let text;
1211
+ try {
1212
+ text = readFileSync2(join7(root, file), "utf8");
1213
+ } catch {
1214
+ return [];
1215
+ }
1216
+ const dir = dirname(join7(root, file));
1217
+ const out = /* @__PURE__ */ new Set();
1218
+ for (const spec of extractSpecifiers(text)) {
1219
+ if (!spec.startsWith(".")) continue;
1220
+ const resolved = resolveRelative(dir, spec);
1221
+ if (resolved) out.add(toPosix(relative3(root, resolved)));
1222
+ }
1223
+ return [...out];
1224
+ }
1225
+ var EXTS, SPEC_RE;
1226
+ var init_scanner_resolver = __esm({
1227
+ "src/project-graph/scanner-resolver.ts"() {
1228
+ "use strict";
1229
+ init_discover();
1230
+ EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
1231
+ SPEC_RE = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]|(?:^|[^.\w])import\s*['"]([^'"]+)['"]|\bimport\(\s*['"]([^'"]+)['"]\s*\)|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
1232
+ }
1233
+ });
1234
+
1235
+ // src/project-graph/ts-resolver.ts
1236
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1237
+ import { createRequire } from "module";
1238
+ import { join as join8, relative as relative4, sep as sep2 } from "path";
1239
+ function hasClassicApi(mod) {
1240
+ const m = mod;
1241
+ 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;
1242
+ }
1243
+ function loadTypeScript(root) {
1244
+ try {
1245
+ const require2 = createRequire(join8(root, "__veris__.js"));
1246
+ const tsPath = require2.resolve("typescript");
1247
+ const mod = require2(tsPath);
1248
+ return hasClassicApi(mod) ? mod : null;
1249
+ } catch {
1250
+ return null;
1251
+ }
1252
+ }
1253
+ function loadCompilerOptions(tsmod, root) {
1254
+ const configPath = join8(root, "tsconfig.json");
1255
+ const read = tsmod.readConfigFile(configPath, tsmod.sys.readFile);
1256
+ if (read.error || !read.config) return {};
1257
+ const parsed = tsmod.parseJsonConfigFileContent(read.config, tsmod.sys, root);
1258
+ return parsed.options;
1259
+ }
1260
+ function tsImports(tsmod, root, options, file) {
1261
+ let text;
1262
+ try {
1263
+ text = readFileSync3(join8(root, file), "utf8");
1264
+ } catch {
1265
+ return [];
1266
+ }
1267
+ const abs = join8(root, file);
1268
+ const out = /* @__PURE__ */ new Set();
1269
+ try {
1270
+ const pre = tsmod.preProcessFile(text, true, true);
1271
+ for (const imp of pre.importedFiles) {
1272
+ const res = tsmod.resolveModuleName(
1273
+ imp.fileName,
1274
+ abs,
1275
+ options,
1276
+ tsmod.sys
1277
+ );
1278
+ const resolved = res.resolvedModule?.resolvedFileName;
1279
+ if (resolved && !resolved.includes("node_modules") && resolved.startsWith(root + sep2)) {
1280
+ out.add(toPosix(relative4(root, resolved)));
1281
+ }
1282
+ }
1283
+ } catch {
1284
+ }
1285
+ return [...out];
1286
+ }
1287
+ function selectResolver(root) {
1288
+ const tsmod = existsSync4(join8(root, "tsconfig.json")) ? loadTypeScript(root) : null;
1289
+ if (tsmod) {
1290
+ try {
1291
+ const options = loadCompilerOptions(tsmod, root);
1292
+ return {
1293
+ resolver: "typescript",
1294
+ importsOf: (file) => tsImports(tsmod, root, options, file)
1295
+ };
1296
+ } catch {
1297
+ }
1298
+ }
1299
+ return {
1300
+ resolver: "scanner",
1301
+ importsOf: (file) => scannerImports(root, file)
1302
+ };
1303
+ }
1304
+ var init_ts_resolver = __esm({
1305
+ "src/project-graph/ts-resolver.ts"() {
1306
+ "use strict";
1307
+ init_discover();
1308
+ init_scanner_resolver();
1309
+ }
1310
+ });
1311
+
1312
+ // src/project-graph/graph.ts
1313
+ var graph_exports = {};
1314
+ __export(graph_exports, {
1315
+ buildGraph: () => buildGraph
1316
+ });
1317
+ async function buildGraph(project) {
1318
+ const root = project.root;
1319
+ const files = discoverFiles(root);
1320
+ const known = new Set(files.map((f) => f.file));
1321
+ const { resolver, importsOf } = selectResolver(root);
1322
+ const nodes = {};
1323
+ for (const f of files) {
1324
+ nodes[f.file] = { file: f.file, kind: f.kind, imports: [], importedBy: [] };
1325
+ }
1326
+ for (const f of files) {
1327
+ const imps = importsOf(f.file).filter((i) => known.has(i) && i !== f.file);
1328
+ nodes[f.file].imports = imps;
1329
+ for (const i of imps) nodes[i]?.importedBy.push(f.file);
1330
+ }
1331
+ return {
1332
+ root,
1333
+ resolver,
1334
+ nodes,
1335
+ sourceFiles: files.filter((f) => f.kind === "source").map((f) => f.file),
1336
+ testFiles: files.filter((f) => f.kind === "test").map((f) => f.file)
1337
+ };
1338
+ }
1339
+ var init_graph = __esm({
1340
+ "src/project-graph/graph.ts"() {
1341
+ "use strict";
1342
+ init_discover();
1343
+ init_ts_resolver();
1344
+ }
1345
+ });
1346
+
1347
+ // src/project-graph/analyze.ts
1348
+ function transitiveDependents(graph, file) {
1349
+ const seen = /* @__PURE__ */ new Set();
1350
+ const stack = [...graph.nodes[file]?.importedBy ?? []];
1351
+ while (stack.length) {
1352
+ const f = stack.pop();
1353
+ if (!f || seen.has(f)) continue;
1354
+ seen.add(f);
1355
+ for (const d of graph.nodes[f]?.importedBy ?? []) stack.push(d);
1356
+ }
1357
+ return seen;
1358
+ }
1359
+ function reachableFromTests(graph) {
1360
+ const seen = /* @__PURE__ */ new Set();
1361
+ const stack = [...graph.testFiles];
1362
+ while (stack.length) {
1363
+ const f = stack.pop();
1364
+ if (!f || seen.has(f)) continue;
1365
+ seen.add(f);
1366
+ for (const i of graph.nodes[f]?.imports ?? []) stack.push(i);
1367
+ }
1368
+ return seen;
1369
+ }
1370
+ function analyze(graph, changed = []) {
1371
+ const blastRadius = {};
1372
+ for (const file of Object.keys(graph.nodes)) {
1373
+ blastRadius[file] = transitiveDependents(graph, file).size;
1374
+ }
1375
+ const reached = reachableFromTests(graph);
1376
+ const byBlast = (a, b) => (blastRadius[b] ?? 0) - (blastRadius[a] ?? 0);
1377
+ const untested = graph.sourceFiles.filter((f) => !reached.has(f)).sort(byBlast);
1378
+ const changedSet = new Set(changed);
1379
+ const untestedSet = new Set(untested);
1380
+ const risky = graph.sourceFiles.filter(
1381
+ (f) => (blastRadius[f] ?? 0) > 0 && (untestedSet.has(f) || changedSet.has(f))
1382
+ ).sort(byBlast);
1383
+ return { untested, blastRadius, risky };
1384
+ }
1385
+ var init_analyze = __esm({
1386
+ "src/project-graph/analyze.ts"() {
1387
+ "use strict";
1388
+ }
1389
+ });
1390
+
1391
+ // src/affected/select.ts
1392
+ var select_exports = {};
1393
+ __export(select_exports, {
1394
+ selectAffectedTests: () => selectAffectedTests
1395
+ });
1396
+ function selectAffectedTests(graph, changed) {
1397
+ if (graph.resolver !== "typescript") {
1398
+ return {
1399
+ mode: "full",
1400
+ testFiles: [],
1401
+ reason: "scanner graph can miss aliased/subpath imports \u2014 running the full suite"
1402
+ };
1403
+ }
1404
+ for (const f of changed) {
1405
+ if (GLOBAL_RE.test(f)) {
1406
+ return {
1407
+ mode: "full",
1408
+ testFiles: [],
1409
+ reason: `global/config change (${f})`
1410
+ };
1411
+ }
1412
+ if (!(f in graph.nodes)) {
1413
+ return {
1414
+ mode: "full",
1415
+ testFiles: [],
1416
+ reason: `unresolved changed file (${f})`
1417
+ };
1418
+ }
1419
+ }
1420
+ const tests = /* @__PURE__ */ new Set();
1421
+ for (const f of changed) {
1422
+ if (graph.nodes[f]?.kind === "test") tests.add(f);
1423
+ for (const dep of transitiveDependents(graph, f)) {
1424
+ if (graph.nodes[dep]?.kind === "test") tests.add(dep);
1425
+ }
1426
+ }
1427
+ if (tests.size === 0) {
1428
+ return {
1429
+ mode: "full",
1430
+ testFiles: [],
1431
+ reason: "no tests reach the changed files"
1432
+ };
1433
+ }
1434
+ const selected = [...tests].sort();
1435
+ if (selected.some((t) => t.startsWith("-"))) {
1436
+ return {
1437
+ mode: "full",
1438
+ testFiles: [],
1439
+ reason: "a reaching test path starts with '-' (unsafe as a CLI argument)"
1440
+ };
1441
+ }
1442
+ return { mode: "graph", testFiles: selected, reason: "" };
1443
+ }
1444
+ var GLOBAL_RE;
1445
+ var init_select = __esm({
1446
+ "src/affected/select.ts"() {
1447
+ "use strict";
1448
+ init_analyze();
1449
+ GLOBAL_RE = /(^|\/)(tsconfig[^/]*\.json|package\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|veris\.config\.[^/]+|[^/]+\.config\.[cm]?[jt]sx?|[^/]+\.setup\.[cm]?[jt]sx?)$/;
1450
+ }
1451
+ });
1452
+
1453
+ // src/core/orchestrate.ts
1454
+ function resolveChecks(configChecks, project, opts) {
1455
+ let checks = configChecks?.length ? configChecks : DEFAULT_CHECKS;
1456
+ if (opts.browser && !checks.includes("browser")) {
1457
+ const cap = project.capabilities.find((c) => c.id === "browser");
1458
+ if (cap?.available) checks = [...checks, "browser"];
1459
+ }
1460
+ return checks;
1461
+ }
1462
+ async function verifyProject(root, opts = {}) {
1463
+ const project = await detectProject(root);
1464
+ const config = await loadConfig(root);
1465
+ const checks = resolveChecks(config?.checks, project, opts);
1466
+ const run = await runChecks(project, checks, root);
1467
+ const git = await gitAnchor(root);
1468
+ const logDigests = await digestLogs(run);
1469
+ const record = buildRecord(run, git, logDigests, VERSION);
1470
+ const reportRef = await writeReport(
1471
+ root,
1472
+ run.id,
1473
+ renderMarkdown(run, record)
1474
+ );
1475
+ run.reportRef = reportRef;
1476
+ const runDir = await createRunDir(root, run.id);
1477
+ await writeEvidence(runDir, record);
1478
+ return { run, record };
1479
+ }
1480
+ async function affectedProject(root, opts = {}) {
1481
+ const project = await detectProject(root);
1482
+ const { files } = await changedFiles(root, { base: opts.base });
1483
+ const plan = affectedChecks(files, project);
1484
+ if (plan.checks.length === 0) {
1485
+ return {
1486
+ note: `Nothing affected \u2014 ${files.length} changed file(s), no checks to run.`,
1487
+ changedCount: files.length,
1488
+ nothingAffected: true
1489
+ };
1490
+ }
1491
+ let targetFiles;
1492
+ let note = "";
1493
+ const unitRunner = project.capabilities.find((c) => c.id === "unit")?.runner;
1494
+ const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
1495
+ if (plan.checks.includes("unit") && canNarrow) {
1496
+ const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
1497
+ const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
1498
+ const graph = await buildGraph2(project);
1499
+ const sel = selectAffectedTests2(graph, files);
1500
+ if (sel.mode === "graph") {
1501
+ targetFiles = { unit: sel.testFiles };
1502
+ note = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
1503
+ } else {
1504
+ note = `unit ran in full \u2014 ${sel.reason}`;
1505
+ }
1506
+ } else if (plan.checks.includes("unit") && unitRunner) {
1507
+ note = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
1508
+ }
1509
+ const run = await runChecks(project, plan.checks, root, { targetFiles });
1510
+ run.scope = { kind: "affected", changedCount: files.length };
1511
+ const affected = new Set(plan.checks);
1512
+ for (const cap of project.capabilities) {
1513
+ if (cap.available && cap.id !== "browser" && !affected.has(cap.id)) {
1514
+ run.results.push({
1515
+ checkId: cap.id,
1516
+ status: "skipped",
1517
+ durationMs: 0,
1518
+ summary: "not affected by changes"
1519
+ });
1520
+ }
1521
+ }
1522
+ const git = await gitAnchor(root);
1523
+ const logDigests = await digestLogs(run);
1524
+ const record = buildRecord(run, git, logDigests, VERSION);
1525
+ const reportRef = await writeReport(
1526
+ root,
1527
+ run.id,
1528
+ renderMarkdown(run, record)
1529
+ );
1530
+ run.reportRef = reportRef;
1531
+ const runDir = await createRunDir(root, run.id);
1532
+ await writeEvidence(runDir, record);
1533
+ return {
1534
+ run,
1535
+ record,
1536
+ note,
1537
+ changedCount: files.length,
1538
+ nothingAffected: false
1539
+ };
1540
+ }
1541
+ var DEFAULT_CHECKS;
1542
+ var init_orchestrate = __esm({
1543
+ "src/core/orchestrate.ts"() {
1544
+ "use strict";
1545
+ init_gate();
1546
+ init_detect();
1547
+ init_load();
1548
+ init_record();
1549
+ init_store();
1550
+ init_changes();
1551
+ init_markdown();
1552
+ init_version();
1553
+ init_orchestrator();
1554
+ DEFAULT_CHECKS = ["types", "lint", "unit"];
1555
+ }
1556
+ });
1557
+
1558
+ // src/publish/comment.ts
1559
+ function renderComment(run, record) {
1560
+ const summary = run.results.filter((r) => r.status !== "skipped").map((r) => `${r.checkId} ${r.status}`).join(" \xB7 ");
1561
+ return [
1562
+ MARKER,
1563
+ `**VerisKit: ${STATE_LABEL2[run.verdict.state]}**`,
1564
+ "",
1565
+ summary,
1566
+ "",
1567
+ "<details><summary>Report</summary>",
1568
+ "",
1569
+ renderMarkdown(run, record),
1570
+ "",
1571
+ "</details>",
1572
+ ""
1573
+ ].join("\n");
1574
+ }
1575
+ var MARKER, STATE_LABEL2;
1576
+ var init_comment = __esm({
1577
+ "src/publish/comment.ts"() {
1578
+ "use strict";
1579
+ init_markdown();
1580
+ MARKER = "<!-- veriskit-report -->";
1581
+ STATE_LABEL2 = {
1582
+ verified: "verified",
1583
+ failed: "failed",
1584
+ partial: "partial"
1585
+ };
1586
+ }
1587
+ });
1588
+
1589
+ // src/publish/context.ts
1590
+ import { readFileSync as readFileSync4 } from "fs";
1591
+ function defaultReadEvent(path) {
1592
+ try {
1593
+ return JSON.parse(readFileSync4(path, "utf8"));
1594
+ } catch {
1595
+ return null;
1596
+ }
1597
+ }
1598
+ function resolvePublishContext(env = process.env, readEvent = defaultReadEvent) {
1599
+ const repository = env.GITHUB_REPOSITORY;
1600
+ const token = env.GITHUB_TOKEN;
1601
+ if (!repository || !token) return null;
1602
+ const [owner, repo] = repository.split("/");
1603
+ if (!owner || !repo) return null;
1604
+ let prNumber = null;
1605
+ let headSha = env.GITHUB_SHA ?? "";
1606
+ const refMatch = (env.GITHUB_REF ?? "").match(/^refs\/pull\/(\d+)\//);
1607
+ if (refMatch) prNumber = Number(refMatch[1]);
1608
+ const event = readEvent(env.GITHUB_EVENT_PATH ?? "");
1609
+ const pr = event?.pull_request;
1610
+ if (pr) {
1611
+ if (prNumber === null && typeof pr.number === "number") {
1612
+ prNumber = pr.number;
1613
+ }
1614
+ if (pr.head?.sha) headSha = pr.head.sha;
1615
+ }
1616
+ if (prNumber === null || !headSha) return null;
1617
+ return { owner, repo, token, prNumber, headSha };
1618
+ }
1619
+ var init_context = __esm({
1620
+ "src/publish/context.ts"() {
1621
+ "use strict";
1622
+ }
1623
+ });
1624
+
1625
+ // src/publish/github.ts
1626
+ function headers(token) {
1627
+ return {
1628
+ Authorization: `Bearer ${token}`,
1629
+ Accept: "application/vnd.github+json",
1630
+ "X-GitHub-Api-Version": "2022-11-28",
1631
+ "User-Agent": "veriskit",
1632
+ "Content-Type": "application/json"
1633
+ };
1634
+ }
1635
+ async function gh(method, url, token, body) {
1636
+ const res = await fetch(url, {
1637
+ method,
1638
+ headers: headers(token),
1639
+ body: body === void 0 ? void 0 : JSON.stringify(body)
1640
+ });
1641
+ if (!res.ok) {
1642
+ const path = url.replace("https://api.github.com", "");
1643
+ let detail = "";
1644
+ try {
1645
+ const errBody = await res.json();
1646
+ if (errBody?.message) detail = `: ${errBody.message}`;
1647
+ } catch {
1648
+ }
1649
+ throw new GitHubApiError(
1650
+ res.status,
1651
+ `GitHub API ${method} ${path} -> ${res.status}${detail}`
1652
+ );
1653
+ }
1654
+ return res.status === 204 ? null : res.json();
1655
+ }
1656
+ function repoBase(ctx) {
1657
+ return `https://api.github.com/repos/${ctx.owner}/${ctx.repo}`;
1658
+ }
1659
+ async function upsertComment(ctx, body) {
1660
+ const base = repoBase(ctx);
1661
+ const comments = await gh(
1662
+ "GET",
1663
+ `${base}/issues/${ctx.prNumber}/comments?per_page=100`,
1664
+ ctx.token
1665
+ );
1666
+ const existing = comments.find((c) => (c.body ?? "").includes(MARKER));
1667
+ if (existing) {
1668
+ await gh("PATCH", `${base}/issues/comments/${existing.id}`, ctx.token, {
1669
+ body
1670
+ });
1671
+ } else {
1672
+ await gh("POST", `${base}/issues/${ctx.prNumber}/comments`, ctx.token, {
1673
+ body
1674
+ });
1675
+ }
1676
+ }
1677
+ async function createCheckRun(ctx, opts) {
1678
+ await gh("POST", `${repoBase(ctx)}/check-runs`, ctx.token, {
1679
+ name: opts.name,
1680
+ head_sha: ctx.headSha,
1681
+ status: "completed",
1682
+ conclusion: opts.conclusion,
1683
+ output: { title: opts.title, summary: opts.summary }
1684
+ });
1685
+ }
1686
+ var GitHubApiError;
1687
+ var init_github = __esm({
1688
+ "src/publish/github.ts"() {
1689
+ "use strict";
1690
+ init_comment();
1691
+ GitHubApiError = class extends Error {
1692
+ status;
1693
+ constructor(status, message) {
1694
+ super(message);
1695
+ this.name = "GitHubApiError";
1696
+ this.status = status;
1697
+ }
1698
+ };
1699
+ }
1700
+ });
1701
+
1702
+ // src/publish/publish.ts
1703
+ async function publishToGitHub(run, record) {
1704
+ const ctx = resolvePublishContext();
1705
+ if (!ctx) {
1706
+ process.stdout.write(
1707
+ "veris: --github set but no GitHub PR context found (need GITHUB_TOKEN + a pull_request event); skipping publish.\n"
1708
+ );
1709
+ return;
1710
+ }
1711
+ try {
1712
+ await upsertComment(ctx, renderComment(run, record));
1713
+ await createCheckRun(ctx, {
1714
+ name: "VerisKit",
1715
+ conclusion: CONCLUSION[run.verdict.state],
1716
+ title: `VerisKit: ${run.verdict.state}`,
1717
+ summary: renderMarkdown(run, record)
1718
+ });
1719
+ process.stdout.write(
1720
+ `veris: published the verdict to PR #${ctx.prNumber}
1721
+ `
1722
+ );
1723
+ } catch (err) {
1724
+ const msg = err instanceof Error ? err.message : String(err);
1725
+ process.stderr.write(`veris: GitHub publish failed: ${msg}
1726
+ `);
1727
+ }
1728
+ }
1729
+ var CONCLUSION;
1730
+ var init_publish = __esm({
1731
+ "src/publish/publish.ts"() {
1732
+ "use strict";
1733
+ init_markdown();
1734
+ init_comment();
1735
+ init_context();
1736
+ init_github();
1737
+ CONCLUSION = {
1738
+ verified: "success",
1739
+ failed: "failure",
1740
+ partial: "neutral"
1741
+ };
1742
+ }
1743
+ });
1744
+
1745
+ // src/cli/commands/verify.ts
1746
+ var verify_exports = {};
1747
+ __export(verify_exports, {
1748
+ runVerify: () => runVerify
1749
+ });
1750
+ async function runVerify(root, opts = {}) {
1751
+ const { run, record } = await verifyProject(root, opts);
1752
+ process.stdout.write(`${renderRun(run, record)}
1753
+ `);
1754
+ if (opts.github) await publishToGitHub(run, record);
1755
+ return verdictExitCode(run.verdict, opts);
1756
+ }
1757
+ var init_verify = __esm({
1758
+ "src/cli/commands/verify.ts"() {
1759
+ "use strict";
1760
+ init_orchestrate();
1761
+ init_verdict();
1762
+ init_publish();
1763
+ init_terminal();
1764
+ }
1765
+ });
1766
+
1767
+ // src/cli/commands/report.ts
1768
+ var report_exports = {};
1769
+ __export(report_exports, {
1770
+ runReport: () => runReport
1771
+ });
1772
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
1773
+ import { join as join9 } from "path";
1774
+ async function runReport(root) {
1775
+ const dir = join9(root, ".veris", "reports");
1776
+ let files;
1777
+ try {
1778
+ files = readdirSync3(dir).filter((f) => f.endsWith(".md")).sort();
1779
+ } catch {
1780
+ process.stdout.write("No reports yet. Run `veris verify` first.\n");
1781
+ return 0;
1782
+ }
1783
+ const latest = files.at(-1);
1784
+ if (!latest) {
1785
+ process.stdout.write("No reports yet. Run `veris verify` first.\n");
1786
+ return 0;
1787
+ }
1788
+ process.stdout.write(`${readFileSync5(join9(dir, latest), "utf8")}
1789
+ `);
1790
+ return 0;
1791
+ }
1792
+ var init_report = __esm({
1793
+ "src/cli/commands/report.ts"() {
1794
+ "use strict";
1795
+ }
1796
+ });
1797
+
1798
+ // src/cli/commands/affected.ts
1799
+ var affected_exports = {};
1800
+ __export(affected_exports, {
1801
+ runAffected: () => runAffected
1802
+ });
1803
+ async function runAffected(root, opts = {}) {
1804
+ const outcome = await affectedProject(root, opts);
1805
+ if (outcome.nothingAffected || !outcome.run || !outcome.record) {
1806
+ process.stdout.write(`${outcome.note}
1807
+ `);
1808
+ return 0;
1809
+ }
1810
+ const { run, record, note } = outcome;
1811
+ process.stdout.write(`${renderRun(run, record)}
1812
+ `);
1813
+ if (note) process.stdout.write(`${note}
1814
+ `);
1815
+ if (opts.github) await publishToGitHub(run, record);
1816
+ return verdictExitCode(run.verdict, opts);
1817
+ }
1818
+ var init_affected = __esm({
1819
+ "src/cli/commands/affected.ts"() {
1820
+ "use strict";
1821
+ init_orchestrate();
1822
+ init_verdict();
1823
+ init_publish();
1824
+ init_terminal();
1825
+ }
1826
+ });
1827
+
1828
+ // src/watch/watcher.ts
1829
+ import {
1830
+ watch as fsWatch,
1831
+ readdirSync as readdirSync4,
1832
+ statSync as statSync3
1833
+ } from "fs";
1834
+ import { basename as basename2, join as join10, relative as relative5 } from "path";
1835
+ function watch(root, opts, onBatch) {
1836
+ const debounceMs = opts.debounceMs ?? 150;
1837
+ const rootBase = basename2(root);
1838
+ let pending = /* @__PURE__ */ new Set();
1839
+ let timer = null;
1840
+ const flush = () => {
1841
+ const batch = [...pending];
1842
+ pending = /* @__PURE__ */ new Set();
1843
+ timer = null;
1844
+ if (batch.length) onBatch(batch);
1845
+ };
1846
+ const schedule = (rel) => {
1847
+ if (!rel || rel === rootBase || IGNORE2.test(rel)) return;
1848
+ pending.add(rel);
1849
+ if (timer) clearTimeout(timer);
1850
+ timer = setTimeout(flush, debounceMs);
1851
+ };
1852
+ if (opts.poll) {
1853
+ const interval = opts.pollIntervalMs ?? 400;
1854
+ const mtimes = scanMtimes(root);
1855
+ const tick = setInterval(() => {
1856
+ const next = scanMtimes(root);
1857
+ for (const [p, m] of next) {
1858
+ if (mtimes.get(p) !== m) schedule(p);
1859
+ }
1860
+ mtimes.clear();
1861
+ for (const [p, m] of next) mtimes.set(p, m);
1862
+ }, interval);
1863
+ return () => {
1864
+ clearInterval(tick);
1865
+ if (timer) clearTimeout(timer);
1866
+ };
1867
+ }
1868
+ let watcher;
1869
+ try {
1870
+ watcher = fsWatch(root, { recursive: true }, (_event, filename) => {
1871
+ if (filename) schedule(filename.toString());
1872
+ });
1873
+ } catch {
1874
+ throw new Error(
1875
+ "recursive fs.watch is unavailable on this platform; rerun with --poll"
1876
+ );
1877
+ }
1878
+ return () => {
1879
+ if (timer) clearTimeout(timer);
1880
+ watcher.close();
1881
+ };
1882
+ }
1883
+ function scanMtimes(root) {
1884
+ const out = /* @__PURE__ */ new Map();
1885
+ const walk = (dir) => {
1886
+ let entries;
1887
+ try {
1888
+ entries = readdirSync4(dir);
1889
+ } catch {
1890
+ return;
1891
+ }
1892
+ for (const name of entries) {
1893
+ const abs = join10(dir, name);
1894
+ const rel = relative5(root, abs);
1895
+ if (IGNORE2.test(rel)) continue;
1896
+ let st;
1897
+ try {
1898
+ st = statSync3(abs);
1899
+ } catch {
1900
+ continue;
1901
+ }
1902
+ if (st.isDirectory()) walk(abs);
1903
+ else out.set(rel, st.mtimeMs);
1904
+ }
1905
+ };
1906
+ walk(root);
1907
+ return out;
1908
+ }
1909
+ var IGNORE2;
1910
+ var init_watcher = __esm({
1911
+ "src/watch/watcher.ts"() {
1912
+ "use strict";
1913
+ IGNORE2 = /(^|\/)(\.git|\.veris|\.agentloop|\.agentflight|node_modules|dist)(\/|$)/;
1914
+ }
1915
+ });
1916
+
1917
+ // src/cli/commands/watch.ts
1918
+ var watch_exports = {};
1919
+ __export(watch_exports, {
1920
+ buildWatchResults: () => buildWatchResults,
1921
+ runWatch: () => runWatch
1922
+ });
1923
+ function buildWatchResults(project, affected, fresh, cache) {
1924
+ const affectedSet = new Set(affected);
1925
+ const freshById = new Map(fresh.map((r) => [r.checkId, r]));
1926
+ const out = [];
1927
+ for (const cap of project.capabilities) {
1928
+ if (!cap.available || cap.id === "browser") continue;
1929
+ const f = freshById.get(cap.id);
1930
+ if (f) {
1931
+ out.push({ ...f, cached: false });
1932
+ continue;
1933
+ }
1934
+ const c = cache.get(cap.id);
1935
+ if (c && !affectedSet.has(cap.id)) {
1936
+ out.push({ ...c, cached: true });
1937
+ continue;
1938
+ }
1939
+ out.push({
1940
+ checkId: cap.id,
1941
+ status: "skipped",
1942
+ durationMs: 0,
1943
+ summary: "not affected by changes"
1944
+ });
1945
+ }
1946
+ return out;
1947
+ }
1948
+ async function runWatch(root, opts = {}) {
1949
+ const cache = /* @__PURE__ */ new Map();
1950
+ let running = false;
1951
+ const availableIds = (project) => project.capabilities.filter((c) => c.available && c.id !== "browser").map((c) => c.id);
1952
+ const tick = async (initial) => {
1953
+ if (running) return;
1954
+ running = true;
1955
+ try {
1956
+ const project = await detectProject(root);
1957
+ const { files } = await changedFiles(root);
1958
+ const checks = initial ? availableIds(project) : affectedChecks(files, project).checks;
1959
+ let targetFiles;
1960
+ let narrowedNote = "";
1961
+ const unitRunner = project.capabilities.find(
1962
+ (c) => c.id === "unit"
1963
+ )?.runner;
1964
+ const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
1965
+ if (!initial && checks.includes("unit") && canNarrow) {
1966
+ const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
1967
+ const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
1968
+ const graph = await buildGraph2(project);
1969
+ const sel = selectAffectedTests2(graph, files);
1970
+ if (sel.mode === "graph") {
1971
+ targetFiles = { unit: sel.testFiles };
1972
+ narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
1973
+ } else {
1974
+ narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
1975
+ }
1976
+ } else if (!initial && checks.includes("unit") && unitRunner) {
1977
+ narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
1978
+ }
1979
+ let fresh = [];
1980
+ if (checks.length) {
1981
+ const r = await runChecks(project, checks, root, { targetFiles });
1982
+ fresh = r.results;
1983
+ for (const result of fresh) cache.set(result.checkId, { ...result });
1984
+ }
1985
+ const results = buildWatchResults(project, checks, fresh, cache);
1986
+ const availableCaps = project.capabilities.filter(
1987
+ (c) => c.available && c.id !== "browser"
1988
+ );
1989
+ const verdict = computeVerdict(results, availableCaps);
1990
+ const run = {
1991
+ id: "watch",
1992
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1993
+ project,
1994
+ results,
1995
+ verdict,
1996
+ env: {
1997
+ os: process.platform,
1998
+ node: process.version,
1999
+ pm: project.packageManager,
2000
+ ci: false,
2001
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2002
+ },
2003
+ scope: { kind: "watch", changedCount: files.length }
2004
+ };
2005
+ process.stdout.write(`${renderRun(run)}
2006
+ `);
2007
+ if (narrowedNote) process.stdout.write(`${narrowedNote}
2008
+ `);
2009
+ } finally {
2010
+ running = false;
2011
+ }
2012
+ };
2013
+ process.stdout.write("veris watch \u2014 press Ctrl-C to stop\n");
2014
+ await tick(true);
2015
+ return await new Promise((resolve2) => {
2016
+ let stop;
2017
+ try {
2018
+ stop = watch(root, { poll: opts.poll }, () => {
2019
+ void tick(false);
2020
+ });
2021
+ } catch (err) {
2022
+ process.stderr.write(
2023
+ `veris: ${err instanceof Error ? err.message : String(err)}
2024
+ `
2025
+ );
2026
+ resolve2(1);
2027
+ return;
2028
+ }
2029
+ process.on("SIGINT", () => {
2030
+ stop();
2031
+ process.stdout.write("\nStopped.\n");
2032
+ resolve2(0);
2033
+ });
2034
+ });
2035
+ }
2036
+ var init_watch = __esm({
2037
+ "src/cli/commands/watch.ts"() {
2038
+ "use strict";
2039
+ init_gate();
2040
+ init_detect();
2041
+ init_orchestrator();
2042
+ init_verdict();
2043
+ init_changes();
2044
+ init_terminal();
2045
+ init_watcher();
2046
+ }
2047
+ });
2048
+
2049
+ // src/cli/commands/scan.ts
2050
+ var scan_exports = {};
2051
+ __export(scan_exports, {
2052
+ renderScan: () => renderScan,
2053
+ runScan: () => runScan
2054
+ });
2055
+ import { writeFile as writeFile3 } from "fs/promises";
2056
+ import { join as join11 } from "path";
2057
+ import pc3 from "picocolors";
2058
+ function renderScan(graph, analysis) {
2059
+ const plain = isPlain();
2060
+ const bold = (s) => plain ? s : pc3.bold(s);
2061
+ const dim = (s) => plain ? s : pc3.dim(s);
2062
+ const warn = (s) => plain ? s : pc3.yellow(s);
2063
+ const lines = [];
2064
+ lines.push(bold("VerisKit \u2014 scan"));
2065
+ lines.push("");
2066
+ lines.push(
2067
+ `Resolver ${graph.resolver}${graph.resolver === "scanner" ? dim(" (no TypeScript found \u2014 relative imports only)") : ""}`
2068
+ );
2069
+ lines.push(
2070
+ `Modules ${Object.keys(graph.nodes).length} \xB7 Source ${graph.sourceFiles.length} \xB7 Tests ${graph.testFiles.length}`
2071
+ );
2072
+ lines.push("");
2073
+ lines.push("Untested (top by impact)");
2074
+ const top = analysis.untested.slice(0, 10);
2075
+ if (top.length === 0)
2076
+ lines.push(dim(" none \u2014 every source file is reached by a test"));
2077
+ for (const f of top) {
2078
+ lines.push(
2079
+ ` ${warn("!")} ${f} ${dim(`${analysis.blastRadius[f] ?? 0} dependents`)}`
2080
+ );
2081
+ }
2082
+ return lines.join("\n");
2083
+ }
2084
+ async function runScan(root) {
2085
+ const project = await detectProject(root);
2086
+ const graph = await buildGraph(project);
2087
+ const analysis = analyze(graph);
2088
+ const dir = join11(root, ".veris");
2089
+ await ensureDir(dir);
2090
+ await writeFile3(
2091
+ join11(dir, "graph.json"),
2092
+ JSON.stringify({ resolver: graph.resolver, nodes: graph.nodes }, null, 2),
2093
+ "utf8"
2094
+ );
2095
+ process.stdout.write(`${renderScan(graph, analysis)}
2096
+ `);
2097
+ process.stdout.write(`
2098
+ Graph ${join11(".veris", "graph.json")}
2099
+ `);
2100
+ return 0;
2101
+ }
2102
+ var init_scan = __esm({
2103
+ "src/cli/commands/scan.ts"() {
2104
+ "use strict";
2105
+ init_detect();
2106
+ init_analyze();
2107
+ init_graph();
2108
+ init_fs_safe();
2109
+ init_tty();
2110
+ }
2111
+ });
2112
+
2113
+ // src/cli/commands/plan.ts
2114
+ var plan_exports = {};
2115
+ __export(plan_exports, {
2116
+ renderPlan: () => renderPlan,
2117
+ runPlan: () => runPlan
2118
+ });
2119
+ import pc4 from "picocolors";
2120
+ function renderPlan(project, graph, analysis, changed) {
2121
+ const plain = isPlain();
2122
+ const bold = (s) => plain ? s : pc4.bold(s);
2123
+ const dim = (s) => plain ? s : pc4.dim(s);
2124
+ const warn = (s) => plain ? s : pc4.yellow(s);
2125
+ const lines = [];
2126
+ lines.push(bold("VerisKit \u2014 plan"));
2127
+ lines.push(dim(`(graph via ${graph.resolver})`));
2128
+ lines.push("");
2129
+ lines.push("Test these first (high impact, untested)");
2130
+ const top = analysis.untested.slice(0, 5);
2131
+ if (top.length === 0)
2132
+ lines.push(
2133
+ dim(
2134
+ " none \u2014 untested files have no dependents or all files are covered"
2135
+ )
2136
+ );
2137
+ top.forEach((f, i) => {
2138
+ lines.push(
2139
+ ` ${i + 1}. ${f} ${dim(`(${analysis.blastRadius[f] ?? 0} dependents, no test reaches it)`)}`
2140
+ );
2141
+ });
2142
+ lines.push("");
2143
+ lines.push("Verification setup");
2144
+ const weak = project.capabilities.filter(
2145
+ (c) => !c.available && c.id !== "browser"
2146
+ );
2147
+ if (weak.length === 0)
2148
+ lines.push(dim(" \u2713 types, lint, and unit are all configured"));
2149
+ for (const c of weak)
2150
+ lines.push(
2151
+ ` ${warn("\u26A0")} ${c.id} capability unavailable \u2014 ${c.reason ?? "not configured"}`
2152
+ );
2153
+ if (changed.length) {
2154
+ lines.push("");
2155
+ lines.push(`Risky changes (${changed.length} changed file(s))`);
2156
+ if (analysis.risky.length === 0) lines.push(dim(" none flagged"));
2157
+ for (const f of analysis.risky.slice(0, 8)) {
2158
+ const untested = analysis.untested.includes(f) ? ", untested" : "";
2159
+ lines.push(
2160
+ ` ${warn("!")} ${f} ${dim(`${analysis.blastRadius[f] ?? 0} dependents${untested}`)}`
2161
+ );
2162
+ }
2163
+ }
2164
+ lines.push("");
2165
+ lines.push(
2166
+ dim("Next: `veris affected` runs only the tests that reach your changes.")
2167
+ );
2168
+ return lines.join("\n");
2169
+ }
2170
+ async function runPlan(root, opts = {}) {
2171
+ const project = await detectProject(root);
2172
+ const graph = await buildGraph(project);
2173
+ const { files } = await changedFiles(root, { base: opts.base });
2174
+ const analysis = analyze(graph, files);
2175
+ process.stdout.write(`${renderPlan(project, graph, analysis, files)}
2176
+ `);
2177
+ return 0;
2178
+ }
2179
+ var init_plan = __esm({
2180
+ "src/cli/commands/plan.ts"() {
2181
+ "use strict";
2182
+ init_detect();
2183
+ init_changes();
2184
+ init_analyze();
2185
+ init_graph();
2186
+ init_tty();
2187
+ }
2188
+ });
2189
+
2190
+ // src/evidence/signing.ts
2191
+ import {
2192
+ createHash as createHash2,
2193
+ createPrivateKey,
2194
+ createPublicKey,
2195
+ sign as cryptoSign,
2196
+ verify as cryptoVerify,
2197
+ generateKeyPairSync
2198
+ } from "crypto";
2199
+ function derOf(publicKey) {
2200
+ return publicKey.export({ type: "spki", format: "der" });
2201
+ }
2202
+ function keyId(publicKey) {
2203
+ const key = typeof publicKey === "string" ? createPublicKey(publicKey) : publicKey;
2204
+ return createHash2("sha256").update(derOf(key)).digest("hex").slice(0, 8);
2205
+ }
2206
+ function signatureKeyId(sig) {
2207
+ const key = createPublicKey({
2208
+ key: Buffer.from(sig.publicKey, "base64"),
2209
+ format: "der",
2210
+ type: "spki"
2211
+ });
2212
+ return keyId(key);
2213
+ }
2214
+ function generateKeyPair() {
2215
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
2216
+ return {
2217
+ publicKeyPem: publicKey.export({ type: "spki", format: "pem" }),
2218
+ privateKeyPem: privateKey.export({
2219
+ type: "pkcs8",
2220
+ format: "pem"
2221
+ }),
2222
+ keyId: keyId(publicKey)
2223
+ };
2224
+ }
2225
+ function signDigest(digest, privateKeyPem) {
2226
+ const privateKey = createPrivateKey(privateKeyPem);
2227
+ const publicKey = createPublicKey(
2228
+ privateKey
2229
+ );
2230
+ const sig = cryptoSign(null, Buffer.from(digest, "utf8"), privateKey);
2231
+ return {
2232
+ schema: SIGNATURE_SCHEMA,
2233
+ alg: "ed25519",
2234
+ keyId: keyId(publicKey),
2235
+ publicKey: derOf(publicKey).toString("base64"),
2236
+ digest,
2237
+ signature: sig.toString("base64"),
2238
+ signedAt: (/* @__PURE__ */ new Date()).toISOString()
2239
+ };
2240
+ }
2241
+ function verifySignature(sig) {
2242
+ try {
2243
+ const publicKey = createPublicKey({
2244
+ key: Buffer.from(sig.publicKey, "base64"),
2245
+ format: "der",
2246
+ type: "spki"
2247
+ });
2248
+ return cryptoVerify(
2249
+ null,
2250
+ Buffer.from(sig.digest, "utf8"),
2251
+ publicKey,
2252
+ Buffer.from(sig.signature, "base64")
2253
+ );
2254
+ } catch {
2255
+ return false;
2256
+ }
2257
+ }
2258
+ var SIGNATURE_SCHEMA;
2259
+ var init_signing = __esm({
2260
+ "src/evidence/signing.ts"() {
2261
+ "use strict";
2262
+ SIGNATURE_SCHEMA = "veriskit/signature@1";
2263
+ }
2264
+ });
2265
+
2266
+ // src/evidence/verify-evidence.ts
2267
+ import { existsSync as existsSync5 } from "fs";
2268
+ import { readFile as readFile3 } from "fs/promises";
2269
+ function verifyRecord(record) {
2270
+ const recomputed = computeDigest(
2271
+ record
2272
+ );
2273
+ const ok = recomputed === record.digest;
2274
+ const checks = [
2275
+ {
2276
+ name: "record digest",
2277
+ ok,
2278
+ detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
2279
+ }
2280
+ ];
2281
+ return { ok, kind: "record", record, checks, signed: false };
2282
+ }
2283
+ function signatureChecks(recordDigest, sig, opts = {}) {
2284
+ const checks = [];
2285
+ const kid = signatureKeyId(sig);
2286
+ const validSig = verifySignature(sig) && sig.digest === recordDigest;
2287
+ checks.push({
2288
+ name: "signature",
2289
+ ok: validSig,
2290
+ detail: validSig ? `valid ed25519 signature by key ${kid}` : "signature does not match this record"
2291
+ });
2292
+ if (opts.expectedKeyId || opts.expectedPubKeyPem) {
2293
+ let matches = false;
2294
+ if (opts.expectedPubKeyPem) {
2295
+ try {
2296
+ matches = keyId(opts.expectedPubKeyPem) === kid;
2297
+ } catch {
2298
+ matches = false;
2299
+ }
2300
+ } else if (opts.expectedKeyId) {
2301
+ matches = opts.expectedKeyId.toLowerCase() === kid.toLowerCase();
2302
+ }
2303
+ checks.push({
2304
+ name: "signer",
2305
+ ok: matches,
2306
+ detail: matches ? `signer matches the expected key ${kid}` : `signer ${kid} does not match the expected key`
2307
+ });
2308
+ }
2309
+ return checks;
2310
+ }
2311
+ async function verifyEvidenceFile(path, opts = {}) {
2312
+ const parsed = JSON.parse(await readFile3(path, "utf8"));
2313
+ if (parsed?.schema === "veriskit/bundle@1") {
2314
+ const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
2315
+ return verifyBundle2(parsed, opts);
2316
+ }
2317
+ const result = verifyRecord(parsed);
2318
+ const assertedSigner = Boolean(
2319
+ opts.expectedKeyId || opts.expectedPubKeyPem || opts.sigPath
2320
+ );
2321
+ const sigPath = opts.sigPath ?? `${path}.sig`;
2322
+ if (existsSync5(sigPath)) {
2323
+ const sig = JSON.parse(await readFile3(sigPath, "utf8"));
2324
+ result.checks.push(
2325
+ ...signatureChecks(result.record.digest, sig, {
2326
+ expectedKeyId: opts.expectedKeyId,
2327
+ expectedPubKeyPem: opts.expectedPubKeyPem
2328
+ })
2329
+ );
2330
+ result.signed = true;
2331
+ } else if (assertedSigner) {
2332
+ result.checks.push({
2333
+ name: "signature",
2334
+ ok: false,
2335
+ detail: "a signature was required (--pubkey/--key-id/--sig) but none was found"
2336
+ });
2337
+ }
2338
+ result.ok = result.checks.every((c) => c.ok);
2339
+ return result;
2340
+ }
2341
+ var init_verify_evidence = __esm({
2342
+ "src/evidence/verify-evidence.ts"() {
2343
+ "use strict";
2344
+ init_record();
2345
+ init_signing();
2346
+ }
2347
+ });
2348
+
2349
+ // src/evidence/bundle.ts
2350
+ var bundle_exports = {};
2351
+ __export(bundle_exports, {
2352
+ BUNDLE_SCHEMA: () => BUNDLE_SCHEMA,
2353
+ buildBundle: () => buildBundle,
2354
+ verifyBundle: () => verifyBundle
2355
+ });
2356
+ function buildBundle(record, report, logs, signature) {
2357
+ const manifest = {
2358
+ record: record.digest,
2359
+ report: sha256(report),
2360
+ logs: Object.fromEntries(
2361
+ Object.entries(logs).map(([k, v]) => [k, sha256(v)])
2362
+ )
2363
+ };
2364
+ const base = signature ? { schema: BUNDLE_SCHEMA, record, report, logs, manifest, signature } : { schema: BUNDLE_SCHEMA, record, report, logs, manifest };
2365
+ return { ...base, bundleDigest: sha256(canonicalize(base)) };
2366
+ }
2367
+ function verifyBundle(bundle, opts = {}) {
2368
+ const checks = [];
2369
+ const recordResult = verifyRecord(bundle.record);
2370
+ checks.push(...recordResult.checks);
2371
+ const reportDigest = sha256(bundle.report);
2372
+ checks.push({
2373
+ name: "report",
2374
+ ok: reportDigest === bundle.manifest.report,
2375
+ detail: reportDigest === bundle.manifest.report ? "matches manifest" : "mismatch"
2376
+ });
2377
+ for (const [id, body] of Object.entries(bundle.logs)) {
2378
+ const d = sha256(body);
2379
+ checks.push({
2380
+ name: `log:${id}`,
2381
+ ok: d === bundle.manifest.logs[id],
2382
+ detail: d === bundle.manifest.logs[id] ? "matches manifest" : "mismatch"
2383
+ });
2384
+ }
2385
+ const { bundleDigest: _omit, ...rest } = bundle;
2386
+ const recomputed = sha256(canonicalize(rest));
2387
+ checks.push({
2388
+ name: "bundle digest",
2389
+ ok: recomputed === bundle.bundleDigest,
2390
+ detail: recomputed === bundle.bundleDigest ? "matches" : "mismatch"
2391
+ });
2392
+ let signed = false;
2393
+ if (bundle.signature) {
2394
+ signed = true;
2395
+ checks.push(
2396
+ ...signatureChecks(bundle.record.digest, bundle.signature, {
2397
+ expectedKeyId: opts.expectedKeyId,
2398
+ expectedPubKeyPem: opts.expectedPubKeyPem
2399
+ })
2400
+ );
2401
+ } else if (opts.expectedKeyId || opts.expectedPubKeyPem) {
2402
+ checks.push({
2403
+ name: "signature",
2404
+ ok: false,
2405
+ detail: "a signature was required (--pubkey/--key-id) but the bundle is unsigned"
2406
+ });
2407
+ }
2408
+ return {
2409
+ ok: checks.every((c) => c.ok),
2410
+ kind: "bundle",
2411
+ record: bundle.record,
2412
+ checks,
2413
+ signed
2414
+ };
2415
+ }
2416
+ var BUNDLE_SCHEMA;
2417
+ var init_bundle = __esm({
2418
+ "src/evidence/bundle.ts"() {
2419
+ "use strict";
2420
+ init_record();
2421
+ init_verify_evidence();
2422
+ BUNDLE_SCHEMA = "veriskit/bundle@1";
2423
+ }
2424
+ });
2425
+
2426
+ // src/cli/commands/evidence.ts
2427
+ var evidence_exports = {};
2428
+ __export(evidence_exports, {
2429
+ runEvidenceBundle: () => runEvidenceBundle,
2430
+ runEvidenceKeygen: () => runEvidenceKeygen,
2431
+ runEvidenceShow: () => runEvidenceShow,
2432
+ runEvidenceSign: () => runEvidenceSign,
2433
+ runEvidenceVerify: () => runEvidenceVerify
2434
+ });
2435
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync } from "fs";
2436
+ import { writeFile as writeFile4 } from "fs/promises";
2437
+ import { basename as basename3, dirname as dirname2, join as join12 } from "path";
2438
+ import pc5 from "picocolors";
2439
+ async function runEvidenceVerify(path, opts = {}) {
2440
+ let expectedPubKeyPem;
2441
+ if (opts.pubkey) {
2442
+ try {
2443
+ expectedPubKeyPem = readFileSync6(opts.pubkey, "utf8");
2444
+ } catch {
2445
+ process.stderr.write(`veris: cannot read public key at ${opts.pubkey}
2446
+ `);
2447
+ return 1;
2448
+ }
2449
+ }
2450
+ let result;
2451
+ try {
2452
+ result = await verifyEvidenceFile(path, {
2453
+ sigPath: opts.sig,
2454
+ expectedKeyId: opts.keyId,
2455
+ expectedPubKeyPem
2456
+ });
2457
+ } catch (err) {
2458
+ const msg = err instanceof Error ? err.message : String(err);
2459
+ process.stderr.write(`veris: cannot read evidence at ${path}: ${msg}
2460
+ `);
2461
+ return 1;
2462
+ }
2463
+ const plain = isPlain();
2464
+ const mark = (ok) => plain ? ok ? "ok" : "FAIL" : ok ? pc5.green("\u2713") : pc5.red("\u2717");
2465
+ for (const check of result.checks) {
2466
+ process.stdout.write(
2467
+ ` ${mark(check.ok)} ${check.name}: ${check.detail}
2468
+ `
2469
+ );
2470
+ }
2471
+ const g = result.record.git;
2472
+ const anchor = g ? `commit ${g.commit.slice(0, 7)} \xB7 ${g.dirty ? "tree dirty" : "tree clean"}` : "no git anchor";
2473
+ process.stdout.write(
2474
+ `
2475
+ ${result.ok ? "digest OK" : "TAMPERED"} \xB7 verdict ${result.record.verdict.state} \xB7 ${anchor}
2476
+ `
2477
+ );
2478
+ process.stdout.write(`
2479
+ ${HONESTY}
2480
+ `);
2481
+ if (result.signed && !opts.pubkey && !opts.keyId) {
2482
+ process.stdout.write(
2483
+ "\nThis record is signed. Confirm you trust the signing key by comparing its\nkey id above to one you already trust, or re-run with --pubkey / --key-id.\n"
2484
+ );
2485
+ }
2486
+ return result.ok ? 0 : 1;
2487
+ }
2488
+ async function runEvidenceKeygen(root, opts = {}) {
2489
+ const out = opts.out ?? join12(root, ".veris", "keys", "veriskit-signing.key");
2490
+ const pub = `${out}.pub`;
2491
+ if (existsSync6(out) || existsSync6(pub)) {
2492
+ process.stderr.write(
2493
+ `veris: a key already exists at ${out}. Refusing to overwrite it.
2494
+ `
2495
+ );
2496
+ return 1;
2497
+ }
2498
+ const { mkdir: mkdir3 } = await import("fs/promises");
2499
+ await mkdir3(dirname2(out), { recursive: true });
2500
+ const kp = generateKeyPair();
2501
+ writeFileSync(out, kp.privateKeyPem, { mode: 384 });
2502
+ chmodSync(out, 384);
2503
+ writeFileSync(pub, kp.publicKeyPem, "utf8");
2504
+ process.stdout.write(
2505
+ [
2506
+ `Wrote signing key ${out} (keep this secret; do not commit it)`,
2507
+ `Wrote public key ${pub}`,
2508
+ `Key id ${kp.keyId}`,
2509
+ ""
2510
+ ].join("\n")
2511
+ );
2512
+ return 0;
2513
+ }
2514
+ async function runEvidenceSign(evidencePath, opts = {}) {
2515
+ let privateKeyPem;
2516
+ if (process.env.VERISKIT_SIGNING_KEY) {
2517
+ privateKeyPem = process.env.VERISKIT_SIGNING_KEY;
2518
+ } else if (opts.key) {
2519
+ try {
2520
+ privateKeyPem = readFileSync6(opts.key, "utf8");
2521
+ } catch {
2522
+ process.stderr.write(`veris: cannot read signing key at ${opts.key}
2523
+ `);
2524
+ return 1;
2525
+ }
2526
+ } else {
2527
+ process.stderr.write(
2528
+ "veris: no signing key. Pass --key <path> or set VERISKIT_SIGNING_KEY.\n"
2529
+ );
2530
+ return 1;
2531
+ }
2532
+ let record;
2533
+ try {
2534
+ record = JSON.parse(readFileSync6(evidencePath, "utf8"));
2535
+ } catch {
2536
+ process.stderr.write(`veris: cannot read evidence at ${evidencePath}
2537
+ `);
2538
+ return 1;
2539
+ }
2540
+ const recomputed = computeDigest(
2541
+ record
2542
+ );
2543
+ if (recomputed !== record.digest) {
2544
+ process.stderr.write(
2545
+ "veris: refusing to sign, the record digest does not match its contents.\n"
2546
+ );
2547
+ return 1;
2548
+ }
2549
+ let sig;
2550
+ try {
2551
+ sig = signDigest(record.digest, privateKeyPem);
2552
+ } catch (err) {
2553
+ const msg = err instanceof Error ? err.message : String(err);
2554
+ process.stderr.write(`veris: signing failed: ${msg}
2555
+ `);
2556
+ return 1;
2557
+ }
2558
+ const out = opts.out ?? `${evidencePath}.sig`;
2559
+ writeFileSync(out, `${JSON.stringify(sig, null, 2)}
2560
+ `, "utf8");
2561
+ process.stdout.write(
2562
+ `Signed ${evidencePath}
2563
+ Wrote ${out} (key ${sig.keyId})
2564
+ `
2565
+ );
2566
+ return 0;
2567
+ }
2568
+ async function runEvidenceBundle(root, opts = {}) {
2569
+ const runDir = latestRunDir(root);
2570
+ if (!runDir) {
2571
+ process.stdout.write("No evidence yet. Run `veris verify` first.\n");
2572
+ return 1;
2573
+ }
2574
+ let record;
2575
+ try {
2576
+ record = JSON.parse(
2577
+ readFileSync6(join12(runDir, "evidence.json"), "utf8")
2578
+ );
2579
+ } catch {
2580
+ process.stderr.write(`veris: no evidence.json in ${runDir}
2581
+ `);
2582
+ return 1;
2583
+ }
2584
+ let report = "";
2585
+ try {
2586
+ report = readFileSync6(
2587
+ join12(root, ".veris", "reports", `verify-${record.id}.md`),
2588
+ "utf8"
2589
+ );
2590
+ } catch {
2591
+ }
2592
+ const logIds = record.checks.filter((c) => c.logDigest).map((c) => c.id);
2593
+ const logs = await readRunLogs(runDir, logIds);
2594
+ let signature;
2595
+ const sigPath = join12(runDir, "evidence.json.sig");
2596
+ if (existsSync6(sigPath)) {
2597
+ try {
2598
+ signature = JSON.parse(readFileSync6(sigPath, "utf8"));
2599
+ } catch {
2600
+ }
2601
+ }
2602
+ const bundle = buildBundle(record, report, logs, signature);
2603
+ const outDir = await ensureEvidenceDir(root);
2604
+ const out = opts.out ?? join12(outDir, `${record.id}.bundle.json`);
2605
+ await writeFile4(out, `${JSON.stringify(bundle, null, 2)}
2606
+ `, "utf8");
2607
+ process.stdout.write(`Wrote portable evidence bundle: ${out}
2608
+ `);
2609
+ return 0;
2610
+ }
2611
+ async function runEvidenceShow(root, path) {
2612
+ const target = path ?? (() => {
2613
+ const dir = latestRunDir(root);
2614
+ return dir ? join12(dir, "evidence.json") : null;
2615
+ })();
2616
+ if (!target) {
2617
+ process.stdout.write("No evidence yet. Run `veris verify` first.\n");
2618
+ return 0;
2619
+ }
2620
+ let record;
2621
+ try {
2622
+ record = JSON.parse(readFileSync6(target, "utf8"));
2623
+ } catch {
2624
+ process.stderr.write(`veris: cannot read evidence at ${target}
2625
+ `);
2626
+ return 1;
2627
+ }
2628
+ const g = record.git;
2629
+ process.stdout.write(
2630
+ [
2631
+ `Evidence ${basename3(target)}`,
2632
+ `Verdict ${record.verdict.state}`,
2633
+ `Project ${record.project.name}`,
2634
+ `Scope ${record.scope.kind} (${record.scope.changedCount} changed)`,
2635
+ `Commit ${g ? `${g.commit.slice(0, 7)} (${g.branch}) ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}` : "no git anchor"}`,
2636
+ `Checks ${record.checks.map((c) => `${c.id}:${c.status}`).join(", ")}`,
2637
+ `Digest ${record.digest}`,
2638
+ ""
2639
+ ].join("\n")
2640
+ );
2641
+ return 0;
2642
+ }
2643
+ var HONESTY;
2644
+ var init_evidence = __esm({
2645
+ "src/cli/commands/evidence.ts"() {
2646
+ "use strict";
2647
+ init_bundle();
2648
+ init_record();
2649
+ init_signing();
2650
+ init_store();
2651
+ init_verify_evidence();
2652
+ init_tty();
2653
+ 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 (veris evidence sign) to prove authorship.";
2654
+ }
2655
+ });
2656
+
2657
+ // src/publish/badge.ts
2658
+ function buildBadge(verdict) {
2659
+ const s = STYLE[verdict.state];
2660
+ return {
2661
+ schemaVersion: 1,
2662
+ label: "veriskit",
2663
+ message: s.message,
2664
+ color: s.color
2665
+ };
2666
+ }
2667
+ var STYLE;
2668
+ var init_badge = __esm({
2669
+ "src/publish/badge.ts"() {
2670
+ "use strict";
2671
+ STYLE = {
2672
+ verified: { message: "verified", color: "14b8a6" },
2673
+ failed: { message: "failed", color: "e5484d" },
2674
+ partial: { message: "partial", color: "f5a623" }
2675
+ };
2676
+ }
2677
+ });
2678
+
2679
+ // src/cli/commands/badge.ts
2680
+ var badge_exports = {};
2681
+ __export(badge_exports, {
2682
+ runBadge: () => runBadge
2683
+ });
2684
+ import { readFileSync as readFileSync7 } from "fs";
2685
+ import { mkdir as mkdir2, writeFile as writeFile5 } from "fs/promises";
2686
+ import { dirname as dirname3, join as join13 } from "path";
2687
+ async function runBadge(root, opts = {}) {
2688
+ const dir = latestRunDir(root);
2689
+ if (!dir) {
2690
+ process.stdout.write("No runs yet. Run `veris verify` first.\n");
2691
+ return 1;
2692
+ }
2693
+ let record;
2694
+ try {
2695
+ record = JSON.parse(
2696
+ readFileSync7(join13(dir, "evidence.json"), "utf8")
2697
+ );
2698
+ } catch {
2699
+ process.stderr.write(`veris: no evidence.json in ${dir}
2700
+ `);
2701
+ return 1;
2702
+ }
2703
+ const out = opts.out ?? join13(root, ".veris", "badge.json");
2704
+ await mkdir2(dirname3(out), { recursive: true });
2705
+ await writeFile5(
2706
+ out,
2707
+ `${JSON.stringify(buildBadge(record.verdict), null, 2)}
2708
+ `,
2709
+ "utf8"
2710
+ );
2711
+ process.stdout.write(`Wrote badge ${out}
2712
+ `);
2713
+ return 0;
2714
+ }
2715
+ var init_badge2 = __esm({
2716
+ "src/cli/commands/badge.ts"() {
2717
+ "use strict";
2718
+ init_store();
2719
+ init_badge();
2720
+ }
2721
+ });
2722
+
2723
+ // src/history/flaky.ts
2724
+ function detectFlaky(records) {
2725
+ const byId = /* @__PURE__ */ new Map();
2726
+ for (const rec of records) {
2727
+ for (const c of rec.checks ?? []) {
2728
+ const arr = byId.get(c.id) ?? [];
2729
+ arr.push(c.status);
2730
+ byId.set(c.id, arr);
2731
+ }
2732
+ }
2733
+ const flaky = [];
2734
+ for (const [id, statuses] of byId) {
2735
+ if (statuses.includes("passed") && statuses.includes("failed")) {
2736
+ flaky.push({ id, statuses });
2737
+ }
2738
+ }
2739
+ return flaky;
2740
+ }
2741
+ var init_flaky = __esm({
2742
+ "src/history/flaky.ts"() {
2743
+ "use strict";
2744
+ }
2745
+ });
2746
+
2747
+ // src/history/read.ts
2748
+ import { readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
2749
+ import { join as join14 } from "path";
2750
+ function loadRuns(root, limit = 20) {
2751
+ const runs = join14(root, ".veris", "runs");
2752
+ let names;
2753
+ try {
2754
+ names = readdirSync5(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name);
2755
+ } catch {
2756
+ return [];
2757
+ }
2758
+ const records = [];
2759
+ for (const name of names) {
2760
+ try {
2761
+ const rec = JSON.parse(
2762
+ readFileSync8(join14(runs, name, "evidence.json"), "utf8")
2763
+ );
2764
+ if (rec?.schema === "veriskit/evidence@1") records.push(rec);
2765
+ } catch {
2766
+ }
2767
+ }
2768
+ records.sort(
2769
+ (a, b) => a.startedAt < b.startedAt ? 1 : a.startedAt > b.startedAt ? -1 : 0
2770
+ );
2771
+ return records.slice(0, limit);
2772
+ }
2773
+ var init_read = __esm({
2774
+ "src/history/read.ts"() {
2775
+ "use strict";
2776
+ }
2777
+ });
2778
+
2779
+ // src/cli/commands/log.ts
2780
+ var log_exports = {};
2781
+ __export(log_exports, {
2782
+ runLog: () => runLog
2783
+ });
2784
+ async function runLog(root, opts = {}) {
2785
+ const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : 20;
2786
+ const records = loadRuns(root, limit);
2787
+ if (records.length === 0) {
2788
+ process.stdout.write("No runs yet. Run `veris verify` first.\n");
2789
+ return 0;
2790
+ }
2791
+ if (opts.flaky) {
2792
+ const flaky = detectFlaky(records);
2793
+ if (flaky.length === 0) {
2794
+ process.stdout.write(
2795
+ `No flaky checks in the last ${records.length} run(s).
2796
+ `
2797
+ );
2798
+ return 0;
2799
+ }
2800
+ process.stdout.write(
2801
+ `Flaky checks (both passed and failed in the last ${records.length} run(s), newest first):
2802
+ `
2803
+ );
2804
+ for (const f of flaky) {
2805
+ process.stdout.write(` ${f.id.padEnd(8)} ${f.statuses.join(" ")}
2806
+ `);
2807
+ }
2808
+ return 0;
2809
+ }
2810
+ for (const rec of records) {
2811
+ const date = rec.startedAt.slice(0, 19).replace("T", " ");
2812
+ const checks = (rec.checks ?? []).map((c) => `${c.id}:${c.status}`).join(" ");
2813
+ const commit = rec.git ? rec.git.commit.slice(0, 7) : "-";
2814
+ process.stdout.write(
2815
+ `${date} ${rec.verdict.state.padEnd(9)} ${checks} ${commit}
2816
+ `
2817
+ );
2818
+ }
2819
+ process.stdout.write(
2820
+ "\nHistory is local to this machine (.veris/runs is gitignored).\n"
2821
+ );
2822
+ return 0;
2823
+ }
2824
+ var init_log = __esm({
2825
+ "src/cli/commands/log.ts"() {
2826
+ "use strict";
2827
+ init_flaky();
2828
+ init_read();
2829
+ }
2830
+ });
2831
+
2832
+ // src/cli/index.ts
2833
+ init_version();
2834
+ import { realpathSync } from "fs";
2835
+ import { argv } from "process";
2836
+ import { pathToFileURL } from "url";
2837
+ import cac from "cac";
2838
+ function buildCli() {
2839
+ const cli = cac("veris");
2840
+ cli.version(VERSION);
2841
+ cli.help();
2842
+ cli.command("doctor", "Report environment + capabilities (read-only)").action(async () => {
2843
+ const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
2844
+ process.exitCode = await runDoctor2(process.cwd());
2845
+ });
2846
+ cli.command("test", "Run the detected unit test runner").action(async () => {
2847
+ const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
2848
+ process.exitCode = await runTest2(process.cwd());
2849
+ });
2850
+ cli.command("init", "Detect the stack and set up .veris (idempotent)").action(async () => {
2851
+ const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
2852
+ process.exitCode = await runInit2(process.cwd());
2853
+ });
2854
+ cli.command("verify", "Run the full check set and produce a verdict + report").option("--partial-ok", "Exit 0 even when the verdict is partial").option(
2855
+ "--github",
2856
+ "Publish the verdict to the GitHub PR (comment + check run)"
2857
+ ).option("--browser", "Also run browser tests (Playwright), when available").action(
2858
+ async (opts) => {
2859
+ const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
2860
+ process.exitCode = await runVerify2(process.cwd(), {
2861
+ partialOk: opts.partialOk,
2862
+ github: opts.github,
2863
+ browser: opts.browser
2864
+ });
2865
+ }
2866
+ );
2867
+ cli.command("report", "Print the latest verification report").action(async () => {
2868
+ const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
2869
+ process.exitCode = await runReport2(process.cwd());
2870
+ });
2871
+ cli.command("affected", "Run only the checks affected by changed files").option("--base <ref>", "Compare against a git ref instead of HEAD").option("--partial-ok", "Exit 0 even when the verdict is partial").option(
2872
+ "--github",
2873
+ "Publish the verdict to the GitHub PR (comment + check run)"
2874
+ ).action(
2875
+ async (opts) => {
2876
+ const { runAffected: runAffected2 } = await Promise.resolve().then(() => (init_affected(), affected_exports));
2877
+ process.exitCode = await runAffected2(process.cwd(), {
2878
+ base: opts.base,
2879
+ partialOk: opts.partialOk,
2880
+ github: opts.github
2881
+ });
2882
+ }
2883
+ );
2884
+ cli.command("watch", "Re-run affected checks as files change (Ctrl-C to stop)").option("--poll", "Use mtime polling instead of native fs.watch").action(async (opts) => {
2885
+ const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
2886
+ process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
2887
+ });
2888
+ cli.command("scan", "Map the import graph and untested areas (read-only)").action(async () => {
2889
+ const { runScan: runScan2 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
2890
+ process.exitCode = await runScan2(process.cwd());
2891
+ });
2892
+ cli.command(
2893
+ "plan",
2894
+ "Recommend what to test, from the import graph (read-only)"
2895
+ ).option("--base <ref>", "Also factor in changes vs a git ref").action(async (opts) => {
2896
+ const { runPlan: runPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
2897
+ process.exitCode = await runPlan2(process.cwd(), { base: opts.base });
2898
+ });
2899
+ cli.command(
2900
+ "evidence <action> [file]",
2901
+ "Evidence: verify <file> | bundle | show [file] | keygen | sign <file>"
2902
+ ).option("--out <file>", "For bundle/keygen/sign: output path").option("--key <file>", "For sign: the private signing key (PEM)").option("--pubkey <file>", "For verify: assert the signer's public key").option("--key-id <id>", "For verify: assert the signer's key id").option("--sig <file>", "For verify: signature path (default <file>.sig)").action(
2903
+ async (action, file, opts) => {
2904
+ const mod = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
2905
+ if (action === "verify") {
2906
+ if (!file) {
2907
+ process.stderr.write("veris: evidence verify needs a <file>\n");
2908
+ process.exitCode = 1;
2909
+ return;
2910
+ }
2911
+ process.exitCode = await mod.runEvidenceVerify(file, {
2912
+ pubkey: opts.pubkey,
2913
+ keyId: opts.keyId,
2914
+ sig: opts.sig
2915
+ });
2916
+ } else if (action === "bundle") {
2917
+ process.exitCode = await mod.runEvidenceBundle(process.cwd(), {
2918
+ out: opts.out
2919
+ });
2920
+ } else if (action === "show") {
2921
+ process.exitCode = await mod.runEvidenceShow(process.cwd(), file);
2922
+ } else if (action === "keygen") {
2923
+ process.exitCode = await mod.runEvidenceKeygen(process.cwd(), {
2924
+ out: opts.out
2925
+ });
2926
+ } else if (action === "sign") {
2927
+ if (!file) {
2928
+ process.stderr.write("veris: evidence sign needs a <file>\n");
2929
+ process.exitCode = 1;
2930
+ return;
2931
+ }
2932
+ process.exitCode = await mod.runEvidenceSign(file, {
2933
+ key: opts.key,
2934
+ out: opts.out
2935
+ });
2936
+ } else {
2937
+ process.stderr.write(
2938
+ `veris: unknown evidence action '${action}' (use verify | bundle | show | keygen | sign)
2939
+ `
2940
+ );
2941
+ process.exitCode = 1;
2942
+ }
2943
+ }
2944
+ );
2945
+ cli.command(
2946
+ "badge",
2947
+ "Write a shields.io endpoint JSON from the latest verdict"
2948
+ ).option("--out <file>", "Output path (default .veris/badge.json)").action(async (opts) => {
2949
+ const { runBadge: runBadge2 } = await Promise.resolve().then(() => (init_badge2(), badge_exports));
2950
+ process.exitCode = await runBadge2(process.cwd(), { out: opts.out });
2951
+ });
2952
+ cli.command("log", "List past verification runs (local history)").option("--limit <n>", "How many runs to show (default 20)").option("--flaky", "Show only checks that flip-flop across runs").action(async (opts) => {
2953
+ const { runLog: runLog2 } = await Promise.resolve().then(() => (init_log(), log_exports));
2954
+ process.exitCode = await runLog2(process.cwd(), {
2955
+ limit: opts.limit ? Number(opts.limit) : void 0,
2956
+ flaky: opts.flaky
2957
+ });
2958
+ });
2959
+ return { raw: cli, version: VERSION };
2960
+ }
2961
+ async function main(argv2) {
2962
+ try {
2963
+ const { raw } = buildCli();
2964
+ if (argv2.length <= 2) {
2965
+ raw.outputHelp();
2966
+ return;
2967
+ }
2968
+ raw.parse(argv2);
2969
+ } catch (err) {
2970
+ const msg = err instanceof Error ? err.message : String(err);
2971
+ process.stderr.write(`veris: ${msg}
2972
+ `);
2973
+ process.exitCode = 1;
2974
+ }
2975
+ }
2976
+ var invokedPath = argv[1] ? realpathSync(argv[1]) : "";
2977
+ var isEntry = import.meta.url === pathToFileURL(invokedPath).href;
2978
+ if (isEntry) void main(argv);
2979
+ export {
2980
+ buildCli,
2981
+ main
2982
+ };