veriskit 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,1073 +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
- let detail = "";
1154
- try {
1155
- const errBody = await res.json();
1156
- if (errBody?.message) detail = `: ${errBody.message}`;
1157
- } catch {
1158
- }
1159
- throw new GitHubApiError(
1160
- res.status,
1161
- `GitHub API ${method} ${path} -> ${res.status}${detail}`
1162
- );
1163
- }
1164
- return res.status === 204 ? null : res.json();
1165
- }
1166
- function repoBase(ctx) {
1167
- 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
+ };
1168
678
  }
1169
- async function upsertComment(ctx, body) {
1170
- const base = repoBase(ctx);
1171
- const comments = await gh(
1172
- "GET",
1173
- `${base}/issues/${ctx.prNumber}/comments?per_page=100`,
1174
- ctx.token
1175
- );
1176
- const existing = comments.find((c) => (c.body ?? "").includes(MARKER));
1177
- if (existing) {
1178
- await gh("PATCH", `${base}/issues/comments/${existing.id}`, ctx.token, {
1179
- body
1180
- });
1181
- } else {
1182
- await gh("POST", `${base}/issues/${ctx.prNumber}/comments`, ctx.token, {
1183
- body
1184
- });
1185
- }
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" };
1186
685
  }
1187
- async function createCheckRun(ctx, opts) {
1188
- await gh("POST", `${repoBase(ctx)}/check-runs`, ctx.token, {
1189
- name: opts.name,
1190
- head_sha: ctx.headSha,
1191
- status: "completed",
1192
- conclusion: opts.conclusion,
1193
- output: { title: opts.title, summary: opts.summary }
1194
- });
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" };
1195
694
  }
1196
- var GitHubApiError;
1197
- var init_github = __esm({
1198
- "src/publish/github.ts"() {
1199
- "use strict";
1200
- init_comment();
1201
- GitHubApiError = class extends Error {
1202
- status;
1203
- constructor(status, message) {
1204
- super(message);
1205
- this.name = "GitHubApiError";
1206
- this.status = status;
1207
- }
1208
- };
1209
- }
1210
- });
1211
-
1212
- // src/publish/publish.ts
1213
- async function publishToGitHub(run, record) {
1214
- const ctx = resolvePublishContext();
1215
- if (!ctx) {
1216
- process.stdout.write(
1217
- "veris: --github set but no GitHub PR context found (need GITHUB_TOKEN + a pull_request event); skipping publish.\n"
1218
- );
1219
- return;
1220
- }
1221
- try {
1222
- await upsertComment(ctx, renderComment(run, record));
1223
- await createCheckRun(ctx, {
1224
- name: "VerisKit",
1225
- conclusion: CONCLUSION[run.verdict.state],
1226
- title: `VerisKit: ${run.verdict.state}`,
1227
- summary: renderMarkdown(run, record)
1228
- });
1229
- process.stdout.write(
1230
- `veris: published the verdict to PR #${ctx.prNumber}
1231
- `
1232
- );
1233
- } catch (err) {
1234
- const msg = err instanceof Error ? err.message : String(err);
1235
- process.stderr.write(`veris: GitHub publish failed: ${msg}
1236
- `);
1237
- }
1238
- }
1239
- var CONCLUSION;
1240
- var init_publish = __esm({
1241
- "src/publish/publish.ts"() {
1242
- "use strict";
1243
- init_markdown();
1244
- init_comment();
1245
- init_context();
1246
- init_github();
1247
- CONCLUSION = {
1248
- verified: "success",
1249
- failed: "failure",
1250
- partial: "neutral"
1251
- };
1252
- }
1253
- });
1254
-
1255
- // src/cli/commands/verify.ts
1256
- var verify_exports = {};
1257
- __export(verify_exports, {
1258
- resolveChecks: () => resolveChecks,
1259
- runVerify: () => runVerify
1260
- });
1261
- function resolveChecks(configChecks, project, opts) {
1262
- let checks = configChecks?.length ? configChecks : DEFAULT_CHECKS;
1263
- if (opts.browser && !checks.includes("browser")) {
1264
- const cap = project.capabilities.find((c) => c.id === "browser");
1265
- if (cap?.available) checks = [...checks, "browser"];
1266
- }
1267
- return checks;
1268
- }
1269
- async function runVerify(root, opts = {}) {
1270
- const project = await detectProject(root);
1271
- const config = await loadConfig(root);
1272
- const checks = resolveChecks(config?.checks, project, opts);
1273
- const run = await runChecks(project, checks, root);
1274
- const git = await gitAnchor(root);
1275
- const logDigests = await digestLogs(run);
1276
- const record = buildRecord(run, git, logDigests, VERSION);
1277
- const reportRef = await writeReport(
1278
- root,
1279
- run.id,
1280
- renderMarkdown(run, record)
1281
- );
1282
- run.reportRef = reportRef;
1283
- const runDir = await createRunDir(root, run.id);
1284
- await writeEvidence(runDir, record);
1285
- process.stdout.write(`${renderRun(run, record)}
1286
- `);
1287
- if (opts.github) await publishToGitHub(run, record);
1288
- return verdictExitCode(run.verdict, opts);
1289
- }
1290
- var DEFAULT_CHECKS;
1291
- var init_verify = __esm({
1292
- "src/cli/commands/verify.ts"() {
1293
- "use strict";
1294
- init_detect();
1295
- init_load();
1296
- init_orchestrator();
1297
- init_verdict();
1298
- init_record();
1299
- init_store();
1300
- init_changes();
1301
- init_publish();
1302
- init_markdown();
1303
- init_terminal();
1304
- init_version();
1305
- DEFAULT_CHECKS = ["types", "lint", "unit"];
1306
- }
1307
- });
1308
-
1309
- // src/cli/commands/report.ts
1310
- var report_exports = {};
1311
- __export(report_exports, {
1312
- runReport: () => runReport
1313
- });
1314
- import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
1315
- import { join as join6 } from "path";
1316
- async function runReport(root) {
1317
- const dir = join6(root, ".veris", "reports");
1318
- let files;
1319
- try {
1320
- files = readdirSync2(dir).filter((f) => f.endsWith(".md")).sort();
1321
- } catch {
1322
- process.stdout.write("No reports yet. Run `veris verify` first.\n");
1323
- return 0;
1324
- }
1325
- const latest = files.at(-1);
1326
- if (!latest) {
1327
- process.stdout.write("No reports yet. Run `veris verify` first.\n");
1328
- return 0;
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" };
1329
698
  }
1330
- process.stdout.write(`${readFileSync3(join6(dir, latest), "utf8")}
1331
- `);
1332
- return 0;
699
+ return {
700
+ id: "browser",
701
+ available: false,
702
+ reason: "no browser runner configured"
703
+ };
1333
704
  }
1334
- var init_report = __esm({
1335
- "src/cli/commands/report.ts"() {
1336
- "use strict";
1337
- }
1338
- });
1339
705
 
1340
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;
1341
713
  function affectedChecks(files, project) {
1342
714
  const available = new Set(
1343
715
  project.capabilities.filter((c) => c.available).map((c) => c.id)
@@ -1377,1327 +749,635 @@ function affectedChecks(files, project) {
1377
749
  changedCount: files.length
1378
750
  };
1379
751
  }
1380
- var ORDER, CONFIG_RE, TEST_RE, TS_RE, JS_RE, DOC_RE;
1381
- var init_gate = __esm({
1382
- "src/affected/gate.ts"() {
1383
- "use strict";
1384
- ORDER = ["types", "lint", "unit", "browser"];
1385
- CONFIG_RE = /(^|\/)(tsconfig[^/]*\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|package\.json|veris\.config\.[^/]+)$/;
1386
- TEST_RE = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|__tests__)\//;
1387
- TS_RE = /\.[cm]?tsx?$/;
1388
- JS_RE = /\.[cm]?jsx?$/;
1389
- DOC_RE = /(\.(md|mdx|markdown|txt|png|jpe?g|gif|svg|webp|ico)$)|((^|\/)LICENSE$)/i;
1390
- }
1391
- });
1392
752
 
1393
- // src/project-graph/discover.ts
1394
- import { readdirSync as readdirSync3, statSync } from "fs";
1395
- import { join as join7, relative as relative2, sep } from "path";
1396
- function toPosix(p) {
1397
- return sep === "/" ? p : p.split(sep).join("/");
1398
- }
1399
- function classify(rel) {
1400
- if (TEST_RE2.test(rel)) return "test";
1401
- if (CONFIG_RE2.test(rel)) return "config";
1402
- return "source";
1403
- }
1404
- function discoverFiles(root) {
1405
- const out = [];
1406
- const walk = (dir) => {
1407
- let entries;
1408
- try {
1409
- entries = readdirSync3(dir);
1410
- } catch {
1411
- return;
1412
- }
1413
- for (const name of entries) {
1414
- const abs = join7(dir, name);
1415
- const rel = toPosix(relative2(root, abs));
1416
- if (IGNORE.test(rel)) continue;
1417
- let st;
1418
- try {
1419
- st = statSync(abs);
1420
- } catch {
1421
- continue;
1422
- }
1423
- if (st.isDirectory()) {
1424
- walk(abs);
1425
- } else if (CODE_RE.test(rel)) {
1426
- out.push({ file: rel, kind: classify(rel) });
1427
- }
1428
- }
1429
- };
1430
- walk(root);
1431
- 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"));
1432
757
  }
1433
- var IGNORE, CODE_RE, TEST_RE2, CONFIG_RE2;
1434
- var init_discover = __esm({
1435
- "src/project-graph/discover.ts"() {
1436
- "use strict";
1437
- IGNORE = /(^|\/)(\.git|\.claude|\.veris|\.agentloop|\.agentflight|node_modules|dist|coverage|build|fixtures|__fixtures__)(\/|$)/;
1438
- CODE_RE = /\.[cm]?[jt]sx?$/;
1439
- TEST_RE2 = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|tests|__tests__)\//;
1440
- CONFIG_RE2 = /(^|\/)([^/]+\.config\.[cm]?[jt]sx?)$/;
1441
- }
1442
- });
1443
758
 
1444
- // src/project-graph/scanner-resolver.ts
1445
- import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
1446
- import { dirname, join as join8, relative as relative3, resolve } from "path";
1447
- function extractSpecifiers(text) {
1448
- const specs = [];
1449
- SPEC_RE.lastIndex = 0;
1450
- let m;
1451
- m = SPEC_RE.exec(text);
1452
- while (m !== null) {
1453
- const s = m[1] ?? m[2] ?? m[3] ?? m[4];
1454
- if (s) specs.push(s);
1455
- m = SPEC_RE.exec(text);
1456
- }
1457
- return specs;
1458
- }
1459
- function isFile(p) {
1460
- try {
1461
- return statSync2(p).isFile();
1462
- } catch {
1463
- return false;
1464
- }
1465
- }
1466
- function resolveRelative(fromDir, spec) {
1467
- const base = resolve(fromDir, spec);
1468
- const candidates = [base];
1469
- for (const ext of EXTS) candidates.push(base + ext);
1470
- const extMatch = base.match(/\.[cm]?[jt]sx?$/);
1471
- if (extMatch) {
1472
- const noExt = base.slice(0, -extMatch[0].length);
1473
- for (const ext of EXTS) candidates.push(noExt + ext);
1474
- }
1475
- for (const ext of EXTS) candidates.push(join8(base, `index${ext}`));
1476
- for (const c of candidates) {
1477
- if (existsSync3(c) && isFile(c)) return c;
1478
- }
1479
- return null;
1480
- }
1481
- function scannerImports(root, file) {
1482
- let text;
1483
- try {
1484
- text = readFileSync4(join8(root, file), "utf8");
1485
- } catch {
1486
- return [];
1487
- }
1488
- const dir = dirname(join8(root, file));
1489
- const out = /* @__PURE__ */ new Set();
1490
- for (const spec of extractSpecifiers(text)) {
1491
- if (!spec.startsWith(".")) continue;
1492
- const resolved = resolveRelative(dir, spec);
1493
- if (resolved) out.add(toPosix(relative3(root, resolved)));
1494
- }
1495
- return [...out];
1496
- }
1497
- var EXTS, SPEC_RE;
1498
- var init_scanner_resolver = __esm({
1499
- "src/project-graph/scanner-resolver.ts"() {
1500
- "use strict";
1501
- init_discover();
1502
- EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
1503
- SPEC_RE = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]|(?:^|[^.\w])import\s*['"]([^'"]+)['"]|\bimport\(\s*['"]([^'"]+)['"]\s*\)|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
1504
- }
1505
- });
759
+ // src/core/orchestrate.ts
760
+ init_record();
1506
761
 
1507
- // src/project-graph/ts-resolver.ts
1508
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
1509
- import { createRequire } from "module";
1510
- import { join as join9, relative as relative4, sep as sep2 } from "path";
1511
- function hasClassicApi(mod) {
1512
- const m = mod;
1513
- 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}`;
1514
772
  }
1515
- function loadTypeScript(root) {
1516
- try {
1517
- const require2 = createRequire(join9(root, "__veris__.js"));
1518
- const tsPath = require2.resolve("typescript");
1519
- const mod = require2(tsPath);
1520
- return hasClassicApi(mod) ? mod : null;
1521
- } catch {
1522
- return null;
1523
- }
773
+ async function createRunDir(root, id) {
774
+ const dir = join3(root, ".veris", "runs", id);
775
+ await ensureDir(dir);
776
+ return dir;
1524
777
  }
1525
- function loadCompilerOptions(tsmod, root) {
1526
- const configPath = join9(root, "tsconfig.json");
1527
- const read = tsmod.readConfigFile(configPath, tsmod.sys.readFile);
1528
- if (read.error || !read.config) return {};
1529
- const parsed = tsmod.parseJsonConfigFileContent(read.config, tsmod.sys, root);
1530
- 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;
1531
782
  }
1532
- function tsImports(tsmod, root, options, file) {
1533
- let text;
1534
- try {
1535
- text = readFileSync5(join9(root, file), "utf8");
1536
- } catch {
1537
- return [];
1538
- }
1539
- const abs = join9(root, file);
1540
- const out = /* @__PURE__ */ new Set();
1541
- try {
1542
- const pre = tsmod.preProcessFile(text, true, true);
1543
- for (const imp of pre.importedFiles) {
1544
- const res = tsmod.resolveModuleName(
1545
- imp.fileName,
1546
- abs,
1547
- options,
1548
- tsmod.sys
1549
- );
1550
- const resolved = res.resolvedModule?.resolvedFileName;
1551
- if (resolved && !resolved.includes("node_modules") && resolved.startsWith(root + sep2)) {
1552
- out.add(toPosix(relative4(root, resolved)));
1553
- }
1554
- }
1555
- } catch {
1556
- }
1557
- 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;
1558
789
  }
1559
- function selectResolver(root) {
1560
- const tsmod = existsSync4(join9(root, "tsconfig.json")) ? loadTypeScript(root) : null;
1561
- 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;
1562
800
  try {
1563
- const options = loadCompilerOptions(tsmod, root);
1564
- return {
1565
- resolver: "typescript",
1566
- importsOf: (file) => tsImports(tsmod, root, options, file)
1567
- };
801
+ out[r.checkId] = sha256(await readFile2(r.logRef, "utf8"));
1568
802
  } catch {
1569
803
  }
1570
804
  }
1571
- return {
1572
- resolver: "scanner",
1573
- importsOf: (file) => scannerImports(root, file)
1574
- };
805
+ return out;
1575
806
  }
1576
- var init_ts_resolver = __esm({
1577
- "src/project-graph/ts-resolver.ts"() {
1578
- "use strict";
1579
- init_discover();
1580
- init_scanner_resolver();
1581
- }
1582
- });
1583
-
1584
- // src/project-graph/graph.ts
1585
- var graph_exports = {};
1586
- __export(graph_exports, {
1587
- buildGraph: () => buildGraph
1588
- });
1589
- async function buildGraph(project) {
1590
- const root = project.root;
1591
- const files = discoverFiles(root);
1592
- const known = new Set(files.map((f) => f.file));
1593
- const { resolver, importsOf } = selectResolver(root);
1594
- const nodes = {};
1595
- for (const f of files) {
1596
- nodes[f.file] = { file: f.file, kind: f.kind, imports: [], importedBy: [] };
1597
- }
1598
- for (const f of files) {
1599
- const imps = importsOf(f.file).filter((i) => known.has(i) && i !== f.file);
1600
- nodes[f.file].imports = imps;
1601
- for (const i of imps) nodes[i]?.importedBy.push(f.file);
1602
- }
1603
- return {
1604
- root,
1605
- resolver,
1606
- nodes,
1607
- sourceFiles: files.filter((f) => f.kind === "source").map((f) => f.file),
1608
- testFiles: files.filter((f) => f.kind === "test").map((f) => f.file)
1609
- };
1610
- }
1611
- var init_graph = __esm({
1612
- "src/project-graph/graph.ts"() {
1613
- "use strict";
1614
- init_discover();
1615
- init_ts_resolver();
1616
- }
1617
- });
1618
-
1619
- // src/project-graph/analyze.ts
1620
- function transitiveDependents(graph, file) {
1621
- const seen = /* @__PURE__ */ new Set();
1622
- const stack = [...graph.nodes[file]?.importedBy ?? []];
1623
- while (stack.length) {
1624
- const f = stack.pop();
1625
- if (!f || seen.has(f)) continue;
1626
- seen.add(f);
1627
- for (const d of graph.nodes[f]?.importedBy ?? []) stack.push(d);
1628
- }
1629
- return seen;
1630
- }
1631
- function reachableFromTests(graph) {
1632
- const seen = /* @__PURE__ */ new Set();
1633
- const stack = [...graph.testFiles];
1634
- while (stack.length) {
1635
- const f = stack.pop();
1636
- if (!f || seen.has(f)) continue;
1637
- seen.add(f);
1638
- for (const i of graph.nodes[f]?.imports ?? []) stack.push(i);
1639
- }
1640
- return seen;
1641
- }
1642
- function analyze(graph, changed = []) {
1643
- const blastRadius = {};
1644
- for (const file of Object.keys(graph.nodes)) {
1645
- blastRadius[file] = transitiveDependents(graph, file).size;
1646
- }
1647
- const reached = reachableFromTests(graph);
1648
- const byBlast = (a, b) => (blastRadius[b] ?? 0) - (blastRadius[a] ?? 0);
1649
- const untested = graph.sourceFiles.filter((f) => !reached.has(f)).sort(byBlast);
1650
- const changedSet = new Set(changed);
1651
- const untestedSet = new Set(untested);
1652
- const risky = graph.sourceFiles.filter(
1653
- (f) => (blastRadius[f] ?? 0) > 0 && (untestedSet.has(f) || changedSet.has(f))
1654
- ).sort(byBlast);
1655
- return { untested, blastRadius, risky };
1656
- }
1657
- var init_analyze = __esm({
1658
- "src/project-graph/analyze.ts"() {
1659
- "use strict";
1660
- }
1661
- });
1662
807
 
1663
- // src/affected/select.ts
1664
- var select_exports = {};
1665
- __export(select_exports, {
1666
- selectAffectedTests: () => selectAffectedTests
1667
- });
1668
- function selectAffectedTests(graph, changed) {
1669
- if (graph.resolver !== "typescript") {
1670
- return {
1671
- mode: "full",
1672
- testFiles: [],
1673
- reason: "scanner graph can miss aliased/subpath imports \u2014 running the full suite"
1674
- };
1675
- }
1676
- for (const f of changed) {
1677
- if (GLOBAL_RE.test(f)) {
1678
- return {
1679
- mode: "full",
1680
- testFiles: [],
1681
- reason: `global/config change (${f})`
1682
- };
1683
- }
1684
- if (!(f in graph.nodes)) {
1685
- return {
1686
- mode: "full",
1687
- testFiles: [],
1688
- reason: `unresolved changed file (${f})`
1689
- };
1690
- }
1691
- }
1692
- const tests = /* @__PURE__ */ new Set();
1693
- for (const f of changed) {
1694
- if (graph.nodes[f]?.kind === "test") tests.add(f);
1695
- for (const dep of transitiveDependents(graph, f)) {
1696
- if (graph.nodes[dep]?.kind === "test") tests.add(dep);
1697
- }
1698
- }
1699
- if (tests.size === 0) {
1700
- return {
1701
- mode: "full",
1702
- testFiles: [],
1703
- reason: "no tests reach the changed files"
1704
- };
1705
- }
1706
- const selected = [...tests].sort();
1707
- if (selected.some((t) => t.startsWith("-"))) {
1708
- return {
1709
- mode: "full",
1710
- testFiles: [],
1711
- reason: "a reaching test path starts with '-' (unsafe as a CLI argument)"
1712
- };
1713
- }
1714
- return { mode: "graph", testFiles: selected, reason: "" };
1715
- }
1716
- var GLOBAL_RE;
1717
- var init_select = __esm({
1718
- "src/affected/select.ts"() {
1719
- "use strict";
1720
- init_analyze();
1721
- GLOBAL_RE = /(^|\/)(tsconfig[^/]*\.json|package\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|veris\.config\.[^/]+|[^/]+\.config\.[cm]?[jt]sx?|[^/]+\.setup\.[cm]?[jt]sx?)$/;
1722
- }
1723
- });
1724
-
1725
- // src/cli/commands/affected.ts
1726
- var affected_exports = {};
1727
- __export(affected_exports, {
1728
- runAffected: () => runAffected
1729
- });
1730
- async function runAffected(root, opts = {}) {
1731
- const project = await detectProject(root);
1732
- const { files } = await changedFiles(root, { base: opts.base });
1733
- const plan = affectedChecks(files, project);
1734
- if (plan.checks.length === 0) {
1735
- process.stdout.write(
1736
- `Nothing affected \u2014 ${files.length} changed file(s), no checks to run.
1737
- `
1738
- );
1739
- return 0;
1740
- }
1741
- let targetFiles;
1742
- let narrowedNote = "";
1743
- const unitRunner = project.capabilities.find((c) => c.id === "unit")?.runner;
1744
- const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
1745
- if (plan.checks.includes("unit") && canNarrow) {
1746
- const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
1747
- const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
1748
- const graph = await buildGraph2(project);
1749
- const sel = selectAffectedTests2(graph, files);
1750
- if (sel.mode === "graph") {
1751
- targetFiles = { unit: sel.testFiles };
1752
- narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
1753
- } else {
1754
- narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
1755
- }
1756
- } else if (plan.checks.includes("unit") && unitRunner) {
1757
- narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
1758
- }
1759
- const run = await runChecks(project, plan.checks, root, { targetFiles });
1760
- run.scope = { kind: "affected", changedCount: files.length };
1761
- const affected = new Set(plan.checks);
1762
- for (const cap of project.capabilities) {
1763
- if (cap.available && cap.id !== "browser" && !affected.has(cap.id)) {
1764
- run.results.push({
1765
- checkId: cap.id,
1766
- status: "skipped",
1767
- durationMs: 0,
1768
- 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
1769
835
  });
1770
- }
1771
- }
1772
- const git = await gitAnchor(root);
1773
- const logDigests = await digestLogs(run);
1774
- const record = buildRecord(run, git, logDigests, VERSION);
1775
- const reportRef = await writeReport(
1776
- root,
1777
- run.id,
1778
- renderMarkdown(run, record)
1779
- );
1780
- run.reportRef = reportRef;
1781
- const runDir = await createRunDir(root, run.id);
1782
- await writeEvidence(runDir, record);
1783
- process.stdout.write(`${renderRun(run, record)}
1784
- `);
1785
- if (narrowedNote) process.stdout.write(`${narrowedNote}
1786
- `);
1787
- if (opts.github) await publishToGitHub(run, record);
1788
- return verdictExitCode(run.verdict, opts);
1789
- }
1790
- var init_affected = __esm({
1791
- "src/cli/commands/affected.ts"() {
1792
- "use strict";
1793
- init_gate();
1794
- init_detect();
1795
- init_orchestrator();
1796
- init_verdict();
1797
- init_record();
1798
- init_store();
1799
- init_changes();
1800
- init_publish();
1801
- init_markdown();
1802
- init_terminal();
1803
- init_version();
1804
- }
1805
- });
1806
-
1807
- // src/watch/watcher.ts
1808
- import {
1809
- watch as fsWatch,
1810
- readdirSync as readdirSync4,
1811
- statSync as statSync3
1812
- } from "fs";
1813
- import { basename as basename2, join as join10, relative as relative5 } from "path";
1814
- function watch(root, opts, onBatch) {
1815
- const debounceMs = opts.debounceMs ?? 150;
1816
- const rootBase = basename2(root);
1817
- let pending = /* @__PURE__ */ new Set();
1818
- let timer = null;
1819
- const flush = () => {
1820
- const batch = [...pending];
1821
- pending = /* @__PURE__ */ new Set();
1822
- timer = null;
1823
- if (batch.length) onBatch(batch);
1824
- };
1825
- const schedule = (rel) => {
1826
- if (!rel || rel === rootBase || IGNORE2.test(rel)) return;
1827
- pending.add(rel);
1828
- if (timer) clearTimeout(timer);
1829
- timer = setTimeout(flush, debounceMs);
1830
- };
1831
- if (opts.poll) {
1832
- const interval = opts.pollIntervalMs ?? 400;
1833
- const mtimes = scanMtimes(root);
1834
- const tick = setInterval(() => {
1835
- const next = scanMtimes(root);
1836
- for (const [p, m] of next) {
1837
- if (mtimes.get(p) !== m) schedule(p);
1838
- }
1839
- mtimes.clear();
1840
- for (const [p, m] of next) mtimes.set(p, m);
1841
- }, interval);
1842
- return () => {
1843
- clearInterval(tick);
836
+ });
837
+ child.on("error", () => {
1844
838
  if (timer) clearTimeout(timer);
1845
- };
1846
- }
1847
- let watcher;
1848
- try {
1849
- watcher = fsWatch(root, { recursive: true }, (_event, filename) => {
1850
- 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
+ });
1851
846
  });
1852
- } catch {
1853
- throw new Error(
1854
- "recursive fs.watch is unavailable on this platform; rerun with --poll"
1855
- );
1856
- }
1857
- return () => {
1858
- if (timer) clearTimeout(timer);
1859
- watcher.close();
1860
- };
1861
- }
1862
- function scanMtimes(root) {
1863
- const out = /* @__PURE__ */ new Map();
1864
- const walk = (dir) => {
1865
- let entries;
1866
- try {
1867
- entries = readdirSync4(dir);
1868
- } catch {
1869
- return;
1870
- }
1871
- for (const name of entries) {
1872
- const abs = join10(dir, name);
1873
- const rel = relative5(root, abs);
1874
- if (IGNORE2.test(rel)) continue;
1875
- let st;
1876
- try {
1877
- st = statSync3(abs);
1878
- } catch {
1879
- continue;
1880
- }
1881
- if (st.isDirectory()) walk(abs);
1882
- else out.set(rel, st.mtimeMs);
1883
- }
1884
- };
1885
- walk(root);
1886
- return out;
847
+ });
1887
848
  }
1888
- var IGNORE2;
1889
- var init_watcher = __esm({
1890
- "src/watch/watcher.ts"() {
1891
- "use strict";
1892
- IGNORE2 = /(^|\/)(\.git|\.veris|\.agentloop|\.agentflight|node_modules|dist)(\/|$)/;
1893
- }
1894
- });
1895
849
 
1896
- // src/cli/commands/watch.ts
1897
- var watch_exports = {};
1898
- __export(watch_exports, {
1899
- buildWatchResults: () => buildWatchResults,
1900
- runWatch: () => runWatch
1901
- });
1902
- function buildWatchResults(project, affected, fresh, cache) {
1903
- const affectedSet = new Set(affected);
1904
- const freshById = new Map(fresh.map((r) => [r.checkId, r]));
1905
- const out = [];
1906
- for (const cap of project.capabilities) {
1907
- if (!cap.available || cap.id === "browser") continue;
1908
- const f = freshById.get(cap.id);
1909
- if (f) {
1910
- out.push({ ...f, cached: false });
1911
- continue;
1912
- }
1913
- const c = cache.get(cap.id);
1914
- if (c && !affectedSet.has(cap.id)) {
1915
- out.push({ ...c, cached: true });
1916
- continue;
1917
- }
1918
- out.push({
1919
- checkId: cap.id,
1920
- status: "skipped",
1921
- durationMs: 0,
1922
- summary: "not affected by changes"
1923
- });
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);
1924
855
  }
1925
- return out;
1926
856
  }
1927
- async function runWatch(root, opts = {}) {
1928
- const cache = /* @__PURE__ */ new Map();
1929
- let running = false;
1930
- const availableIds = (project) => project.capabilities.filter((c) => c.available && c.id !== "browser").map((c) => c.id);
1931
- const tick = async (initial) => {
1932
- if (running) return;
1933
- running = true;
1934
- try {
1935
- const project = await detectProject(root);
1936
- const { files } = await changedFiles(root);
1937
- const checks = initial ? availableIds(project) : affectedChecks(files, project).checks;
1938
- let targetFiles;
1939
- let narrowedNote = "";
1940
- const unitRunner = project.capabilities.find(
1941
- (c) => c.id === "unit"
1942
- )?.runner;
1943
- const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
1944
- if (!initial && checks.includes("unit") && canNarrow) {
1945
- const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
1946
- const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
1947
- const graph = await buildGraph2(project);
1948
- const sel = selectAffectedTests2(graph, files);
1949
- if (sel.mode === "graph") {
1950
- targetFiles = { unit: sel.testFiles };
1951
- narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
1952
- } else {
1953
- narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
1954
- }
1955
- } else if (!initial && checks.includes("unit") && unitRunner) {
1956
- narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
1957
- }
1958
- let fresh = [];
1959
- if (checks.length) {
1960
- const r = await runChecks(project, checks, root, { targetFiles });
1961
- fresh = r.results;
1962
- for (const result of fresh) cache.set(result.checkId, { ...result });
1963
- }
1964
- const results = buildWatchResults(project, checks, fresh, cache);
1965
- const availableCaps = project.capabilities.filter(
1966
- (c) => c.available && c.id !== "browser"
1967
- );
1968
- const verdict = computeVerdict(results, availableCaps);
1969
- const run = {
1970
- id: "watch",
1971
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1972
- project,
1973
- results,
1974
- verdict,
1975
- env: {
1976
- os: process.platform,
1977
- node: process.version,
1978
- pm: project.packageManager,
1979
- ci: false,
1980
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1981
- },
1982
- scope: { kind: "watch", changedCount: files.length }
1983
- };
1984
- process.stdout.write(`${renderRun(run)}
1985
- `);
1986
- if (narrowedNote) process.stdout.write(`${narrowedNote}
1987
- `);
1988
- } finally {
1989
- running = false;
1990
- }
1991
- };
1992
- process.stdout.write("veris watch \u2014 press Ctrl-C to stop\n");
1993
- await tick(true);
1994
- return await new Promise((resolve2) => {
1995
- let stop;
1996
- try {
1997
- stop = watch(root, { poll: opts.poll }, () => {
1998
- void tick(false);
1999
- });
2000
- } catch (err) {
2001
- process.stderr.write(
2002
- `veris: ${err instanceof Error ? err.message : String(err)}
2003
- `
2004
- );
2005
- resolve2(1);
2006
- return;
2007
- }
2008
- process.on("SIGINT", () => {
2009
- stop();
2010
- process.stdout.write("\nStopped.\n");
2011
- resolve2(0);
2012
- });
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
2013
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
+ };
2014
887
  }
2015
- var init_watch = __esm({
2016
- "src/cli/commands/watch.ts"() {
2017
- "use strict";
2018
- init_gate();
2019
- init_detect();
2020
- init_orchestrator();
2021
- init_verdict();
2022
- init_changes();
2023
- init_terminal();
2024
- init_watcher();
2025
- }
2026
- });
2027
888
 
2028
- // src/cli/commands/scan.ts
2029
- var scan_exports = {};
2030
- __export(scan_exports, {
2031
- renderScan: () => renderScan,
2032
- runScan: () => runScan
2033
- });
2034
- import { writeFile as writeFile3 } from "fs/promises";
2035
- import { join as join11 } from "path";
2036
- import pc3 from "picocolors";
2037
- function renderScan(graph, analysis) {
2038
- const plain = isPlain();
2039
- const bold = (s) => plain ? s : pc3.bold(s);
2040
- const dim = (s) => plain ? s : pc3.dim(s);
2041
- 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;
2042
901
  const lines = [];
2043
- lines.push(bold("VerisKit \u2014 scan"));
2044
- lines.push("");
2045
- lines.push(
2046
- `Resolver ${graph.resolver}${graph.resolver === "scanner" ? dim(" (no TypeScript found \u2014 relative imports only)") : ""}`
2047
- );
2048
- lines.push(
2049
- `Modules ${Object.keys(graph.nodes).length} \xB7 Source ${graph.sourceFiles.length} \xB7 Tests ${graph.testFiles.length}`
2050
- );
2051
- lines.push("");
2052
- lines.push("Untested (top by impact)");
2053
- const top = analysis.untested.slice(0, 10);
2054
- if (top.length === 0)
2055
- lines.push(dim(" none \u2014 every source file is reached by a test"));
2056
- for (const f of top) {
902
+ lines.push("# VerisKit Verification Report");
903
+ if (run.scope?.kind) {
904
+ lines.push("");
2057
905
  lines.push(
2058
- ` ${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.`
2059
907
  );
2060
908
  }
2061
- return lines.join("\n");
2062
- }
2063
- async function runScan(root) {
2064
- const project = await detectProject(root);
2065
- const graph = await buildGraph(project);
2066
- const analysis = analyze(graph);
2067
- const dir = join11(root, ".veris");
2068
- await ensureDir(dir);
2069
- await writeFile3(
2070
- join11(dir, "graph.json"),
2071
- JSON.stringify({ resolver: graph.resolver, nodes: graph.nodes }, null, 2),
2072
- "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}`
2073
914
  );
2074
- process.stdout.write(`${renderScan(graph, analysis)}
2075
- `);
2076
- process.stdout.write(`
2077
- Graph ${join11(".veris", "graph.json")}
2078
- `);
2079
- return 0;
2080
- }
2081
- var init_scan = __esm({
2082
- "src/cli/commands/scan.ts"() {
2083
- "use strict";
2084
- init_detect();
2085
- init_analyze();
2086
- init_graph();
2087
- init_fs_safe();
2088
- 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");
2089
921
  }
2090
- });
2091
-
2092
- // src/cli/commands/plan.ts
2093
- var plan_exports = {};
2094
- __export(plan_exports, {
2095
- renderPlan: () => renderPlan,
2096
- runPlan: () => runPlan
2097
- });
2098
- import pc4 from "picocolors";
2099
- function renderPlan(project, graph, analysis, changed) {
2100
- const plain = isPlain();
2101
- const bold = (s) => plain ? s : pc4.bold(s);
2102
- const dim = (s) => plain ? s : pc4.dim(s);
2103
- const warn = (s) => plain ? s : pc4.yellow(s);
2104
- const lines = [];
2105
- lines.push(bold("VerisKit \u2014 plan"));
2106
- lines.push(dim(`(graph via ${graph.resolver})`));
2107
922
  lines.push("");
2108
- lines.push("Test these first (high impact, untested)");
2109
- const top = analysis.untested.slice(0, 5);
2110
- if (top.length === 0)
2111
- lines.push(
2112
- dim(
2113
- " none \u2014 untested files have no dependents or all files are covered"
2114
- )
2115
- );
2116
- top.forEach((f, i) => {
2117
- lines.push(
2118
- ` ${i + 1}. ${f} ${dim(`(${analysis.blastRadius[f] ?? 0} dependents, no test reaches it)`)}`
2119
- );
2120
- });
923
+ lines.push("## Checks");
2121
924
  lines.push("");
2122
- lines.push("Verification setup");
2123
- const weak = project.capabilities.filter(
2124
- (c) => !c.available && c.id !== "browser"
2125
- );
2126
- if (weak.length === 0)
2127
- lines.push(dim(" \u2713 types, lint, and unit are all configured"));
2128
- 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";
2129
930
  lines.push(
2130
- ` ${warn("\u26A0")} ${c.id} capability unavailable \u2014 ${c.reason ?? "not configured"}`
931
+ `| ${r.checkId} | ${r.status} | ${dur} | ${cell(r.summary)} | ${log} |`
2131
932
  );
2132
- if (changed.length) {
2133
- lines.push("");
2134
- lines.push(`Risky changes (${changed.length} changed file(s))`);
2135
- if (analysis.risky.length === 0) lines.push(dim(" none flagged"));
2136
- for (const f of analysis.risky.slice(0, 8)) {
2137
- const untested = analysis.untested.includes(f) ? ", untested" : "";
2138
- lines.push(
2139
- ` ${warn("!")} ${f} ${dim(`${analysis.blastRadius[f] ?? 0} dependents${untested}`)}`
2140
- );
2141
- }
2142
933
  }
2143
- lines.push("");
2144
- lines.push(
2145
- dim("Next: `veris affected` runs only the tests that reach your changes.")
2146
- );
2147
- return lines.join("\n");
2148
- }
2149
- async function runPlan(root, opts = {}) {
2150
- const project = await detectProject(root);
2151
- const graph = await buildGraph(project);
2152
- const { files } = await changedFiles(root, { base: opts.base });
2153
- const analysis = analyze(graph, files);
2154
- process.stdout.write(`${renderPlan(project, graph, analysis, files)}
2155
- `);
2156
- return 0;
2157
- }
2158
- var init_plan = __esm({
2159
- "src/cli/commands/plan.ts"() {
2160
- "use strict";
2161
- init_detect();
2162
- init_changes();
2163
- init_analyze();
2164
- init_graph();
2165
- 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}`);
2166
939
  }
2167
- });
2168
-
2169
- // src/evidence/signing.ts
2170
- import {
2171
- createHash as createHash2,
2172
- createPrivateKey,
2173
- createPublicKey,
2174
- sign as cryptoSign,
2175
- verify as cryptoVerify,
2176
- generateKeyPairSync
2177
- } from "crypto";
2178
- function derOf(publicKey) {
2179
- return publicKey.export({ type: "spki", format: "der" });
2180
- }
2181
- function keyId(publicKey) {
2182
- const key = typeof publicKey === "string" ? createPublicKey(publicKey) : publicKey;
2183
- return createHash2("sha256").update(derOf(key)).digest("hex").slice(0, 8);
2184
- }
2185
- function signatureKeyId(sig) {
2186
- const key = createPublicKey({
2187
- key: Buffer.from(sig.publicKey, "base64"),
2188
- format: "der",
2189
- type: "spki"
2190
- });
2191
- return keyId(key);
2192
- }
2193
- function generateKeyPair() {
2194
- const { publicKey, privateKey } = generateKeyPairSync("ed25519");
2195
- return {
2196
- publicKeyPem: publicKey.export({ type: "spki", format: "pem" }),
2197
- privateKeyPem: privateKey.export({
2198
- type: "pkcs8",
2199
- format: "pem"
2200
- }),
2201
- keyId: keyId(publicKey)
2202
- };
2203
- }
2204
- function signDigest(digest, privateKeyPem) {
2205
- const privateKey = createPrivateKey(privateKeyPem);
2206
- const publicKey = createPublicKey(
2207
- privateKey
2208
- );
2209
- const sig = cryptoSign(null, Buffer.from(digest, "utf8"), privateKey);
2210
- return {
2211
- schema: SIGNATURE_SCHEMA,
2212
- alg: "ed25519",
2213
- keyId: keyId(publicKey),
2214
- publicKey: derOf(publicKey).toString("base64"),
2215
- digest,
2216
- signature: sig.toString("base64"),
2217
- signedAt: (/* @__PURE__ */ new Date()).toISOString()
2218
- };
2219
- }
2220
- function verifySignature(sig) {
2221
- try {
2222
- const publicKey = createPublicKey({
2223
- key: Buffer.from(sig.publicKey, "base64"),
2224
- format: "der",
2225
- type: "spki"
2226
- });
2227
- return cryptoVerify(
2228
- null,
2229
- Buffer.from(sig.digest, "utf8"),
2230
- publicKey,
2231
- 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._"
2232
958
  );
2233
- } catch {
2234
- return false;
2235
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");
2236
966
  }
2237
- var SIGNATURE_SCHEMA;
2238
- var init_signing = __esm({
2239
- "src/evidence/signing.ts"() {
2240
- "use strict";
2241
- SIGNATURE_SCHEMA = "veriskit/signature@1";
2242
- }
2243
- });
2244
967
 
2245
- // src/evidence/verify-evidence.ts
2246
- import { existsSync as existsSync5 } from "fs";
2247
- import { readFile as readFile3 } from "fs/promises";
2248
- function verifyRecord(record) {
2249
- const recomputed = computeDigest(
2250
- record
2251
- );
2252
- const ok = recomputed === record.digest;
2253
- const checks = [
2254
- {
2255
- name: "record digest",
2256
- ok,
2257
- detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
2258
- }
2259
- ];
2260
- 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);
2261
980
  }
2262
- function signatureChecks(recordDigest, sig, opts = {}) {
2263
- const checks = [];
2264
- const kid = signatureKeyId(sig);
2265
- const validSig = verifySignature(sig) && sig.digest === recordDigest;
2266
- checks.push({
2267
- name: "signature",
2268
- ok: validSig,
2269
- 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
2270
986
  });
2271
- if (opts.expectedKeyId || opts.expectedPubKeyPem) {
2272
- let matches = false;
2273
- if (opts.expectedPubKeyPem) {
2274
- try {
2275
- matches = keyId(opts.expectedPubKeyPem) === kid;
2276
- } catch {
2277
- matches = false;
2278
- }
2279
- } else if (opts.expectedKeyId) {
2280
- matches = opts.expectedKeyId.toLowerCase() === kid.toLowerCase();
2281
- }
2282
- checks.push({
2283
- name: "signer",
2284
- ok: matches,
2285
- detail: matches ? `signer matches the expected key ${kid}` : `signer ${kid} does not match the expected key`
2286
- });
2287
- }
2288
- return checks;
2289
- }
2290
- async function verifyEvidenceFile(path, opts = {}) {
2291
- const parsed = JSON.parse(await readFile3(path, "utf8"));
2292
- if (parsed?.schema === "veriskit/bundle@1") {
2293
- const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
2294
- return verifyBundle2(parsed, opts);
2295
- }
2296
- const result = verifyRecord(parsed);
2297
- const assertedSigner = Boolean(
2298
- opts.expectedKeyId || opts.expectedPubKeyPem || opts.sigPath
2299
- );
2300
- const sigPath = opts.sigPath ?? `${path}.sig`;
2301
- if (existsSync5(sigPath)) {
2302
- const sig = JSON.parse(await readFile3(sigPath, "utf8"));
2303
- result.checks.push(
2304
- ...signatureChecks(result.record.digest, sig, {
2305
- expectedKeyId: opts.expectedKeyId,
2306
- expectedPubKeyPem: opts.expectedPubKeyPem
2307
- })
2308
- );
2309
- result.signed = true;
2310
- } else if (assertedSigner) {
2311
- result.checks.push({
2312
- name: "signature",
2313
- ok: false,
2314
- detail: "a signature was required (--pubkey/--key-id/--sig) but none was found"
2315
- });
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");
2316
1000
  }
2317
- result.ok = result.checks.every((c) => c.ok);
2318
1001
  return result;
2319
1002
  }
2320
- var init_verify_evidence = __esm({
2321
- "src/evidence/verify-evidence.ts"() {
2322
- "use strict";
2323
- init_record();
2324
- 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
+ });
2325
1022
  }
2326
- });
1023
+ };
2327
1024
 
2328
- // src/evidence/bundle.ts
2329
- var bundle_exports = {};
2330
- __export(bundle_exports, {
2331
- BUNDLE_SCHEMA: () => BUNDLE_SCHEMA,
2332
- buildBundle: () => buildBundle,
2333
- verifyBundle: () => verifyBundle
2334
- });
2335
- function buildBundle(record, report, logs, signature) {
2336
- const manifest = {
2337
- record: record.digest,
2338
- report: sha256(report),
2339
- logs: Object.fromEntries(
2340
- Object.entries(logs).map(([k, v]) => [k, sha256(v)])
2341
- )
2342
- };
2343
- const base = signature ? { schema: BUNDLE_SCHEMA, record, report, logs, manifest, signature } : { schema: BUNDLE_SCHEMA, record, report, logs, manifest };
2344
- return { ...base, bundleDigest: sha256(canonicalize(base)) };
2345
- }
2346
- function verifyBundle(bundle, opts = {}) {
2347
- const checks = [];
2348
- const recordResult = verifyRecord(bundle.record);
2349
- checks.push(...recordResult.checks);
2350
- const reportDigest = sha256(bundle.report);
2351
- checks.push({
2352
- name: "report",
2353
- ok: reportDigest === bundle.manifest.report,
2354
- detail: reportDigest === bundle.manifest.report ? "matches manifest" : "mismatch"
2355
- });
2356
- for (const [id, body] of Object.entries(bundle.logs)) {
2357
- const d = sha256(body);
2358
- checks.push({
2359
- name: `log:${id}`,
2360
- ok: d === bundle.manifest.logs[id],
2361
- 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
2362
1042
  });
2363
1043
  }
2364
- const { bundleDigest: _omit, ...rest } = bundle;
2365
- const recomputed = sha256(canonicalize(rest));
2366
- checks.push({
2367
- name: "bundle digest",
2368
- ok: recomputed === bundle.bundleDigest,
2369
- detail: recomputed === bundle.bundleDigest ? "matches" : "mismatch"
2370
- });
2371
- let signed = false;
2372
- if (bundle.signature) {
2373
- signed = true;
2374
- checks.push(
2375
- ...signatureChecks(bundle.record.digest, bundle.signature, {
2376
- expectedKeyId: opts.expectedKeyId,
2377
- expectedPubKeyPem: opts.expectedPubKeyPem
2378
- })
2379
- );
2380
- } else if (opts.expectedKeyId || opts.expectedPubKeyPem) {
2381
- checks.push({
2382
- name: "signature",
2383
- ok: false,
2384
- 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
2385
1063
  });
2386
1064
  }
2387
- return {
2388
- ok: checks.every((c) => c.ok),
2389
- kind: "bundle",
2390
- record: bundle.record,
2391
- checks,
2392
- signed
2393
- };
2394
- }
2395
- var BUNDLE_SCHEMA;
2396
- var init_bundle = __esm({
2397
- "src/evidence/bundle.ts"() {
2398
- "use strict";
2399
- init_record();
2400
- init_verify_evidence();
2401
- 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
+ });
2402
1085
  }
2403
- });
1086
+ };
2404
1087
 
2405
- // src/cli/commands/evidence.ts
2406
- var evidence_exports = {};
2407
- __export(evidence_exports, {
2408
- runEvidenceBundle: () => runEvidenceBundle,
2409
- runEvidenceKeygen: () => runEvidenceKeygen,
2410
- runEvidenceShow: () => runEvidenceShow,
2411
- runEvidenceSign: () => runEvidenceSign,
2412
- runEvidenceVerify: () => runEvidenceVerify
2413
- });
2414
- import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync } from "fs";
2415
- import { writeFile as writeFile4 } from "fs/promises";
2416
- import { basename as basename3, dirname as dirname2, join as join12 } from "path";
2417
- import pc5 from "picocolors";
2418
- async function runEvidenceVerify(path, opts = {}) {
2419
- let expectedPubKeyPem;
2420
- if (opts.pubkey) {
2421
- try {
2422
- expectedPubKeyPem = readFileSync6(opts.pubkey, "utf8");
2423
- } catch {
2424
- 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}
2425
1116
  `);
2426
- 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
+ };
2427
1141
  }
1142
+ if (status !== "passed" && output) {
1143
+ result.outputTail = output.split("\n").slice(-TAIL_LINES2).join("\n");
1144
+ }
1145
+ return result;
2428
1146
  }
2429
- let result;
2430
- try {
2431
- result = await verifyEvidenceFile(path, {
2432
- sigPath: opts.sig,
2433
- expectedKeyId: opts.keyId,
2434
- 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
2435
1188
  });
2436
- } catch (err) {
2437
- const msg = err instanceof Error ? err.message : String(err);
2438
- process.stderr.write(`veris: cannot read evidence at ${path}: ${msg}
2439
- `);
2440
- return 1;
2441
- }
2442
- const plain = isPlain();
2443
- const mark = (ok) => plain ? ok ? "ok" : "FAIL" : ok ? pc5.green("\u2713") : pc5.red("\u2717");
2444
- for (const check of result.checks) {
2445
- process.stdout.write(
2446
- ` ${mark(check.ok)} ${check.name}: ${check.detail}
2447
- `
2448
- );
2449
- }
2450
- const g = result.record.git;
2451
- const anchor = g ? `commit ${g.commit.slice(0, 7)} \xB7 ${g.dirty ? "tree dirty" : "tree clean"}` : "no git anchor";
2452
- process.stdout.write(
2453
- `
2454
- ${result.ok ? "digest OK" : "TAMPERED"} \xB7 verdict ${result.record.verdict.state} \xB7 ${anchor}
2455
- `
2456
- );
2457
- process.stdout.write(`
2458
- ${HONESTY}
2459
- `);
2460
- if (result.signed && !opts.pubkey && !opts.keyId) {
2461
- process.stdout.write(
2462
- "\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"
2463
- );
2464
1189
  }
2465
- 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";
2466
1207
  }
2467
- async function runEvidenceKeygen(root, opts = {}) {
2468
- const out = opts.out ?? join12(root, ".veris", "keys", "veriskit-signing.key");
2469
- const pub = `${out}.pub`;
2470
- if (existsSync6(out) || existsSync6(pub)) {
2471
- process.stderr.write(
2472
- `veris: a key already exists at ${out}. Refusing to overwrite it.
2473
- `
2474
- );
2475
- return 1;
2476
- }
2477
- const { mkdir: mkdir3 } = await import("fs/promises");
2478
- await mkdir3(dirname2(out), { recursive: true });
2479
- const kp = generateKeyPair();
2480
- writeFileSync(out, kp.privateKeyPem, { mode: 384 });
2481
- chmodSync(out, 384);
2482
- writeFileSync(pub, kp.publicKeyPem, "utf8");
2483
- process.stdout.write(
2484
- [
2485
- `Wrote signing key ${out} (keep this secret; do not commit it)`,
2486
- `Wrote public key ${pub}`,
2487
- `Key id ${kp.keyId}`,
2488
- ""
2489
- ].join("\n")
2490
- );
2491
- 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
+ };
2492
1216
  }
2493
- async function runEvidenceSign(evidencePath, opts = {}) {
2494
- let privateKeyPem;
2495
- if (process.env.VERISKIT_SIGNING_KEY) {
2496
- privateKeyPem = process.env.VERISKIT_SIGNING_KEY;
2497
- } else if (opts.key) {
2498
- try {
2499
- privateKeyPem = readFileSync6(opts.key, "utf8");
2500
- } catch {
2501
- process.stderr.write(`veris: cannot read signing key at ${opts.key}
2502
- `);
2503
- 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;
2504
1230
  }
2505
- } else {
2506
- process.stderr.write(
2507
- "veris: no signing key. Pass --key <path> or set VERISKIT_SIGNING_KEY.\n"
2508
- );
2509
- return 1;
2510
- }
2511
- let record;
2512
- try {
2513
- record = JSON.parse(readFileSync6(evidencePath, "utf8"));
2514
- } catch {
2515
- process.stderr.write(`veris: cannot read evidence at ${evidencePath}
2516
- `);
2517
- 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"}`);
2518
1241
  }
2519
- const recomputed = computeDigest(
2520
- record
2521
- );
2522
- if (recomputed !== record.digest) {
2523
- process.stderr.write(
2524
- "veris: refusing to sign, the record digest does not match its contents.\n"
2525
- );
2526
- 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(", ")}`);
2527
1255
  }
2528
- let sig;
2529
- try {
2530
- sig = signDigest(record.digest, privateKeyPem);
2531
- } catch (err) {
2532
- const msg = err instanceof Error ? err.message : String(err);
2533
- process.stderr.write(`veris: signing failed: ${msg}
2534
- `);
2535
- 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"];
2536
1296
  }
2537
- const out = opts.out ?? `${evidencePath}.sig`;
2538
- writeFileSync(out, `${JSON.stringify(sig, null, 2)}
2539
- `, "utf8");
2540
- process.stdout.write(
2541
- `Signed ${evidencePath}
2542
- Wrote ${out} (key ${sig.keyId})
2543
- `
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)
2544
1311
  );
2545
- return 0;
1312
+ run.reportRef = reportRef;
1313
+ const runDir = await createRunDir(root, run.id);
1314
+ await writeEvidence(runDir, record);
1315
+ return { run, record };
2546
1316
  }
2547
- async function runEvidenceBundle(root, opts = {}) {
2548
- const runDir = latestRunDir(root);
2549
- if (!runDir) {
2550
- process.stdout.write("No evidence yet. Run `veris verify` first.\n");
2551
- return 1;
2552
- }
2553
- let record;
2554
- try {
2555
- record = JSON.parse(
2556
- readFileSync6(join12(runDir, "evidence.json"), "utf8")
2557
- );
2558
- } catch {
2559
- process.stderr.write(`veris: no evidence.json in ${runDir}
2560
- `);
2561
- 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
+ };
2562
1327
  }
2563
- let report = "";
2564
- try {
2565
- report = readFileSync6(
2566
- join12(root, ".veris", "reports", `verify-${record.id}.md`),
2567
- "utf8"
2568
- );
2569
- } 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`;
2570
1345
  }
2571
- const logIds = record.checks.filter((c) => c.logDigest).map((c) => c.id);
2572
- const logs = await readRunLogs(runDir, logIds);
2573
- let signature;
2574
- const sigPath = join12(runDir, "evidence.json.sig");
2575
- if (existsSync6(sigPath)) {
2576
- try {
2577
- signature = JSON.parse(readFileSync6(sigPath, "utf8"));
2578
- } 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
+ });
2579
1357
  }
2580
1358
  }
2581
- const bundle = buildBundle(record, report, logs, signature);
2582
- const outDir = await ensureEvidenceDir(root);
2583
- const out = opts.out ?? join12(outDir, `${record.id}.bundle.json`);
2584
- await writeFile4(out, `${JSON.stringify(bundle, null, 2)}
2585
- `, "utf8");
2586
- process.stdout.write(`Wrote portable evidence bundle: ${out}
2587
- `);
2588
- return 0;
2589
- }
2590
- async function runEvidenceShow(root, path) {
2591
- const target = path ?? (() => {
2592
- const dir = latestRunDir(root);
2593
- return dir ? join12(dir, "evidence.json") : null;
2594
- })();
2595
- if (!target) {
2596
- process.stdout.write("No evidence yet. Run `veris verify` first.\n");
2597
- return 0;
2598
- }
2599
- let record;
2600
- try {
2601
- record = JSON.parse(readFileSync6(target, "utf8"));
2602
- } catch {
2603
- process.stderr.write(`veris: cannot read evidence at ${target}
2604
- `);
2605
- return 1;
2606
- }
2607
- const g = record.git;
2608
- process.stdout.write(
2609
- [
2610
- `Evidence ${basename3(target)}`,
2611
- `Verdict ${record.verdict.state}`,
2612
- `Project ${record.project.name}`,
2613
- `Scope ${record.scope.kind} (${record.scope.changedCount} changed)`,
2614
- `Commit ${g ? `${g.commit.slice(0, 7)} (${g.branch}) ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}` : "no git anchor"}`,
2615
- `Checks ${record.checks.map((c) => `${c.id}:${c.status}`).join(", ")}`,
2616
- `Digest ${record.digest}`,
2617
- ""
2618
- ].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)
2619
1366
  );
2620
- return 0;
2621
- }
2622
- var HONESTY;
2623
- var init_evidence = __esm({
2624
- "src/cli/commands/evidence.ts"() {
2625
- "use strict";
2626
- init_bundle();
2627
- init_record();
2628
- init_signing();
2629
- init_store();
2630
- init_verify_evidence();
2631
- init_tty();
2632
- 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.";
2633
- }
2634
- });
2635
-
2636
- // src/publish/badge.ts
2637
- function buildBadge(verdict) {
2638
- const s = STYLE[verdict.state];
1367
+ run.reportRef = reportRef;
1368
+ const runDir = await createRunDir(root, run.id);
1369
+ await writeEvidence(runDir, record);
2639
1370
  return {
2640
- schemaVersion: 1,
2641
- label: "veriskit",
2642
- message: s.message,
2643
- color: s.color
1371
+ run,
1372
+ record,
1373
+ note,
1374
+ changedCount: files.length,
1375
+ nothingAffected: false
2644
1376
  };
2645
1377
  }
2646
- var STYLE;
2647
- var init_badge = __esm({
2648
- "src/publish/badge.ts"() {
2649
- "use strict";
2650
- STYLE = {
2651
- verified: { message: "verified", color: "14b8a6" },
2652
- failed: { message: "failed", color: "e5484d" },
2653
- partial: { message: "partial", color: "f5a623" }
2654
- };
2655
- }
2656
- });
2657
1378
 
2658
- // src/cli/commands/badge.ts
2659
- var badge_exports = {};
2660
- __export(badge_exports, {
2661
- runBadge: () => runBadge
2662
- });
2663
- import { readFileSync as readFileSync7 } from "fs";
2664
- import { mkdir as mkdir2, writeFile as writeFile5 } from "fs/promises";
2665
- import { dirname as dirname3, join as join13 } from "path";
2666
- async function runBadge(root, opts = {}) {
2667
- const dir = latestRunDir(root);
2668
- if (!dir) {
2669
- process.stdout.write("No runs yet. Run `veris verify` first.\n");
2670
- return 1;
2671
- }
2672
- let record;
2673
- try {
2674
- record = JSON.parse(
2675
- readFileSync7(join13(dir, "evidence.json"), "utf8")
2676
- );
2677
- } catch {
2678
- process.stderr.write(`veris: no evidence.json in ${dir}
2679
- `);
2680
- return 1;
2681
- }
2682
- const out = opts.out ?? join13(root, ".veris", "badge.json");
2683
- await mkdir2(dirname3(out), { recursive: true });
2684
- await writeFile5(
2685
- out,
2686
- `${JSON.stringify(buildBadge(record.verdict), null, 2)}
2687
- `,
2688
- "utf8"
2689
- );
2690
- process.stdout.write(`Wrote badge ${out}
2691
- `);
2692
- return 0;
2693
- }
2694
- var init_badge2 = __esm({
2695
- "src/cli/commands/badge.ts"() {
2696
- "use strict";
2697
- init_store();
2698
- init_badge();
2699
- }
2700
- });
1379
+ // src/index.ts
1380
+ init_verify_evidence();
2701
1381
 
2702
1382
  // src/history/flaky.ts
2703
1383
  function detectFlaky(records) {
@@ -2717,20 +1397,15 @@ function detectFlaky(records) {
2717
1397
  }
2718
1398
  return flaky;
2719
1399
  }
2720
- var init_flaky = __esm({
2721
- "src/history/flaky.ts"() {
2722
- "use strict";
2723
- }
2724
- });
2725
1400
 
2726
1401
  // src/history/read.ts
2727
- import { readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
2728
- import { join as join14 } from "path";
1402
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "fs";
1403
+ import { join as join8 } from "path";
2729
1404
  function loadRuns(root, limit = 20) {
2730
- const runs = join14(root, ".veris", "runs");
1405
+ const runs = join8(root, ".veris", "runs");
2731
1406
  let names;
2732
1407
  try {
2733
- 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);
2734
1409
  } catch {
2735
1410
  return [];
2736
1411
  }
@@ -2738,7 +1413,7 @@ function loadRuns(root, limit = 20) {
2738
1413
  for (const name of names) {
2739
1414
  try {
2740
1415
  const rec = JSON.parse(
2741
- readFileSync8(join14(runs, name, "evidence.json"), "utf8")
1416
+ readFileSync4(join8(runs, name, "evidence.json"), "utf8")
2742
1417
  );
2743
1418
  if (rec?.schema === "veriskit/evidence@1") records.push(rec);
2744
1419
  } catch {
@@ -2749,213 +1424,18 @@ function loadRuns(root, limit = 20) {
2749
1424
  );
2750
1425
  return records.slice(0, limit);
2751
1426
  }
2752
- var init_read = __esm({
2753
- "src/history/read.ts"() {
2754
- "use strict";
2755
- }
2756
- });
2757
-
2758
- // src/cli/commands/log.ts
2759
- var log_exports = {};
2760
- __export(log_exports, {
2761
- runLog: () => runLog
2762
- });
2763
- async function runLog(root, opts = {}) {
2764
- const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : 20;
2765
- const records = loadRuns(root, limit);
2766
- if (records.length === 0) {
2767
- process.stdout.write("No runs yet. Run `veris verify` first.\n");
2768
- return 0;
2769
- }
2770
- if (opts.flaky) {
2771
- const flaky = detectFlaky(records);
2772
- if (flaky.length === 0) {
2773
- process.stdout.write(
2774
- `No flaky checks in the last ${records.length} run(s).
2775
- `
2776
- );
2777
- return 0;
2778
- }
2779
- process.stdout.write(
2780
- `Flaky checks (both passed and failed in the last ${records.length} run(s), newest first):
2781
- `
2782
- );
2783
- for (const f of flaky) {
2784
- process.stdout.write(` ${f.id.padEnd(8)} ${f.statuses.join(" ")}
2785
- `);
2786
- }
2787
- return 0;
2788
- }
2789
- for (const rec of records) {
2790
- const date = rec.startedAt.slice(0, 19).replace("T", " ");
2791
- const checks = (rec.checks ?? []).map((c) => `${c.id}:${c.status}`).join(" ");
2792
- const commit = rec.git ? rec.git.commit.slice(0, 7) : "-";
2793
- process.stdout.write(
2794
- `${date} ${rec.verdict.state.padEnd(9)} ${checks} ${commit}
2795
- `
2796
- );
2797
- }
2798
- process.stdout.write(
2799
- "\nHistory is local to this machine (.veris/runs is gitignored).\n"
2800
- );
2801
- return 0;
2802
- }
2803
- var init_log = __esm({
2804
- "src/cli/commands/log.ts"() {
2805
- "use strict";
2806
- init_flaky();
2807
- init_read();
2808
- }
2809
- });
2810
1427
 
2811
- // src/cli/index.ts
2812
- init_version();
2813
- import { realpathSync } from "fs";
2814
- import { argv } from "process";
2815
- import { pathToFileURL } from "url";
2816
- import cac from "cac";
2817
- function buildCli() {
2818
- const cli = cac("veris");
2819
- cli.version(VERSION);
2820
- cli.help();
2821
- cli.command("doctor", "Report environment + capabilities (read-only)").action(async () => {
2822
- const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
2823
- process.exitCode = await runDoctor2(process.cwd());
2824
- });
2825
- cli.command("test", "Run the detected unit test runner").action(async () => {
2826
- const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
2827
- process.exitCode = await runTest2(process.cwd());
2828
- });
2829
- cli.command("init", "Detect the stack and set up .veris (idempotent)").action(async () => {
2830
- const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
2831
- process.exitCode = await runInit2(process.cwd());
2832
- });
2833
- 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(
2834
- "--github",
2835
- "Publish the verdict to the GitHub PR (comment + check run)"
2836
- ).option("--browser", "Also run browser tests (Playwright), when available").action(
2837
- async (opts) => {
2838
- const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
2839
- process.exitCode = await runVerify2(process.cwd(), {
2840
- partialOk: opts.partialOk,
2841
- github: opts.github,
2842
- browser: opts.browser
2843
- });
2844
- }
2845
- );
2846
- cli.command("report", "Print the latest verification report").action(async () => {
2847
- const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
2848
- process.exitCode = await runReport2(process.cwd());
2849
- });
2850
- 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(
2851
- "--github",
2852
- "Publish the verdict to the GitHub PR (comment + check run)"
2853
- ).action(
2854
- async (opts) => {
2855
- const { runAffected: runAffected2 } = await Promise.resolve().then(() => (init_affected(), affected_exports));
2856
- process.exitCode = await runAffected2(process.cwd(), {
2857
- base: opts.base,
2858
- partialOk: opts.partialOk,
2859
- github: opts.github
2860
- });
2861
- }
2862
- );
2863
- 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) => {
2864
- const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
2865
- process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
2866
- });
2867
- cli.command("scan", "Map the import graph and untested areas (read-only)").action(async () => {
2868
- const { runScan: runScan2 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
2869
- process.exitCode = await runScan2(process.cwd());
2870
- });
2871
- cli.command(
2872
- "plan",
2873
- "Recommend what to test, from the import graph (read-only)"
2874
- ).option("--base <ref>", "Also factor in changes vs a git ref").action(async (opts) => {
2875
- const { runPlan: runPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
2876
- process.exitCode = await runPlan2(process.cwd(), { base: opts.base });
2877
- });
2878
- cli.command(
2879
- "evidence <action> [file]",
2880
- "Evidence: verify <file> | bundle | show [file] | keygen | sign <file>"
2881
- ).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(
2882
- async (action, file, opts) => {
2883
- const mod = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
2884
- if (action === "verify") {
2885
- if (!file) {
2886
- process.stderr.write("veris: evidence verify needs a <file>\n");
2887
- process.exitCode = 1;
2888
- return;
2889
- }
2890
- process.exitCode = await mod.runEvidenceVerify(file, {
2891
- pubkey: opts.pubkey,
2892
- keyId: opts.keyId,
2893
- sig: opts.sig
2894
- });
2895
- } else if (action === "bundle") {
2896
- process.exitCode = await mod.runEvidenceBundle(process.cwd(), {
2897
- out: opts.out
2898
- });
2899
- } else if (action === "show") {
2900
- process.exitCode = await mod.runEvidenceShow(process.cwd(), file);
2901
- } else if (action === "keygen") {
2902
- process.exitCode = await mod.runEvidenceKeygen(process.cwd(), {
2903
- out: opts.out
2904
- });
2905
- } else if (action === "sign") {
2906
- if (!file) {
2907
- process.stderr.write("veris: evidence sign needs a <file>\n");
2908
- process.exitCode = 1;
2909
- return;
2910
- }
2911
- process.exitCode = await mod.runEvidenceSign(file, {
2912
- key: opts.key,
2913
- out: opts.out
2914
- });
2915
- } else {
2916
- process.stderr.write(
2917
- `veris: unknown evidence action '${action}' (use verify | bundle | show | keygen | sign)
2918
- `
2919
- );
2920
- process.exitCode = 1;
2921
- }
2922
- }
2923
- );
2924
- cli.command(
2925
- "badge",
2926
- "Write a shields.io endpoint JSON from the latest verdict"
2927
- ).option("--out <file>", "Output path (default .veris/badge.json)").action(async (opts) => {
2928
- const { runBadge: runBadge2 } = await Promise.resolve().then(() => (init_badge2(), badge_exports));
2929
- process.exitCode = await runBadge2(process.cwd(), { out: opts.out });
2930
- });
2931
- 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) => {
2932
- const { runLog: runLog2 } = await Promise.resolve().then(() => (init_log(), log_exports));
2933
- process.exitCode = await runLog2(process.cwd(), {
2934
- limit: opts.limit ? Number(opts.limit) : void 0,
2935
- flaky: opts.flaky
2936
- });
2937
- });
2938
- return { raw: cli, version: VERSION };
2939
- }
2940
- async function main(argv2) {
2941
- try {
2942
- const { raw } = buildCli();
2943
- if (argv2.length <= 2) {
2944
- raw.outputHelp();
2945
- return;
2946
- }
2947
- raw.parse(argv2);
2948
- } catch (err) {
2949
- const msg = err instanceof Error ? err.message : String(err);
2950
- process.stderr.write(`veris: ${msg}
2951
- `);
2952
- process.exitCode = 1;
2953
- }
2954
- }
2955
- var invokedPath = argv[1] ? realpathSync(argv[1]) : "";
2956
- var isEntry = import.meta.url === pathToFileURL(invokedPath).href;
2957
- if (isEntry) void main(argv);
1428
+ // src/index.ts
1429
+ init_analyze();
1430
+ init_graph();
2958
1431
  export {
2959
- buildCli,
2960
- main
1432
+ affectedProject,
1433
+ analyze,
1434
+ buildGraph,
1435
+ detectFlaky,
1436
+ detectProject,
1437
+ getEnvironmentInfo,
1438
+ loadRuns,
1439
+ verifyEvidenceFile,
1440
+ verifyProject
2961
1441
  };