@safetnsr/vet 1.22.0 → 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/README.md +39 -1
- package/dist/checks/scan.js +1 -0
- package/dist/cli.js +59 -2
- package/dist/utils-bad.d.ts +1 -0
- package/dist/utils-bad.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,6 +127,8 @@ catch problems as the agent creates them, not after it's done.
|
|
|
127
127
|
|
|
128
128
|
## CI/CD
|
|
129
129
|
|
|
130
|
+
### Quick (one-liner)
|
|
131
|
+
|
|
130
132
|
```yaml
|
|
131
133
|
# .github/workflows/vet.yml
|
|
132
134
|
name: vet
|
|
@@ -141,7 +143,43 @@ jobs:
|
|
|
141
143
|
- run: npx @safetnsr/vet --ci
|
|
142
144
|
```
|
|
143
145
|
|
|
144
|
-
GitHub Action
|
|
146
|
+
### GitHub Action (with PR comments)
|
|
147
|
+
|
|
148
|
+
Posts a score card directly on your PR with pass/fail status:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
name: vet
|
|
152
|
+
on:
|
|
153
|
+
pull_request:
|
|
154
|
+
branches: [main]
|
|
155
|
+
|
|
156
|
+
permissions:
|
|
157
|
+
contents: read
|
|
158
|
+
pull-requests: write
|
|
159
|
+
|
|
160
|
+
jobs:
|
|
161
|
+
vet:
|
|
162
|
+
runs-on: ubuntu-latest
|
|
163
|
+
steps:
|
|
164
|
+
- uses: actions/checkout@v4
|
|
165
|
+
with:
|
|
166
|
+
fetch-depth: 0
|
|
167
|
+
- uses: safetnsr/vet/.github/actions/vet@main
|
|
168
|
+
with:
|
|
169
|
+
threshold: C # minimum grade to pass (A/B/C/D/F)
|
|
170
|
+
comment: true # post score card as PR comment
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Inputs:**
|
|
174
|
+
|
|
175
|
+
| Input | Default | Description |
|
|
176
|
+
|-------|---------|-------------|
|
|
177
|
+
| `threshold` | `C` | Minimum grade to pass |
|
|
178
|
+
| `working-directory` | `.` | Directory to run vet in |
|
|
179
|
+
| `version` | `latest` | @safetnsr/vet version |
|
|
180
|
+
| `comment` | `true` | Post results as PR comment |
|
|
181
|
+
|
|
182
|
+
**Outputs:** `score`, `grade`, `passed`
|
|
145
183
|
|
|
146
184
|
## config
|
|
147
185
|
|
package/dist/checks/scan.js
CHANGED
|
@@ -156,6 +156,7 @@ function isInCodeContext(lines, lineIndex) {
|
|
|
156
156
|
function isWorkflowFile(relPath) {
|
|
157
157
|
const normalized = relPath.replace(/\\/g, '/');
|
|
158
158
|
return normalized.includes('.github/workflows/') ||
|
|
159
|
+
normalized.includes('.github/actions/') ||
|
|
159
160
|
normalized.includes('.circleci/') ||
|
|
160
161
|
normalized.includes('.gitlab-ci') ||
|
|
161
162
|
/Makefile|Dockerfile|Jenkinsfile/i.test(normalized);
|
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
|
-
|
|
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;
|