veriskit 0.2.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/index.js ADDED
@@ -0,0 +1,1268 @@
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/util/fs-safe.ts
12
+ import { existsSync } from "fs";
13
+ import { mkdir, readFile, writeFile } from "fs/promises";
14
+ async function ensureDir(path) {
15
+ await mkdir(path, { recursive: true });
16
+ }
17
+ async function writeIfAbsent(path, content) {
18
+ if (existsSync(path)) return false;
19
+ await writeFile(path, content, "utf8");
20
+ return true;
21
+ }
22
+ async function readJsonIfExists(path) {
23
+ if (!existsSync(path)) return null;
24
+ try {
25
+ return JSON.parse(await readFile(path, "utf8"));
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ var init_fs_safe = __esm({
31
+ "src/util/fs-safe.ts"() {
32
+ "use strict";
33
+ }
34
+ });
35
+
36
+ // src/config/detect.ts
37
+ import { existsSync as existsSync2 } from "fs";
38
+ import { join } from "path";
39
+ function detectPackageManager(root) {
40
+ if (existsSync2(join(root, "pnpm-lock.yaml"))) return "pnpm";
41
+ if (existsSync2(join(root, "yarn.lock"))) return "yarn";
42
+ if (existsSync2(join(root, "bun.lockb"))) return "bun";
43
+ return "npm";
44
+ }
45
+ async function detectProject(root) {
46
+ const pkg = await readJsonIfExists(join(root, "package.json")) ?? {};
47
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
48
+ const has = (name) => name in deps;
49
+ const scripts = pkg.scripts ?? {};
50
+ const languages = existsSync2(join(root, "tsconfig.json")) ? ["typescript", "javascript"] : ["javascript"];
51
+ const frameworks = ["next", "vite", "react"].filter(has);
52
+ const capabilities = [
53
+ detectTypes(root, has),
54
+ detectUnit(has, scripts),
55
+ detectLint(root, has),
56
+ detectBrowser(root, has)
57
+ ];
58
+ return {
59
+ root,
60
+ packageManager: detectPackageManager(root),
61
+ frameworks,
62
+ languages,
63
+ scripts,
64
+ capabilities
65
+ };
66
+ }
67
+ function detectTypes(root, has) {
68
+ if (existsSync2(join(root, "tsconfig.json")) && has("typescript"))
69
+ return { id: "types", available: true, runner: "tsc" };
70
+ return {
71
+ id: "types",
72
+ available: false,
73
+ reason: "no tsconfig.json + typescript dependency"
74
+ };
75
+ }
76
+ function detectUnit(has, scripts) {
77
+ if (has("vitest")) return { id: "unit", available: true, runner: "vitest" };
78
+ if (has("jest")) return { id: "unit", available: true, runner: "jest" };
79
+ if (Object.values(scripts).some((s) => s.includes("node --test")))
80
+ return { id: "unit", available: true, runner: "node-test" };
81
+ return { id: "unit", available: false, reason: "no test runner detected" };
82
+ }
83
+ function detectLint(root, has) {
84
+ if (existsSync2(join(root, "biome.json")) || has("@biomejs/biome"))
85
+ return { id: "lint", available: true, runner: "biome" };
86
+ if (has("eslint") || [".eslintrc", ".eslintrc.json", ".eslintrc.cjs", "eslint.config.js"].some(
87
+ (f) => existsSync2(join(root, f))
88
+ ))
89
+ return { id: "lint", available: true, runner: "eslint" };
90
+ return { id: "lint", available: false, reason: "no linter configured" };
91
+ }
92
+ function detectBrowser(root, has) {
93
+ if (has("@playwright/test") || existsSync2(join(root, "playwright.config.ts")))
94
+ return {
95
+ id: "browser",
96
+ available: false,
97
+ runner: "playwright",
98
+ reason: "detected; browser execution deferred to v0.5"
99
+ };
100
+ return {
101
+ id: "browser",
102
+ available: false,
103
+ reason: "no browser runner configured"
104
+ };
105
+ }
106
+ var init_detect = __esm({
107
+ "src/config/detect.ts"() {
108
+ "use strict";
109
+ init_fs_safe();
110
+ }
111
+ });
112
+
113
+ // src/util/env.ts
114
+ import { platform } from "os";
115
+ function detectCI() {
116
+ return process.env.CI === "true" || process.env.CI === "1";
117
+ }
118
+ function getEnvironmentInfo(pm) {
119
+ return {
120
+ os: platform(),
121
+ node: process.version,
122
+ pm,
123
+ ci: detectCI(),
124
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
125
+ };
126
+ }
127
+ var init_env = __esm({
128
+ "src/util/env.ts"() {
129
+ "use strict";
130
+ }
131
+ });
132
+
133
+ // src/cli/tty.ts
134
+ function isPlain() {
135
+ return detectCI() || !process.stdout.isTTY;
136
+ }
137
+ var init_tty = __esm({
138
+ "src/cli/tty.ts"() {
139
+ "use strict";
140
+ init_env();
141
+ }
142
+ });
143
+
144
+ // src/cli/commands/doctor.ts
145
+ var doctor_exports = {};
146
+ __export(doctor_exports, {
147
+ renderDoctor: () => renderDoctor,
148
+ runDoctor: () => runDoctor
149
+ });
150
+ import pc from "picocolors";
151
+ function renderDoctor(project, env) {
152
+ const plain = isPlain();
153
+ const ok = (s) => plain ? s : pc.green(s);
154
+ const dim = (s) => plain ? s : pc.dim(s);
155
+ const lines = [];
156
+ lines.push("Veris doctor");
157
+ lines.push("");
158
+ lines.push(`Package manager ${project.packageManager}`);
159
+ lines.push(`Node ${env.node}`);
160
+ lines.push(`Languages ${project.languages.join(", ")}`);
161
+ if (project.frameworks.length)
162
+ lines.push(`Frameworks ${project.frameworks.join(", ")}`);
163
+ lines.push("");
164
+ lines.push("Capabilities");
165
+ for (const c of project.capabilities) {
166
+ if (c.available) {
167
+ lines.push(` ${ok("\u2713")} ${c.id.padEnd(8)} ${dim(`via ${c.runner}`)}`);
168
+ } else {
169
+ lines.push(
170
+ ` ${dim("\u2298")} ${c.id.padEnd(8)} ${dim(`skipped \u2014 ${c.reason}`)}`
171
+ );
172
+ }
173
+ }
174
+ return lines.join("\n");
175
+ }
176
+ async function runDoctor(root) {
177
+ const project = await detectProject(root);
178
+ const env = getEnvironmentInfo(project.packageManager);
179
+ process.stdout.write(`${renderDoctor(project, env)}
180
+ `);
181
+ return 0;
182
+ }
183
+ var init_doctor = __esm({
184
+ "src/cli/commands/doctor.ts"() {
185
+ "use strict";
186
+ init_detect();
187
+ init_env();
188
+ init_tty();
189
+ }
190
+ });
191
+
192
+ // src/evidence/store.ts
193
+ import { writeFile as writeFile2 } from "fs/promises";
194
+ import { join as join2 } from "path";
195
+ function newRunId() {
196
+ counter += 1;
197
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
198
+ return `${stamp}-${counter}`;
199
+ }
200
+ async function createRunDir(root, id) {
201
+ const dir = join2(root, ".veris", "runs", id);
202
+ await ensureDir(dir);
203
+ return dir;
204
+ }
205
+ async function writeLog(runDir, checkId, content) {
206
+ const ref = join2(runDir, `${checkId}.log`);
207
+ await writeFile2(ref, content, "utf8");
208
+ return ref;
209
+ }
210
+ async function writeMetadata(runDir, run) {
211
+ const ref = join2(runDir, "metadata.json");
212
+ await writeFile2(ref, JSON.stringify(run, null, 2), "utf8");
213
+ return ref;
214
+ }
215
+ async function writeReport(root, id, markdown) {
216
+ const dir = join2(root, ".veris", "reports");
217
+ await ensureDir(dir);
218
+ const ref = join2(dir, `verify-${id}.md`);
219
+ await writeFile2(ref, markdown, "utf8");
220
+ return ref;
221
+ }
222
+ var counter;
223
+ var init_store = __esm({
224
+ "src/evidence/store.ts"() {
225
+ "use strict";
226
+ init_fs_safe();
227
+ counter = 0;
228
+ }
229
+ });
230
+
231
+ // src/util/exec.ts
232
+ import { spawn } from "child_process";
233
+ function exec(cmd, args, opts = {}) {
234
+ return new Promise((resolve) => {
235
+ const start = performance.now();
236
+ const child = spawn(cmd, args, {
237
+ cwd: opts.cwd,
238
+ env: opts.env ?? process.env,
239
+ shell: false
240
+ });
241
+ let stdout = "";
242
+ let stderr = "";
243
+ let timedOut = false;
244
+ const timer = opts.timeoutMs ? setTimeout(() => {
245
+ timedOut = true;
246
+ child.kill("SIGKILL");
247
+ }, opts.timeoutMs) : null;
248
+ child.stdout.on("data", (d) => stdout += d.toString());
249
+ child.stderr.on("data", (d) => stderr += d.toString());
250
+ child.on("close", (code) => {
251
+ if (timer) clearTimeout(timer);
252
+ resolve({
253
+ code: code ?? 1,
254
+ stdout,
255
+ stderr,
256
+ durationMs: Math.round(performance.now() - start),
257
+ timedOut
258
+ });
259
+ });
260
+ child.on("error", () => {
261
+ if (timer) clearTimeout(timer);
262
+ resolve({
263
+ code: 127,
264
+ stdout,
265
+ stderr: stderr || `failed to spawn ${cmd}`,
266
+ durationMs: Math.round(performance.now() - start),
267
+ timedOut
268
+ });
269
+ });
270
+ });
271
+ }
272
+ var init_exec = __esm({
273
+ "src/util/exec.ts"() {
274
+ "use strict";
275
+ }
276
+ });
277
+
278
+ // src/runners/base.ts
279
+ import { join as join3 } from "path";
280
+ function localBin(root, name) {
281
+ return join3(root, "node_modules", ".bin", name);
282
+ }
283
+ async function runViaExec(check, ctx, opts) {
284
+ const r = await exec(check.cmd, check.args, {
285
+ cwd: ctx.root,
286
+ timeoutMs: opts.timeoutMs
287
+ });
288
+ const status = r.code === 0 ? "passed" : r.timedOut ? "unknown" : "failed";
289
+ const output = [r.stdout, r.stderr].filter(Boolean).join("\n").trim();
290
+ const logRef = await writeLog(ctx.runDir, check.id, `${output}
291
+ `);
292
+ const result = {
293
+ checkId: check.id,
294
+ status,
295
+ durationMs: r.durationMs,
296
+ summary: status === "passed" ? opts.pass : r.timedOut ? "timed out" : opts.fail,
297
+ logRef
298
+ };
299
+ if (status !== "passed" && output) {
300
+ result.outputTail = output.split("\n").slice(-TAIL_LINES).join("\n");
301
+ }
302
+ return result;
303
+ }
304
+ var TAIL_LINES;
305
+ var init_base = __esm({
306
+ "src/runners/base.ts"() {
307
+ "use strict";
308
+ init_store();
309
+ init_exec();
310
+ TAIL_LINES = 20;
311
+ }
312
+ });
313
+
314
+ // src/runners/biome.ts
315
+ var biomeRunner;
316
+ var init_biome = __esm({
317
+ "src/runners/biome.ts"() {
318
+ "use strict";
319
+ init_base();
320
+ biomeRunner = {
321
+ id: "biome",
322
+ toCheck(project, _cap) {
323
+ return {
324
+ id: "lint",
325
+ title: "Lint",
326
+ runner: "biome",
327
+ cmd: localBin(project.root, "biome"),
328
+ args: ["check", "."]
329
+ };
330
+ },
331
+ run(check, ctx) {
332
+ return runViaExec(check, ctx, {
333
+ pass: "no lint errors",
334
+ fail: "lint errors found",
335
+ timeoutMs: 3 * 6e4
336
+ });
337
+ }
338
+ };
339
+ }
340
+ });
341
+
342
+ // src/runners/eslint.ts
343
+ var eslintRunner;
344
+ var init_eslint = __esm({
345
+ "src/runners/eslint.ts"() {
346
+ "use strict";
347
+ init_base();
348
+ eslintRunner = {
349
+ id: "eslint",
350
+ toCheck(project, _cap) {
351
+ return {
352
+ id: "lint",
353
+ title: "Lint",
354
+ runner: "eslint",
355
+ cmd: localBin(project.root, "eslint"),
356
+ args: ["."]
357
+ };
358
+ },
359
+ run(check, ctx) {
360
+ return runViaExec(check, ctx, {
361
+ pass: "no lint errors",
362
+ fail: "lint errors found",
363
+ timeoutMs: 3 * 6e4
364
+ });
365
+ }
366
+ };
367
+ }
368
+ });
369
+
370
+ // src/runners/jest.ts
371
+ var jestRunner;
372
+ var init_jest = __esm({
373
+ "src/runners/jest.ts"() {
374
+ "use strict";
375
+ init_base();
376
+ jestRunner = {
377
+ id: "jest",
378
+ toCheck(project, _cap) {
379
+ return {
380
+ id: "unit",
381
+ title: "Unit tests",
382
+ runner: "jest",
383
+ cmd: localBin(project.root, "jest"),
384
+ args: ["--ci"]
385
+ };
386
+ },
387
+ run(check, ctx) {
388
+ return runViaExec(check, ctx, {
389
+ pass: "unit tests passed",
390
+ fail: "unit tests failed",
391
+ timeoutMs: 10 * 6e4
392
+ });
393
+ }
394
+ };
395
+ }
396
+ });
397
+
398
+ // src/runners/node-test.ts
399
+ var nodeTestRunner;
400
+ var init_node_test = __esm({
401
+ "src/runners/node-test.ts"() {
402
+ "use strict";
403
+ init_base();
404
+ nodeTestRunner = {
405
+ id: "node-test",
406
+ toCheck(_project, _cap) {
407
+ return {
408
+ id: "unit",
409
+ title: "Unit tests",
410
+ runner: "node-test",
411
+ cmd: process.execPath,
412
+ args: ["--test"]
413
+ };
414
+ },
415
+ run(check, ctx) {
416
+ return runViaExec(check, ctx, {
417
+ pass: "unit tests passed",
418
+ fail: "unit tests failed",
419
+ timeoutMs: 10 * 6e4
420
+ });
421
+ }
422
+ };
423
+ }
424
+ });
425
+
426
+ // src/runners/tsc.ts
427
+ var tscRunner;
428
+ var init_tsc = __esm({
429
+ "src/runners/tsc.ts"() {
430
+ "use strict";
431
+ init_base();
432
+ tscRunner = {
433
+ id: "tsc",
434
+ toCheck(project, _cap) {
435
+ return {
436
+ id: "types",
437
+ title: "Types",
438
+ runner: "tsc",
439
+ cmd: localBin(project.root, "tsc"),
440
+ args: ["--noEmit", "--pretty", "false"]
441
+ };
442
+ },
443
+ run(check, ctx) {
444
+ return runViaExec(check, ctx, {
445
+ pass: "no type errors",
446
+ fail: "type errors found",
447
+ timeoutMs: 5 * 6e4
448
+ });
449
+ }
450
+ };
451
+ }
452
+ });
453
+
454
+ // src/runners/vitest.ts
455
+ var vitestRunner;
456
+ var init_vitest = __esm({
457
+ "src/runners/vitest.ts"() {
458
+ "use strict";
459
+ init_base();
460
+ vitestRunner = {
461
+ id: "vitest",
462
+ toCheck(project, _cap) {
463
+ return {
464
+ id: "unit",
465
+ title: "Unit tests",
466
+ runner: "vitest",
467
+ cmd: localBin(project.root, "vitest"),
468
+ args: ["run", "--reporter=json"]
469
+ };
470
+ },
471
+ run(check, ctx) {
472
+ return runViaExec(check, ctx, {
473
+ pass: "unit tests passed",
474
+ fail: "unit tests failed",
475
+ timeoutMs: 10 * 6e4
476
+ });
477
+ }
478
+ };
479
+ }
480
+ });
481
+
482
+ // src/runners/index.ts
483
+ var runners;
484
+ var init_runners = __esm({
485
+ "src/runners/index.ts"() {
486
+ "use strict";
487
+ init_biome();
488
+ init_eslint();
489
+ init_jest();
490
+ init_node_test();
491
+ init_tsc();
492
+ init_vitest();
493
+ init_base();
494
+ runners = {
495
+ tsc: tscRunner,
496
+ vitest: vitestRunner,
497
+ jest: jestRunner,
498
+ "node-test": nodeTestRunner,
499
+ eslint: eslintRunner,
500
+ biome: biomeRunner
501
+ };
502
+ }
503
+ });
504
+
505
+ // src/core/verdict.ts
506
+ function computeVerdict(results, capabilities) {
507
+ const reasons = [];
508
+ const skipped = [];
509
+ const verifiedCapabilities = [];
510
+ const anyFailed = results.some((r) => r.status === "failed");
511
+ for (const cap of capabilities) {
512
+ const result = results.find((r) => r.checkId === cap.id);
513
+ if (!cap.available) {
514
+ skipped.push(cap.id);
515
+ reasons.push(`${cap.id} skipped \u2014 ${cap.reason ?? "not configured"}`);
516
+ continue;
517
+ }
518
+ if (result?.status === "passed") {
519
+ verifiedCapabilities.push(cap.id);
520
+ continue;
521
+ }
522
+ if (result?.status === "failed") {
523
+ reasons.push(`${cap.id} failed \u2014 ${result.summary}`);
524
+ continue;
525
+ }
526
+ skipped.push(cap.id);
527
+ reasons.push(`${cap.id} skipped \u2014 ${result?.summary ?? "did not run"}`);
528
+ }
529
+ let state;
530
+ if (anyFailed) state = "failed";
531
+ else if (skipped.length > 0) state = "partial";
532
+ else state = "verified";
533
+ return { state, verifiedCapabilities, skipped, reasons };
534
+ }
535
+ function verdictExitCode(verdict, opts = {}) {
536
+ if (verdict.state === "failed") return 1;
537
+ if (verdict.state === "partial") return opts.partialOk ? 0 : 2;
538
+ return 0;
539
+ }
540
+ var init_verdict = __esm({
541
+ "src/core/verdict.ts"() {
542
+ "use strict";
543
+ }
544
+ });
545
+
546
+ // src/core/orchestrator.ts
547
+ async function runChecks(project, ids, root) {
548
+ const known = new Set(project.capabilities.map((c) => c.id));
549
+ const unknown = ids.filter((id2) => !known.has(id2));
550
+ if (unknown.length > 0) {
551
+ throw new Error(`Unknown check id(s): ${unknown.join(", ")}`);
552
+ }
553
+ const id = newRunId();
554
+ const runDir = await createRunDir(root, id);
555
+ const ctx = { root, runDir };
556
+ const tasks = ids.map(async (capId) => {
557
+ const cap = project.capabilities.find((c) => c.id === capId);
558
+ const runner = cap?.runner ? runners[cap.runner] : void 0;
559
+ if (!cap?.available || !runner) {
560
+ const summary = !cap?.available ? cap?.reason ?? "not configured" : `no runner registered for ${cap.runner}`;
561
+ return {
562
+ checkId: capId,
563
+ status: "skipped",
564
+ durationMs: 0,
565
+ summary
566
+ };
567
+ }
568
+ const check = runner.toCheck(project, cap);
569
+ return runner.run(check, ctx);
570
+ });
571
+ const results = await Promise.all(tasks);
572
+ const requested = project.capabilities.filter((c) => ids.includes(c.id));
573
+ const verdict = computeVerdict(results, requested);
574
+ return {
575
+ id,
576
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
577
+ project,
578
+ results,
579
+ verdict,
580
+ env: getEnvironmentInfo(project.packageManager)
581
+ };
582
+ }
583
+ var init_orchestrator = __esm({
584
+ "src/core/orchestrator.ts"() {
585
+ "use strict";
586
+ init_store();
587
+ init_runners();
588
+ init_env();
589
+ init_verdict();
590
+ }
591
+ });
592
+
593
+ // src/reporters/terminal.ts
594
+ import pc2 from "picocolors";
595
+ function glyph(status, plain) {
596
+ const map = {
597
+ passed: "\u2713",
598
+ failed: "\u2717",
599
+ skipped: "\u2298",
600
+ unknown: "?"
601
+ };
602
+ const g = map[status];
603
+ if (plain) return g;
604
+ if (status === "passed") return pc2.green(g);
605
+ if (status === "failed") return pc2.red(g);
606
+ return pc2.dim(g);
607
+ }
608
+ function secs(ms) {
609
+ return ms === 0 ? "" : `${(ms / 1e3).toFixed(1)}s`;
610
+ }
611
+ function renderRun(run) {
612
+ const plain = isPlain();
613
+ const bold = (s) => plain ? s : pc2.bold(s);
614
+ const dim = (s) => plain ? s : pc2.dim(s);
615
+ const lines = [];
616
+ const scoped = run.scope?.kind;
617
+ if (scoped) {
618
+ lines.push(bold(`Veris \u2014 ${scoped}`));
619
+ lines.push("");
620
+ lines.push(`Scope ${run.scope?.changedCount ?? 0} changed file(s)`);
621
+ } else {
622
+ lines.push(bold("Veris"));
623
+ lines.push("");
624
+ lines.push(
625
+ `Project ${run.project.root.split("/").pop() ?? run.project.root}`
626
+ );
627
+ lines.push(`Risk ${dim("\u2014")}`);
628
+ }
629
+ lines.push("");
630
+ lines.push("Checks");
631
+ for (const r of run.results) {
632
+ const g = glyph(r.status, plain);
633
+ const detail = r.status === "skipped" ? dim(`skipped \u2014 ${r.summary}`) : r.cached ? dim(`\u27F3 cached \xB7 ${secs(r.durationMs) || "\u2014"}`) : secs(r.durationMs);
634
+ lines.push(` ${g} ${r.checkId.padEnd(14)} ${detail}`);
635
+ if (r.outputTail) {
636
+ for (const tail of r.outputTail.split("\n")) {
637
+ lines.push(dim(` ${tail}`));
638
+ }
639
+ }
640
+ }
641
+ lines.push("");
642
+ lines.push("Result");
643
+ const label = run.verdict.state === "verified" ? scoped ? "\u2713 Affected checks passed" : "\u2713 Verified" : run.verdict.state === "failed" ? scoped ? "\u2717 Affected checks failed" : "\u2717 Failed" : scoped ? "\u25B2 Affected: partial" : "\u25B2 Partial";
644
+ const color = run.verdict.state === "verified" ? pc2.green : run.verdict.state === "failed" ? pc2.red : pc2.yellow;
645
+ lines.push(` ${plain ? label : color(label)}`);
646
+ if (run.reportRef) {
647
+ lines.push("");
648
+ lines.push("Report");
649
+ lines.push(` ${run.reportRef}`);
650
+ }
651
+ return lines.join("\n");
652
+ }
653
+ var init_terminal = __esm({
654
+ "src/reporters/terminal.ts"() {
655
+ "use strict";
656
+ init_tty();
657
+ }
658
+ });
659
+
660
+ // src/cli/commands/test.ts
661
+ var test_exports = {};
662
+ __export(test_exports, {
663
+ runTest: () => runTest
664
+ });
665
+ async function runTest(root) {
666
+ const project = await detectProject(root);
667
+ const run = await runChecks(project, ["unit"], root);
668
+ process.stdout.write(`${renderRun(run)}
669
+ `);
670
+ return verdictExitCode(run.verdict);
671
+ }
672
+ var init_test = __esm({
673
+ "src/cli/commands/test.ts"() {
674
+ "use strict";
675
+ init_detect();
676
+ init_orchestrator();
677
+ init_verdict();
678
+ init_terminal();
679
+ }
680
+ });
681
+
682
+ // src/cli/commands/init.ts
683
+ var init_exports = {};
684
+ __export(init_exports, {
685
+ runInit: () => runInit
686
+ });
687
+ import { join as join4 } from "path";
688
+ async function runInit(root) {
689
+ const project = await detectProject(root);
690
+ const dir = join4(root, ".veris");
691
+ await ensureDir(dir);
692
+ const defaultChecks = project.capabilities.filter((c) => c.available && c.id !== "browser").map((c) => c.id);
693
+ const wroteConfig = await writeIfAbsent(
694
+ join4(dir, "config.json"),
695
+ `${JSON.stringify({ checks: defaultChecks }, null, 2)}
696
+ `
697
+ );
698
+ await writeIfAbsent(join4(dir, ".gitignore"), GITIGNORE);
699
+ process.stdout.write(
700
+ wroteConfig ? `Veris initialized. Detected checks: ${defaultChecks.join(", ") || "none"}.
701
+ ` : "Veris already initialized (.veris/config.json exists). Nothing overwritten.\n"
702
+ );
703
+ return 0;
704
+ }
705
+ var GITIGNORE;
706
+ var init_init = __esm({
707
+ "src/cli/commands/init.ts"() {
708
+ "use strict";
709
+ init_detect();
710
+ init_fs_safe();
711
+ GITIGNORE = ["runs/", "reports/", "cache/", ""].join("\n");
712
+ }
713
+ });
714
+
715
+ // src/config/load.ts
716
+ import { join as join5 } from "path";
717
+ async function loadConfig(root) {
718
+ return readJsonIfExists(join5(root, ".veris", "config.json"));
719
+ }
720
+ var init_load = __esm({
721
+ "src/config/load.ts"() {
722
+ "use strict";
723
+ init_fs_safe();
724
+ }
725
+ });
726
+
727
+ // src/reporters/markdown.ts
728
+ import { relative } from "path";
729
+ function cell(value) {
730
+ return value.replace(/\|/g, "\\|");
731
+ }
732
+ function renderMarkdown(run) {
733
+ const root = run.project.root;
734
+ const lines = [];
735
+ lines.push("# Veris Verification Report");
736
+ if (run.scope?.kind) {
737
+ lines.push("");
738
+ lines.push(
739
+ `> **Scope:** ${run.scope.kind} \u2014 ${run.scope.changedCount} changed file(s). Only affected checks ran; this is not a full verification.`
740
+ );
741
+ }
742
+ lines.push("");
743
+ lines.push(`**Verdict:** ${STATE_LABEL[run.verdict.state]}`);
744
+ lines.push(`**When:** ${run.startedAt}`);
745
+ lines.push(
746
+ `**Node:** ${run.env.node} \xB7 **OS:** ${run.env.os} \xB7 **PM:** ${run.env.pm} \xB7 **CI:** ${run.env.ci}`
747
+ );
748
+ lines.push("");
749
+ lines.push("## Checks");
750
+ lines.push("");
751
+ lines.push("| Check | Status | Duration | Summary | Log |");
752
+ lines.push("| --- | --- | --- | --- | --- |");
753
+ for (const r of run.results) {
754
+ const dur = r.durationMs ? `${(r.durationMs / 1e3).toFixed(1)}s` : "\u2014";
755
+ const log = r.logRef ? cell(relative(root, r.logRef)) : "\u2014";
756
+ lines.push(
757
+ `| ${r.checkId} | ${r.status} | ${dur} | ${cell(r.summary)} | ${log} |`
758
+ );
759
+ }
760
+ if (run.verdict.skipped.length) {
761
+ lines.push("");
762
+ lines.push("## Skipped");
763
+ lines.push("");
764
+ for (const reason of run.verdict.reasons) lines.push(`- ${reason}`);
765
+ }
766
+ const failures = run.results.filter((r) => r.outputTail);
767
+ if (failures.length) {
768
+ lines.push("");
769
+ lines.push("## Failure output");
770
+ for (const r of failures) {
771
+ lines.push("");
772
+ lines.push(`### ${r.checkId}`);
773
+ lines.push("");
774
+ lines.push("```");
775
+ lines.push(r.outputTail ?? "");
776
+ lines.push("```");
777
+ }
778
+ }
779
+ lines.push("");
780
+ lines.push(
781
+ "_Generated by Veris. A `Partial` verdict means one or more capabilities did not run \u2014 it is not a pass._"
782
+ );
783
+ lines.push("");
784
+ return lines.join("\n");
785
+ }
786
+ var STATE_LABEL;
787
+ var init_markdown = __esm({
788
+ "src/reporters/markdown.ts"() {
789
+ "use strict";
790
+ STATE_LABEL = {
791
+ verified: "Verified",
792
+ failed: "Failed",
793
+ partial: "Partial"
794
+ };
795
+ }
796
+ });
797
+
798
+ // src/cli/commands/verify.ts
799
+ var verify_exports = {};
800
+ __export(verify_exports, {
801
+ runVerify: () => runVerify
802
+ });
803
+ async function runVerify(root, opts = {}) {
804
+ const project = await detectProject(root);
805
+ const config = await loadConfig(root);
806
+ const checks = config?.checks?.length ? config.checks : DEFAULT_CHECKS;
807
+ const run = await runChecks(project, checks, root);
808
+ const reportRef = await writeReport(root, run.id, renderMarkdown(run));
809
+ run.reportRef = reportRef;
810
+ const runDir = await createRunDir(root, run.id);
811
+ await writeMetadata(runDir, run);
812
+ process.stdout.write(`${renderRun(run)}
813
+ `);
814
+ return verdictExitCode(run.verdict, opts);
815
+ }
816
+ var DEFAULT_CHECKS;
817
+ var init_verify = __esm({
818
+ "src/cli/commands/verify.ts"() {
819
+ "use strict";
820
+ init_detect();
821
+ init_load();
822
+ init_orchestrator();
823
+ init_verdict();
824
+ init_store();
825
+ init_markdown();
826
+ init_terminal();
827
+ DEFAULT_CHECKS = ["types", "lint", "unit"];
828
+ }
829
+ });
830
+
831
+ // src/cli/commands/report.ts
832
+ var report_exports = {};
833
+ __export(report_exports, {
834
+ runReport: () => runReport
835
+ });
836
+ import { readdirSync, readFileSync as readFileSync2 } from "fs";
837
+ import { join as join6 } from "path";
838
+ async function runReport(root) {
839
+ const dir = join6(root, ".veris", "reports");
840
+ let files;
841
+ try {
842
+ files = readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
843
+ } catch {
844
+ process.stdout.write("No reports yet. Run `veris verify` first.\n");
845
+ return 0;
846
+ }
847
+ const latest = files.at(-1);
848
+ if (!latest) {
849
+ process.stdout.write("No reports yet. Run `veris verify` first.\n");
850
+ return 0;
851
+ }
852
+ process.stdout.write(`${readFileSync2(join6(dir, latest), "utf8")}
853
+ `);
854
+ return 0;
855
+ }
856
+ var init_report = __esm({
857
+ "src/cli/commands/report.ts"() {
858
+ "use strict";
859
+ }
860
+ });
861
+
862
+ // src/affected/gate.ts
863
+ function affectedChecks(files, project) {
864
+ const available = new Set(
865
+ project.capabilities.filter((c) => c.available).map((c) => c.id)
866
+ );
867
+ const wanted = /* @__PURE__ */ new Set();
868
+ const reasonByCheck = {};
869
+ const want = (id, reason) => {
870
+ if (available.has(id) && !wanted.has(id)) {
871
+ wanted.add(id);
872
+ reasonByCheck[id] = reason;
873
+ }
874
+ };
875
+ const wantAll = (reason) => {
876
+ for (const id of available) want(id, reason);
877
+ };
878
+ for (const f of files) {
879
+ if (CONFIG_RE.test(f)) {
880
+ wantAll(`config changed (${f})`);
881
+ } else if (DOC_RE.test(f)) {
882
+ } else if (TEST_RE.test(f)) {
883
+ want("unit", "test file changed");
884
+ want("lint", "test file changed");
885
+ } else if (TS_RE.test(f)) {
886
+ want("types", "TypeScript changed");
887
+ want("lint", "TypeScript changed");
888
+ want("unit", "TypeScript changed");
889
+ } else if (JS_RE.test(f)) {
890
+ want("lint", "JavaScript changed");
891
+ want("unit", "JavaScript changed");
892
+ } else {
893
+ wantAll(`unrecognized file changed (${f})`);
894
+ }
895
+ }
896
+ return {
897
+ checks: ORDER.filter((id) => wanted.has(id)),
898
+ reasonByCheck,
899
+ changedCount: files.length
900
+ };
901
+ }
902
+ var ORDER, CONFIG_RE, TEST_RE, TS_RE, JS_RE, DOC_RE;
903
+ var init_gate = __esm({
904
+ "src/affected/gate.ts"() {
905
+ "use strict";
906
+ ORDER = ["types", "lint", "unit", "browser"];
907
+ CONFIG_RE = /(^|\/)(tsconfig[^/]*\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|package\.json|veris\.config\.[^/]+)$/;
908
+ TEST_RE = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|__tests__)\//;
909
+ TS_RE = /\.[cm]?tsx?$/;
910
+ JS_RE = /\.[cm]?jsx?$/;
911
+ DOC_RE = /(\.(md|mdx|markdown|txt|png|jpe?g|gif|svg|webp|ico)$)|((^|\/)LICENSE$)/i;
912
+ }
913
+ });
914
+
915
+ // src/git/changes.ts
916
+ function collect(set, out) {
917
+ for (const line of out.split("\n")) {
918
+ const f = line.trim();
919
+ if (f) set.add(f);
920
+ }
921
+ }
922
+ async function changedFiles(root, opts = {}) {
923
+ const base = opts.base ?? null;
924
+ const files = /* @__PURE__ */ new Set();
925
+ const diffArgs = base ? ["diff", "--name-only", base] : ["diff", "--name-only", "HEAD"];
926
+ const diff = await exec("git", diffArgs, { cwd: root });
927
+ if (diff.code === 0) collect(files, diff.stdout);
928
+ const untracked = await exec(
929
+ "git",
930
+ ["ls-files", "--others", "--exclude-standard"],
931
+ { cwd: root }
932
+ );
933
+ if (untracked.code === 0) collect(files, untracked.stdout);
934
+ return { files: [...files].sort(), base };
935
+ }
936
+ var init_changes = __esm({
937
+ "src/git/changes.ts"() {
938
+ "use strict";
939
+ init_exec();
940
+ }
941
+ });
942
+
943
+ // src/cli/commands/affected.ts
944
+ var affected_exports = {};
945
+ __export(affected_exports, {
946
+ runAffected: () => runAffected
947
+ });
948
+ async function runAffected(root, opts = {}) {
949
+ const project = await detectProject(root);
950
+ const { files } = await changedFiles(root, { base: opts.base });
951
+ const plan = affectedChecks(files, project);
952
+ if (plan.checks.length === 0) {
953
+ process.stdout.write(
954
+ `Nothing affected \u2014 ${files.length} changed file(s), no checks to run.
955
+ `
956
+ );
957
+ return 0;
958
+ }
959
+ const run = await runChecks(project, plan.checks, root);
960
+ run.scope = { kind: "affected", changedCount: files.length };
961
+ const affected = new Set(plan.checks);
962
+ for (const cap of project.capabilities) {
963
+ if (cap.available && cap.id !== "browser" && !affected.has(cap.id)) {
964
+ run.results.push({
965
+ checkId: cap.id,
966
+ status: "skipped",
967
+ durationMs: 0,
968
+ summary: "not affected by changes"
969
+ });
970
+ }
971
+ }
972
+ const reportRef = await writeReport(root, run.id, renderMarkdown(run));
973
+ run.reportRef = reportRef;
974
+ const runDir = await createRunDir(root, run.id);
975
+ await writeMetadata(runDir, run);
976
+ process.stdout.write(`${renderRun(run)}
977
+ `);
978
+ return verdictExitCode(run.verdict, opts);
979
+ }
980
+ var init_affected = __esm({
981
+ "src/cli/commands/affected.ts"() {
982
+ "use strict";
983
+ init_gate();
984
+ init_detect();
985
+ init_orchestrator();
986
+ init_verdict();
987
+ init_store();
988
+ init_changes();
989
+ init_markdown();
990
+ init_terminal();
991
+ }
992
+ });
993
+
994
+ // src/watch/watcher.ts
995
+ import {
996
+ watch as fsWatch,
997
+ readdirSync as readdirSync2,
998
+ statSync
999
+ } from "fs";
1000
+ import { basename, join as join7, relative as relative2 } from "path";
1001
+ function watch(root, opts, onBatch) {
1002
+ const debounceMs = opts.debounceMs ?? 150;
1003
+ const rootBase = basename(root);
1004
+ let pending = /* @__PURE__ */ new Set();
1005
+ let timer = null;
1006
+ const flush = () => {
1007
+ const batch = [...pending];
1008
+ pending = /* @__PURE__ */ new Set();
1009
+ timer = null;
1010
+ if (batch.length) onBatch(batch);
1011
+ };
1012
+ const schedule = (rel) => {
1013
+ if (!rel || rel === rootBase || IGNORE.test(rel)) return;
1014
+ pending.add(rel);
1015
+ if (timer) clearTimeout(timer);
1016
+ timer = setTimeout(flush, debounceMs);
1017
+ };
1018
+ if (opts.poll) {
1019
+ const interval = opts.pollIntervalMs ?? 400;
1020
+ const mtimes = scanMtimes(root);
1021
+ const tick = setInterval(() => {
1022
+ const next = scanMtimes(root);
1023
+ for (const [p, m] of next) {
1024
+ if (mtimes.get(p) !== m) schedule(p);
1025
+ }
1026
+ mtimes.clear();
1027
+ for (const [p, m] of next) mtimes.set(p, m);
1028
+ }, interval);
1029
+ return () => {
1030
+ clearInterval(tick);
1031
+ if (timer) clearTimeout(timer);
1032
+ };
1033
+ }
1034
+ let watcher;
1035
+ try {
1036
+ watcher = fsWatch(root, { recursive: true }, (_event, filename) => {
1037
+ if (filename) schedule(filename.toString());
1038
+ });
1039
+ } catch {
1040
+ throw new Error(
1041
+ "recursive fs.watch is unavailable on this platform; rerun with --poll"
1042
+ );
1043
+ }
1044
+ return () => {
1045
+ if (timer) clearTimeout(timer);
1046
+ watcher.close();
1047
+ };
1048
+ }
1049
+ function scanMtimes(root) {
1050
+ const out = /* @__PURE__ */ new Map();
1051
+ const walk = (dir) => {
1052
+ let entries;
1053
+ try {
1054
+ entries = readdirSync2(dir);
1055
+ } catch {
1056
+ return;
1057
+ }
1058
+ for (const name of entries) {
1059
+ const abs = join7(dir, name);
1060
+ const rel = relative2(root, abs);
1061
+ if (IGNORE.test(rel)) continue;
1062
+ let st;
1063
+ try {
1064
+ st = statSync(abs);
1065
+ } catch {
1066
+ continue;
1067
+ }
1068
+ if (st.isDirectory()) walk(abs);
1069
+ else out.set(rel, st.mtimeMs);
1070
+ }
1071
+ };
1072
+ walk(root);
1073
+ return out;
1074
+ }
1075
+ var IGNORE;
1076
+ var init_watcher = __esm({
1077
+ "src/watch/watcher.ts"() {
1078
+ "use strict";
1079
+ IGNORE = /(^|\/)(\.git|\.veris|\.agentloop|\.agentflight|node_modules|dist)(\/|$)/;
1080
+ }
1081
+ });
1082
+
1083
+ // src/cli/commands/watch.ts
1084
+ var watch_exports = {};
1085
+ __export(watch_exports, {
1086
+ buildWatchResults: () => buildWatchResults,
1087
+ runWatch: () => runWatch
1088
+ });
1089
+ function buildWatchResults(project, affected, fresh, cache) {
1090
+ const affectedSet = new Set(affected);
1091
+ const freshById = new Map(fresh.map((r) => [r.checkId, r]));
1092
+ const out = [];
1093
+ for (const cap of project.capabilities) {
1094
+ if (!cap.available || cap.id === "browser") continue;
1095
+ const f = freshById.get(cap.id);
1096
+ if (f) {
1097
+ out.push({ ...f, cached: false });
1098
+ continue;
1099
+ }
1100
+ const c = cache.get(cap.id);
1101
+ if (c && !affectedSet.has(cap.id)) {
1102
+ out.push({ ...c, cached: true });
1103
+ continue;
1104
+ }
1105
+ out.push({
1106
+ checkId: cap.id,
1107
+ status: "skipped",
1108
+ durationMs: 0,
1109
+ summary: "not affected by changes"
1110
+ });
1111
+ }
1112
+ return out;
1113
+ }
1114
+ async function runWatch(root, opts = {}) {
1115
+ const cache = /* @__PURE__ */ new Map();
1116
+ let running = false;
1117
+ const availableIds = (project) => project.capabilities.filter((c) => c.available && c.id !== "browser").map((c) => c.id);
1118
+ const tick = async (initial) => {
1119
+ if (running) return;
1120
+ running = true;
1121
+ try {
1122
+ const project = await detectProject(root);
1123
+ const { files } = await changedFiles(root);
1124
+ const checks = initial ? availableIds(project) : affectedChecks(files, project).checks;
1125
+ let fresh = [];
1126
+ if (checks.length) {
1127
+ const r = await runChecks(project, checks, root);
1128
+ fresh = r.results;
1129
+ for (const result of fresh) cache.set(result.checkId, { ...result });
1130
+ }
1131
+ const results = buildWatchResults(project, checks, fresh, cache);
1132
+ const availableCaps = project.capabilities.filter(
1133
+ (c) => c.available && c.id !== "browser"
1134
+ );
1135
+ const verdict = computeVerdict(results, availableCaps);
1136
+ const run = {
1137
+ id: "watch",
1138
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1139
+ project,
1140
+ results,
1141
+ verdict,
1142
+ env: {
1143
+ os: process.platform,
1144
+ node: process.version,
1145
+ pm: project.packageManager,
1146
+ ci: false,
1147
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1148
+ },
1149
+ scope: { kind: "watch", changedCount: files.length }
1150
+ };
1151
+ process.stdout.write(`${renderRun(run)}
1152
+ `);
1153
+ } finally {
1154
+ running = false;
1155
+ }
1156
+ };
1157
+ process.stdout.write("veris watch \u2014 press Ctrl-C to stop\n");
1158
+ await tick(true);
1159
+ return await new Promise((resolve) => {
1160
+ let stop;
1161
+ try {
1162
+ stop = watch(root, { poll: opts.poll }, () => {
1163
+ void tick(false);
1164
+ });
1165
+ } catch (err) {
1166
+ process.stderr.write(
1167
+ `veris: ${err instanceof Error ? err.message : String(err)}
1168
+ `
1169
+ );
1170
+ resolve(1);
1171
+ return;
1172
+ }
1173
+ process.on("SIGINT", () => {
1174
+ stop();
1175
+ process.stdout.write("\nStopped.\n");
1176
+ resolve(0);
1177
+ });
1178
+ });
1179
+ }
1180
+ var init_watch = __esm({
1181
+ "src/cli/commands/watch.ts"() {
1182
+ "use strict";
1183
+ init_gate();
1184
+ init_detect();
1185
+ init_orchestrator();
1186
+ init_verdict();
1187
+ init_changes();
1188
+ init_terminal();
1189
+ init_watcher();
1190
+ }
1191
+ });
1192
+
1193
+ // src/cli/index.ts
1194
+ import { realpathSync } from "fs";
1195
+ import { argv } from "process";
1196
+ import { pathToFileURL } from "url";
1197
+ import cac from "cac";
1198
+
1199
+ // src/version.ts
1200
+ import { readFileSync } from "fs";
1201
+ import { fileURLToPath } from "url";
1202
+ var pkgUrl = new URL("../package.json", import.meta.url);
1203
+ var VERSION = JSON.parse(
1204
+ readFileSync(fileURLToPath(pkgUrl), "utf8")
1205
+ ).version;
1206
+
1207
+ // src/cli/index.ts
1208
+ function buildCli() {
1209
+ const cli = cac("veris");
1210
+ cli.version(VERSION);
1211
+ cli.help();
1212
+ cli.command("doctor", "Report environment + capabilities (read-only)").action(async () => {
1213
+ const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
1214
+ process.exitCode = await runDoctor2(process.cwd());
1215
+ });
1216
+ cli.command("test", "Run the detected unit test runner").action(async () => {
1217
+ const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
1218
+ process.exitCode = await runTest2(process.cwd());
1219
+ });
1220
+ cli.command("init", "Detect the stack and set up .veris (idempotent)").action(async () => {
1221
+ const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1222
+ process.exitCode = await runInit2(process.cwd());
1223
+ });
1224
+ cli.command("verify", "Run the full check set and produce a verdict + report").option("--partial-ok", "Exit 0 even when the verdict is partial").action(async (opts) => {
1225
+ const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
1226
+ process.exitCode = await runVerify2(process.cwd(), {
1227
+ partialOk: opts.partialOk
1228
+ });
1229
+ });
1230
+ cli.command("report", "Print the latest verification report").action(async () => {
1231
+ const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
1232
+ process.exitCode = await runReport2(process.cwd());
1233
+ });
1234
+ 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").action(async (opts) => {
1235
+ const { runAffected: runAffected2 } = await Promise.resolve().then(() => (init_affected(), affected_exports));
1236
+ process.exitCode = await runAffected2(process.cwd(), {
1237
+ base: opts.base,
1238
+ partialOk: opts.partialOk
1239
+ });
1240
+ });
1241
+ 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) => {
1242
+ const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
1243
+ process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
1244
+ });
1245
+ return { raw: cli, version: VERSION };
1246
+ }
1247
+ async function main(argv2) {
1248
+ try {
1249
+ const { raw } = buildCli();
1250
+ if (argv2.length <= 2) {
1251
+ raw.outputHelp();
1252
+ return;
1253
+ }
1254
+ raw.parse(argv2);
1255
+ } catch (err) {
1256
+ const msg = err instanceof Error ? err.message : String(err);
1257
+ process.stderr.write(`veris: ${msg}
1258
+ `);
1259
+ process.exitCode = 1;
1260
+ }
1261
+ }
1262
+ var invokedPath = argv[1] ? realpathSync(argv[1]) : "";
1263
+ var isEntry = import.meta.url === pathToFileURL(invokedPath).href;
1264
+ if (isEntry) void main(argv);
1265
+ export {
1266
+ buildCli,
1267
+ main
1268
+ };