@safetnsr/vet 1.22.2 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from 'node:path';
3
3
  import { readFileSync } from 'node:fs';
4
- import { isGitRepo, readFile, c } from './util.js';
4
+ import { isGitRepo, readFile, c, gitExec } from './util.js';
5
5
  import { checkReady } from './checks/ready.js';
6
6
  import { checkDiff } from './checks/diff.js';
7
7
  import { checkModels } from './checks/models.js';
@@ -35,6 +35,7 @@ import { checkContext, runContextCommand } from './checks/context.js';
35
35
  import { checkSplit, runSplitCommand } from './checks/split.js';
36
36
  import { checkCompleteness } from './checks/completeness.js';
37
37
  import { score } from './scorer.js';
38
+ import { toGrade } from './categories.js';
38
39
  import { reportPretty, reportJSON, reportBadge } from './reporter.js';
39
40
  import { clearCache } from './file-cache.js';
40
41
  const args = process.argv.slice(2);
@@ -105,6 +106,7 @@ if (flags.has('--help') || flags.has('-h')) {
105
106
  --hook pre-commit hook mode (exit 1 if below grade C)
106
107
  --badge print markdown badge string and exit
107
108
  --fix auto-fix configs, models
109
+ --diff-only only score files changed in current branch (great for CI)
108
110
  --since REF diff against specific commit/range
109
111
  --watch re-run on file changes
110
112
  --json JSON output
@@ -133,6 +135,7 @@ const isHook = flags.has('--hook');
133
135
  const isFix = flags.has('--fix');
134
136
  const isWatch = flags.has('--watch');
135
137
  const isBadge = flags.has('--badge');
138
+ const isDiffOnly = flags.has('--diff-only');
136
139
  const isJSON = flags.has('--json') || (!process.stdout.isTTY && !flags.has('--pretty') && !isBadge);
137
140
  const since = flagMap.get('since');
138
141
  const maxFiles = flagMap.has('max-files') ? (parseInt(flagMap.get('max-files'), 10) || 0) : 0;
@@ -339,6 +342,56 @@ async function withTimeout(name, fn, timeoutMs = 30_000) {
339
342
  Promise.resolve(fn()).then((r) => { clearTimeout(timer); res(r); }).catch(() => { clearTimeout(timer); res({ name, score: 100, maxScore: 100, issues: [], summary: 'check failed' }); });
340
343
  });
341
344
  }
345
+ /** Get files changed vs main/master branch or --since ref */
346
+ function getChangedFiles(cwd, sinceRef) {
347
+ const base = sinceRef || (() => {
348
+ const main = gitExec(['merge-base', 'HEAD', 'origin/main'], cwd);
349
+ if (main)
350
+ return main;
351
+ const master = gitExec(['merge-base', 'HEAD', 'origin/master'], cwd);
352
+ if (master)
353
+ return master;
354
+ return 'HEAD~1';
355
+ })();
356
+ const output = gitExec(['diff', '--name-only', base, 'HEAD'], cwd);
357
+ if (!output)
358
+ return new Set();
359
+ return new Set(output.split('\n').filter(Boolean));
360
+ }
361
+ /** Filter VetResult to only include issues from changed files, then re-score */
362
+ function filterDiffOnly(result, changedFiles) {
363
+ if (changedFiles.size === 0)
364
+ return result;
365
+ const filtered = {
366
+ ...result,
367
+ categories: result.categories.map(cat => {
368
+ const filteredChecks = cat.checks.map(check => {
369
+ const filteredIssues = check.issues.filter(issue => !issue.file || changedFiles.has(issue.file));
370
+ const penalty = filteredIssues.reduce((sum, i) => {
371
+ if (i.severity === 'error')
372
+ return sum + 25;
373
+ if (i.severity === 'warning')
374
+ return sum + 10;
375
+ return sum + 2;
376
+ }, 0);
377
+ const newScore = Math.max(0, 100 - penalty);
378
+ return { ...check, score: newScore, issues: filteredIssues };
379
+ });
380
+ const allIssues = filteredChecks.flatMap(c => c.issues);
381
+ const checksWithIssues = filteredChecks.filter(c => c.issues.length > 0);
382
+ const avgScore = checksWithIssues.length > 0
383
+ ? Math.round(checksWithIssues.reduce((sum, c) => sum + c.score, 0) / checksWithIssues.length)
384
+ : 100;
385
+ return { ...cat, checks: filteredChecks, issues: allIssues, score: avgScore };
386
+ }),
387
+ };
388
+ const totalWeight = filtered.categories.reduce((sum, c) => sum + c.weight, 0);
389
+ filtered.score = Math.round(filtered.categories.reduce((sum, c) => sum + c.score * c.weight, 0) / totalWeight);
390
+ filtered.grade = toGrade(filtered.score);
391
+ filtered.totalIssues = filtered.categories.reduce((sum, c) => c.issues.length + sum, 0);
392
+ filtered.fixableIssues = filtered.categories.reduce((sum, c) => c.issues.filter(i => i.fixable).length + sum, 0);
393
+ return filtered;
394
+ }
342
395
  async function runChecks() {
343
396
  const globalStart = Date.now();
344
397
  const GLOBAL_TIMEOUT = 120_000;
@@ -454,7 +507,11 @@ if (isWatch) {
454
507
  else {
455
508
  // Normal run
456
509
  try {
457
- const result = await runChecks();
510
+ let result = await runChecks();
511
+ if (isDiffOnly) {
512
+ const changedFiles = getChangedFiles(cwd, since);
513
+ result = filterDiffOnly(result, changedFiles);
514
+ }
458
515
  if (isJSON) {
459
516
  console.log(reportJSON(result));
460
517
  }
@@ -0,0 +1 @@
1
+ export declare function bad(): void;
@@ -0,0 +1,6 @@
1
+ const SECRET = "sk-abc123456789";
2
+ // TODO: remove this hack
3
+ export function bad() { try {
4
+ throw new Error();
5
+ }
6
+ catch (e) { } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safetnsr/vet",
3
- "version": "1.22.2",
3
+ "version": "1.23.0",
4
4
  "description": "vet your AI-generated code — one command, one score card, one letter grade",
5
5
  "type": "module",
6
6
  "bin": {