@safetnsr/vet 1.6.0 → 1.6.1

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.
@@ -83,11 +83,14 @@ export function extractPackageName(specifier) {
83
83
  // Skip node: builtins
84
84
  if (specifier.startsWith('node:'))
85
85
  return null;
86
+ // Path aliases: @/ is always a path alias (no npm package starts with @/)
87
+ if (specifier.startsWith('@/'))
88
+ return null;
86
89
  // Scoped packages: @scope/name or @scope/name/sub
87
90
  if (specifier.startsWith('@')) {
88
91
  const parts = specifier.split('/');
89
92
  if (parts.length < 2)
90
- return null;
93
+ return null; // bare @scope with no / is not a valid package
91
94
  return `${parts[0]}/${parts[1]}`;
92
95
  }
93
96
  // Regular package: name or name/sub
@@ -184,15 +187,25 @@ export async function checkDeps(cwd) {
184
187
  }
185
188
  // ── 2. Typosquat detection ─────────────────────────────────────────────────
186
189
  const topSet = new Set(TOP_PACKAGES);
190
+ // Known-legitimate short packages that happen to be close to popular ones
191
+ const TYPOSQUAT_WHITELIST = new Set([
192
+ 'ai', 'clsx', 'ws', 'os', 'ms', 'pg', 'ip', 'bn', 'qs', 'co', 'is',
193
+ ]);
187
194
  for (const pkg of declaredNames) {
188
195
  if (topSet.has(pkg))
189
196
  continue; // it IS the popular package
197
+ if (pkg.length <= 3)
198
+ continue; // too short, too many false matches
199
+ if (TYPOSQUAT_WHITELIST.has(pkg))
200
+ continue;
190
201
  for (const top of TOP_PACKAGES) {
191
202
  const dist = levenshtein(pkg, top);
192
203
  if (dist >= 1 && dist <= 2) {
204
+ // If the package exists on the registry, it's likely legitimate — downgrade to info
205
+ const existsOnRegistry = registryResults.get(pkg) === true;
193
206
  issues.push({
194
- severity: 'error',
195
- message: `possible typosquat: "${pkg}" is ${dist} edit${dist > 1 ? 's' : ''} from "${top}"`,
207
+ severity: existsOnRegistry ? 'info' : 'error',
208
+ message: `possible typosquat: "${pkg}" is ${dist} edit${dist > 1 ? 's' : ''} from "${top}"${existsOnRegistry ? ' (exists on npm)' : ''}`,
196
209
  file: 'package.json',
197
210
  fixable: true,
198
211
  fixHint: `did you mean "${top}"?`,
@@ -83,10 +83,26 @@ function getRecentMessages(cwd, since) {
83
83
  }
84
84
  return raw.split('\n').map(l => l.replace(/^[a-f0-9]+\s+/, '').trim()).filter(l => l.length > 0);
85
85
  }
86
+ // ── Python project detection ─────────────────────────────────────────────────
87
+ function isPythonProject(cwd) {
88
+ const markers = ['pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt'];
89
+ return markers.some(m => existsSync(join(cwd, m)));
90
+ }
91
+ function isPythonBoilerplate(filePath) {
92
+ const base = basename(filePath);
93
+ if (base === '__init__.py')
94
+ return true;
95
+ if (filePath.endsWith('.pyi'))
96
+ return true;
97
+ if (filePath.replace(/\\/g, '/').includes('__pycache__/'))
98
+ return true;
99
+ return false;
100
+ }
86
101
  // ── Main check ───────────────────────────────────────────────────────────────
87
102
  export function checkVerify(cwd, since) {
88
103
  const issues = [];
89
104
  let deductions = 0;
105
+ const python = isPythonProject(cwd);
90
106
  // Check if git repo
91
107
  const isGit = safeExec('git rev-parse --is-inside-work-tree', cwd).trim();
92
108
  if (isGit !== 'true') {
@@ -165,6 +181,11 @@ export function checkVerify(cwd, since) {
165
181
  }
166
182
  const lineCount = countLines(content);
167
183
  // 2. File must have meaningful content (>10 non-empty lines)
184
+ // Skip thin file check for Python boilerplate files
185
+ if (python && isPythonBoilerplate(relPath)) {
186
+ verified++;
187
+ continue;
188
+ }
168
189
  if (lineCount < 10 && lineCount > 0) {
169
190
  issues.push({
170
191
  severity: 'warning',
@@ -207,13 +228,15 @@ export function checkVerify(cwd, since) {
207
228
  verified++;
208
229
  }
209
230
  const finalScore = Math.max(0, 100 - deductions);
231
+ const baseSummary = failed === 0
232
+ ? `${verified} agent claim${verified !== 1 ? 's' : ''} verified clean`
233
+ : `${failed} claim${failed !== 1 ? 's' : ''} failed verification (${verified} passed)`;
234
+ const summary = python ? `${baseSummary} (python project detected — some checks have reduced scope)` : baseSummary;
210
235
  return {
211
236
  name: 'verify',
212
237
  score: finalScore,
213
238
  maxScore: 100,
214
239
  issues,
215
- summary: failed === 0
216
- ? `${verified} agent claim${verified !== 1 ? 's' : ''} verified clean`
217
- : `${failed} claim${failed !== 1 ? 's' : ''} failed verification (${verified} passed)`,
240
+ summary,
218
241
  };
219
242
  }
@@ -0,0 +1,6 @@
1
+ export type ProjectLanguage = 'javascript' | 'typescript' | 'python' | 'unknown';
2
+ /**
3
+ * Detect the primary language of a project by checking for marker files.
4
+ * Priority: typescript > javascript > python > unknown
5
+ */
6
+ export declare function detectProjectLanguage(cwd: string): ProjectLanguage;
@@ -0,0 +1,24 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ /**
4
+ * Detect the primary language of a project by checking for marker files.
5
+ * Priority: typescript > javascript > python > unknown
6
+ */
7
+ export function detectProjectLanguage(cwd) {
8
+ // TypeScript markers
9
+ if (existsSync(join(cwd, 'tsconfig.json')))
10
+ return 'typescript';
11
+ // JavaScript/TypeScript (package.json present)
12
+ if (existsSync(join(cwd, 'package.json'))) {
13
+ // Check if any tsconfig variant exists
14
+ const tsConfigs = ['tsconfig.build.json', 'tsconfig.app.json', 'tsconfig.node.json'];
15
+ if (tsConfigs.some(f => existsSync(join(cwd, f))))
16
+ return 'typescript';
17
+ return 'javascript';
18
+ }
19
+ // Python markers
20
+ const pythonMarkers = ['pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt'];
21
+ if (pythonMarkers.some(f => existsSync(join(cwd, f))))
22
+ return 'python';
23
+ return 'unknown';
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safetnsr/vet",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "vet your AI-generated code — one command, one score card, one letter grade",
5
5
  "type": "module",
6
6
  "bin": {