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