@safetnsr/vet 1.20.1 → 1.21.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/checks/context.d.ts +3 -0
- package/dist/checks/context.js +359 -0
- package/dist/cli.js +17 -3
- package/package.json +3 -2
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { c } from '../util.js';
|
|
5
|
+
import { cachedReadFile } from '../file-cache.js';
|
|
6
|
+
// ── Tiktoken lazy init ───────────────────────────────────────────────────────
|
|
7
|
+
import { encodingForModel } from 'js-tiktoken';
|
|
8
|
+
let _encoder = null;
|
|
9
|
+
function getEncoder() {
|
|
10
|
+
if (!_encoder) {
|
|
11
|
+
_encoder = encodingForModel('gpt-4');
|
|
12
|
+
}
|
|
13
|
+
return _encoder;
|
|
14
|
+
}
|
|
15
|
+
function countTokens(text) {
|
|
16
|
+
return getEncoder().encode(text).length;
|
|
17
|
+
}
|
|
18
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
19
|
+
const CONTEXT_FILES = ['CLAUDE.md', 'AGENTS.md', 'SOUL.md', '.cursorrules', 'codex.md'];
|
|
20
|
+
const CURSOR_RULES_DIR = join('.cursor', 'rules');
|
|
21
|
+
const MEMORY_DIR = 'memory';
|
|
22
|
+
const DAILY_DIR = join(MEMORY_DIR, 'daily');
|
|
23
|
+
const MODEL_COSTS = {
|
|
24
|
+
opus: 15, // $15 per MTok input
|
|
25
|
+
sonnet: 3, // $3 per MTok input
|
|
26
|
+
haiku: 0.25, // $0.25 per MTok input
|
|
27
|
+
};
|
|
28
|
+
const TOKEN_THRESHOLD = 8000;
|
|
29
|
+
const BLOATED_FILE_THRESHOLD = 10000;
|
|
30
|
+
function splitIntoSections(content, file) {
|
|
31
|
+
const lines = content.split('\n');
|
|
32
|
+
const sections = [];
|
|
33
|
+
let currentTitle = '(intro)';
|
|
34
|
+
let currentLines = [];
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
const headerMatch = line.match(/^(#{2,3})\s+(.+)/);
|
|
37
|
+
if (headerMatch) {
|
|
38
|
+
// flush previous
|
|
39
|
+
if (currentLines.length > 0) {
|
|
40
|
+
const text = currentLines.join('\n');
|
|
41
|
+
sections.push({ title: currentTitle, content: text, tokens: countTokens(text), file });
|
|
42
|
+
}
|
|
43
|
+
currentTitle = headerMatch[2].trim();
|
|
44
|
+
currentLines = [line];
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
currentLines.push(line);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// flush last
|
|
51
|
+
if (currentLines.length > 0) {
|
|
52
|
+
const text = currentLines.join('\n');
|
|
53
|
+
sections.push({ title: currentTitle, content: text, tokens: countTokens(text), file });
|
|
54
|
+
}
|
|
55
|
+
return sections;
|
|
56
|
+
}
|
|
57
|
+
// ── File discovery ───────────────────────────────────────────────────────────
|
|
58
|
+
function discoverContextFiles(cwd) {
|
|
59
|
+
const files = [];
|
|
60
|
+
for (const name of CONTEXT_FILES) {
|
|
61
|
+
const full = join(cwd, name);
|
|
62
|
+
if (existsSync(full))
|
|
63
|
+
files.push(full);
|
|
64
|
+
}
|
|
65
|
+
// memory/*.md (not daily/)
|
|
66
|
+
const memDir = join(cwd, MEMORY_DIR);
|
|
67
|
+
if (existsSync(memDir) && statSync(memDir).isDirectory()) {
|
|
68
|
+
try {
|
|
69
|
+
for (const entry of readdirSync(memDir)) {
|
|
70
|
+
if (!entry.endsWith('.md'))
|
|
71
|
+
continue;
|
|
72
|
+
const full = join(memDir, entry);
|
|
73
|
+
try {
|
|
74
|
+
if (statSync(full).isFile())
|
|
75
|
+
files.push(full);
|
|
76
|
+
}
|
|
77
|
+
catch { /* skip */ }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { /* skip */ }
|
|
81
|
+
}
|
|
82
|
+
// .cursor/rules
|
|
83
|
+
const cursorDir = join(cwd, CURSOR_RULES_DIR);
|
|
84
|
+
if (existsSync(cursorDir) && statSync(cursorDir).isDirectory()) {
|
|
85
|
+
try {
|
|
86
|
+
for (const entry of readdirSync(cursorDir)) {
|
|
87
|
+
const full = join(cursorDir, entry);
|
|
88
|
+
try {
|
|
89
|
+
if (statSync(full).isFile())
|
|
90
|
+
files.push(full);
|
|
91
|
+
}
|
|
92
|
+
catch { /* skip */ }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch { /* skip */ }
|
|
96
|
+
}
|
|
97
|
+
return files;
|
|
98
|
+
}
|
|
99
|
+
// ── Stale detection ──────────────────────────────────────────────────────────
|
|
100
|
+
function detectStaleSections(sections) {
|
|
101
|
+
const stale = new Set();
|
|
102
|
+
const claudeDir = join(homedir(), '.claude', 'projects');
|
|
103
|
+
if (!existsSync(claudeDir))
|
|
104
|
+
return stale;
|
|
105
|
+
// Collect recent session log content
|
|
106
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
107
|
+
let sessionContent = '';
|
|
108
|
+
try {
|
|
109
|
+
const projects = readdirSync(claudeDir);
|
|
110
|
+
for (const project of projects) {
|
|
111
|
+
const projectDir = join(claudeDir, project);
|
|
112
|
+
try {
|
|
113
|
+
if (!statSync(projectDir).isDirectory())
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const files = readdirSync(projectDir);
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
if (!file.endsWith('.jsonl'))
|
|
123
|
+
continue;
|
|
124
|
+
const full = join(projectDir, file);
|
|
125
|
+
try {
|
|
126
|
+
const stat = statSync(full);
|
|
127
|
+
if (stat.mtimeMs < sevenDaysAgo)
|
|
128
|
+
continue;
|
|
129
|
+
sessionContent += readFileSync(full, 'utf-8') + '\n';
|
|
130
|
+
}
|
|
131
|
+
catch { /* skip */ }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch { /* skip */ }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return stale;
|
|
139
|
+
}
|
|
140
|
+
if (!sessionContent)
|
|
141
|
+
return stale;
|
|
142
|
+
// Check each section: extract significant phrases and grep
|
|
143
|
+
for (const section of sections) {
|
|
144
|
+
if (section.title === '(intro)')
|
|
145
|
+
continue;
|
|
146
|
+
// Extract significant words from section content (skip short/generic)
|
|
147
|
+
const words = section.content
|
|
148
|
+
.replace(/[#`*_\-\[\](){}|]/g, ' ')
|
|
149
|
+
.split(/\s+/)
|
|
150
|
+
.filter(w => w.length > 4);
|
|
151
|
+
// Take a sample of phrases to check
|
|
152
|
+
const phrases = words.slice(0, 20);
|
|
153
|
+
if (phrases.length === 0)
|
|
154
|
+
continue;
|
|
155
|
+
const found = phrases.some(phrase => sessionContent.includes(phrase));
|
|
156
|
+
if (!found) {
|
|
157
|
+
stale.add(`${section.file}::${section.title}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return stale;
|
|
161
|
+
}
|
|
162
|
+
// ── Cost calculation ─────────────────────────────────────────────────────────
|
|
163
|
+
function calculateCost(tokens, model) {
|
|
164
|
+
const rate = MODEL_COSTS[model] || 3;
|
|
165
|
+
return (tokens / 1_000_000) * rate;
|
|
166
|
+
}
|
|
167
|
+
function formatCost(cost) {
|
|
168
|
+
if (cost < 0.001)
|
|
169
|
+
return `$${cost.toFixed(6)}`;
|
|
170
|
+
if (cost < 0.01)
|
|
171
|
+
return `$${cost.toFixed(4)}`;
|
|
172
|
+
return `$${cost.toFixed(3)}`;
|
|
173
|
+
}
|
|
174
|
+
// ── CheckResult (for full vet scan) ──────────────────────────────────────────
|
|
175
|
+
export function checkContext(cwd) {
|
|
176
|
+
const files = discoverContextFiles(cwd);
|
|
177
|
+
const issues = [];
|
|
178
|
+
let score = 100;
|
|
179
|
+
if (files.length === 0) {
|
|
180
|
+
score -= 15;
|
|
181
|
+
issues.push({
|
|
182
|
+
severity: 'error',
|
|
183
|
+
message: 'No agent context files found',
|
|
184
|
+
fixable: false,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
name: 'context',
|
|
188
|
+
score: Math.max(0, score),
|
|
189
|
+
maxScore: 100,
|
|
190
|
+
issues,
|
|
191
|
+
summary: 'no agent context files found',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const allSections = [];
|
|
195
|
+
let totalTokens = 0;
|
|
196
|
+
for (const filePath of files) {
|
|
197
|
+
const content = cachedReadFile(filePath);
|
|
198
|
+
if (!content)
|
|
199
|
+
continue;
|
|
200
|
+
const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
201
|
+
const sections = splitIntoSections(content, relPath);
|
|
202
|
+
allSections.push(...sections);
|
|
203
|
+
const fileTokens = sections.reduce((sum, s) => sum + s.tokens, 0);
|
|
204
|
+
totalTokens += fileTokens;
|
|
205
|
+
if (fileTokens > BLOATED_FILE_THRESHOLD) {
|
|
206
|
+
issues.push({
|
|
207
|
+
severity: 'warning',
|
|
208
|
+
message: `Context file exceeds 10K tokens: ${relPath} (${fileTokens} tokens)`,
|
|
209
|
+
file: relPath,
|
|
210
|
+
fixable: false,
|
|
211
|
+
fixHint: 'Split or trim this file to reduce token cost',
|
|
212
|
+
});
|
|
213
|
+
score -= 5;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Stale detection
|
|
217
|
+
const staleSections = detectStaleSections(allSections);
|
|
218
|
+
let staleDeduction = 0;
|
|
219
|
+
let staleSavings = 0;
|
|
220
|
+
for (const key of staleSections) {
|
|
221
|
+
const [file, title] = key.split('::');
|
|
222
|
+
const section = allSections.find(s => s.file === file && s.title === title);
|
|
223
|
+
if (section)
|
|
224
|
+
staleSavings += section.tokens;
|
|
225
|
+
if (staleDeduction < 40) {
|
|
226
|
+
issues.push({
|
|
227
|
+
severity: 'warning',
|
|
228
|
+
message: `Stale section: "${title}" in ${file}`,
|
|
229
|
+
file,
|
|
230
|
+
fixable: false,
|
|
231
|
+
fixHint: 'Section not referenced in recent sessions — consider removing',
|
|
232
|
+
});
|
|
233
|
+
staleDeduction += 10;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
score -= Math.min(staleDeduction, 40);
|
|
237
|
+
// Token threshold penalty
|
|
238
|
+
if (totalTokens > TOKEN_THRESHOLD) {
|
|
239
|
+
const over = totalTokens - TOKEN_THRESHOLD;
|
|
240
|
+
const penalty = Math.min(Math.floor(over / 2000) * 5, 30);
|
|
241
|
+
score -= penalty;
|
|
242
|
+
}
|
|
243
|
+
// Info issues
|
|
244
|
+
issues.push({
|
|
245
|
+
severity: 'info',
|
|
246
|
+
message: `Total context: ${totalTokens} tokens across ${files.length} file${files.length !== 1 ? 's' : ''}`,
|
|
247
|
+
fixable: false,
|
|
248
|
+
});
|
|
249
|
+
if (staleSavings > 0) {
|
|
250
|
+
const savings = formatCost(calculateCost(staleSavings, 'sonnet'));
|
|
251
|
+
issues.push({
|
|
252
|
+
severity: 'info',
|
|
253
|
+
message: `Potential savings: ${staleSavings} tokens (${savings}/call at sonnet rates) from removing stale sections`,
|
|
254
|
+
fixable: false,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
name: 'context',
|
|
259
|
+
score: Math.max(0, score),
|
|
260
|
+
maxScore: 100,
|
|
261
|
+
issues,
|
|
262
|
+
summary: `${totalTokens} tokens in ${files.length} context file${files.length !== 1 ? 's' : ''}${staleSections.size > 0 ? `, ${staleSections.size} stale section${staleSections.size !== 1 ? 's' : ''}` : ''}`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// ── Subcommand output ────────────────────────────────────────────────────────
|
|
266
|
+
export async function runContextCommand(format, cwd) {
|
|
267
|
+
const files = discoverContextFiles(cwd);
|
|
268
|
+
if (files.length === 0) {
|
|
269
|
+
if (format === 'json') {
|
|
270
|
+
console.log(JSON.stringify({ files: [], sections: [], totalTokens: 0, costs: {}, stale: [], score: 0 }, null, 2));
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
console.log(`\n ${c.bold}vet context${c.reset} — no agent context files found\n`);
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const allSections = [];
|
|
278
|
+
let totalTokens = 0;
|
|
279
|
+
for (const filePath of files) {
|
|
280
|
+
const content = cachedReadFile(filePath);
|
|
281
|
+
if (!content)
|
|
282
|
+
continue;
|
|
283
|
+
const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
284
|
+
const sections = splitIntoSections(content, relPath);
|
|
285
|
+
allSections.push(...sections);
|
|
286
|
+
totalTokens += sections.reduce((sum, s) => sum + s.tokens, 0);
|
|
287
|
+
}
|
|
288
|
+
const staleSections = detectStaleSections(allSections);
|
|
289
|
+
if (format === 'json') {
|
|
290
|
+
const result = {
|
|
291
|
+
files: files.map(f => f.startsWith(cwd) ? f.slice(cwd.length + 1) : f),
|
|
292
|
+
sections: allSections.map(s => ({
|
|
293
|
+
file: s.file,
|
|
294
|
+
title: s.title,
|
|
295
|
+
tokens: s.tokens,
|
|
296
|
+
stale: staleSections.has(`${s.file}::${s.title}`),
|
|
297
|
+
costs: {
|
|
298
|
+
opus: calculateCost(s.tokens, 'opus'),
|
|
299
|
+
sonnet: calculateCost(s.tokens, 'sonnet'),
|
|
300
|
+
haiku: calculateCost(s.tokens, 'haiku'),
|
|
301
|
+
},
|
|
302
|
+
})),
|
|
303
|
+
totalTokens,
|
|
304
|
+
costs: {
|
|
305
|
+
opus: calculateCost(totalTokens, 'opus'),
|
|
306
|
+
sonnet: calculateCost(totalTokens, 'sonnet'),
|
|
307
|
+
haiku: calculateCost(totalTokens, 'haiku'),
|
|
308
|
+
},
|
|
309
|
+
stale: [...staleSections],
|
|
310
|
+
staleSavingsTokens: allSections
|
|
311
|
+
.filter(s => staleSections.has(`${s.file}::${s.title}`))
|
|
312
|
+
.reduce((sum, s) => sum + s.tokens, 0),
|
|
313
|
+
};
|
|
314
|
+
console.log(JSON.stringify(result, null, 2));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// ASCII table output
|
|
318
|
+
console.log(`\n ${c.bold}vet context${c.reset} — agent context cost audit\n`);
|
|
319
|
+
// Header
|
|
320
|
+
const fileW = 30;
|
|
321
|
+
const sectionW = 25;
|
|
322
|
+
const tokenW = 8;
|
|
323
|
+
const costW = 12;
|
|
324
|
+
console.log(` ${c.dim}${'─'.repeat(fileW + sectionW + tokenW + costW * 3 + 10)}${c.reset}`);
|
|
325
|
+
console.log(` ${pad('File', fileW)} ${pad('Section', sectionW)} ${padR('Tokens', tokenW)} ${padR('Opus', costW)} ${padR('Sonnet', costW)} ${padR('Haiku', costW)}`);
|
|
326
|
+
console.log(` ${c.dim}${'─'.repeat(fileW + sectionW + tokenW + costW * 3 + 10)}${c.reset}`);
|
|
327
|
+
for (const s of allSections) {
|
|
328
|
+
const isStale = staleSections.has(`${s.file}::${s.title}`);
|
|
329
|
+
const staleMarker = isStale ? ` ${c.yellow}⚠ stale${c.reset}` : '';
|
|
330
|
+
const file = truncate(s.file, fileW);
|
|
331
|
+
const title = truncate(s.title, sectionW);
|
|
332
|
+
console.log(` ${pad(file, fileW)} ${pad(title, sectionW)} ${padR(String(s.tokens), tokenW)} ${padR(formatCost(calculateCost(s.tokens, 'opus')), costW)} ${padR(formatCost(calculateCost(s.tokens, 'sonnet')), costW)} ${padR(formatCost(calculateCost(s.tokens, 'haiku')), costW)}${staleMarker}`);
|
|
333
|
+
}
|
|
334
|
+
console.log(` ${c.dim}${'─'.repeat(fileW + sectionW + tokenW + costW * 3 + 10)}${c.reset}`);
|
|
335
|
+
console.log(` ${pad(c.bold + 'Total' + c.reset, fileW)} ${pad('', sectionW)} ${padR(String(totalTokens), tokenW)} ${padR(formatCost(calculateCost(totalTokens, 'opus')), costW)} ${padR(formatCost(calculateCost(totalTokens, 'sonnet')), costW)} ${padR(formatCost(calculateCost(totalTokens, 'haiku')), costW)}`);
|
|
336
|
+
console.log('');
|
|
337
|
+
if (staleSections.size > 0) {
|
|
338
|
+
const staleToks = allSections
|
|
339
|
+
.filter(s => staleSections.has(`${s.file}::${s.title}`))
|
|
340
|
+
.reduce((sum, s) => sum + s.tokens, 0);
|
|
341
|
+
console.log(` ${c.yellow}⚠ ${staleSections.size} stale section${staleSections.size !== 1 ? 's' : ''} detected${c.reset} — ${staleToks} tokens (${formatCost(calculateCost(staleToks, 'sonnet'))}/call at sonnet rates)`);
|
|
342
|
+
console.log(` ${c.dim}These sections weren't referenced in recent Claude sessions${c.reset}\n`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// ── String helpers ───────────────────────────────────────────────────────────
|
|
346
|
+
function pad(s, w) {
|
|
347
|
+
// Strip ANSI for length calculation
|
|
348
|
+
const clean = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
349
|
+
return s + ' '.repeat(Math.max(0, w - clean.length));
|
|
350
|
+
}
|
|
351
|
+
function padR(s, w) {
|
|
352
|
+
const clean = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
353
|
+
return ' '.repeat(Math.max(0, w - clean.length)) + s;
|
|
354
|
+
}
|
|
355
|
+
function truncate(s, max) {
|
|
356
|
+
if (s.length <= max)
|
|
357
|
+
return s;
|
|
358
|
+
return s.slice(0, max - 1) + '…';
|
|
359
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,7 @@ import { checkLoop, runLoopCommand } from './checks/loop.js';
|
|
|
31
31
|
import { checkBloat, runBloatCommand } from './checks/bloat.js';
|
|
32
32
|
import { checkGuard, runGuardCommand } from './checks/guard.js';
|
|
33
33
|
import { checkExplain, runExplainCommand } from './checks/explain.js';
|
|
34
|
+
import { checkContext, runContextCommand } from './checks/context.js';
|
|
34
35
|
import { checkCompleteness } from './checks/completeness.js';
|
|
35
36
|
import { score } from './scorer.js';
|
|
36
37
|
import { reportPretty, reportJSON, reportBadge } from './reporter.js';
|
|
@@ -86,6 +87,7 @@ if (flags.has('--help') || flags.has('-h')) {
|
|
|
86
87
|
npx @safetnsr/vet bloat detect agent-generated code bloat
|
|
87
88
|
npx @safetnsr/vet guard [dir] scan for destructive operation bomb sites
|
|
88
89
|
npx @safetnsr/vet explain [--since REF] [--verbose] [--json] risk-tier agent changes
|
|
90
|
+
npx @safetnsr/vet context [dir] audit agent context files for token cost + stale sections
|
|
89
91
|
|
|
90
92
|
${c.dim}categories:${c.reset}
|
|
91
93
|
security (30%) scan, secrets, config, model usage
|
|
@@ -121,7 +123,7 @@ if (flags.has('--version') || flags.has('-v')) {
|
|
|
121
123
|
}
|
|
122
124
|
process.exit(0);
|
|
123
125
|
}
|
|
124
|
-
const COMMANDS = ['init', 'receipt', 'map', 'permissions', 'compact', 'subsidy', 'loop', 'bloat', 'guard', 'explain'];
|
|
126
|
+
const COMMANDS = ['init', 'receipt', 'map', 'permissions', 'compact', 'subsidy', 'loop', 'bloat', 'guard', 'explain', 'context'];
|
|
125
127
|
const command = COMMANDS.includes(positional[0]) ? positional[0] : undefined;
|
|
126
128
|
const cwd = resolve(positional.find(p => !COMMANDS.includes(p)) || '.');
|
|
127
129
|
const isCI = flags.has('--ci');
|
|
@@ -263,6 +265,17 @@ if (command === 'guard') {
|
|
|
263
265
|
}
|
|
264
266
|
process.exit(0);
|
|
265
267
|
}
|
|
268
|
+
if (command === 'context') {
|
|
269
|
+
try {
|
|
270
|
+
const format = isJSON ? 'json' : 'ascii';
|
|
271
|
+
await runContextCommand(format, cwd);
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
console.error(`${c.red}context failed:${c.reset}`, e instanceof Error ? e.message : e);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
process.exit(0);
|
|
278
|
+
}
|
|
266
279
|
if (command === 'explain') {
|
|
267
280
|
try {
|
|
268
281
|
const format = isJSON ? 'json' : 'ascii';
|
|
@@ -327,7 +340,7 @@ async function runChecks() {
|
|
|
327
340
|
}
|
|
328
341
|
}
|
|
329
342
|
// Run ALL independent checks in parallel
|
|
330
|
-
const [scanResult, secretsResult, configResult, modelsResult, owaspResult, permissionsResult, integrityResult, readyResult, debtResult, depsResult, receiptResult, compactResult, subsidyResult, memoryResult, verifyResult, testsResult, loopResult, completenessResult, bloatResult, guardResult, explainResult, architectureResult, aireadyResult, deepResult, semanticResult, hotspotsResult, clonesResult,] = await Promise.all([
|
|
343
|
+
const [scanResult, secretsResult, configResult, modelsResult, owaspResult, permissionsResult, integrityResult, readyResult, debtResult, depsResult, receiptResult, compactResult, subsidyResult, memoryResult, verifyResult, testsResult, loopResult, completenessResult, bloatResult, guardResult, explainResult, architectureResult, aireadyResult, deepResult, semanticResult, hotspotsResult, clonesResult, contextResult,] = await Promise.all([
|
|
331
344
|
withTimeout('scan', () => checkScan(cwd)),
|
|
332
345
|
withTimeout('secrets', () => checkSecrets(cwd)),
|
|
333
346
|
withTimeout('config', () => checkConfig(cwd, ignore)),
|
|
@@ -355,6 +368,7 @@ async function runChecks() {
|
|
|
355
368
|
withTimeout('semantic', () => checkSemantic(cwd), 60_000),
|
|
356
369
|
withTimeout('hotspots', () => checkHotspots(cwd), 30_000),
|
|
357
370
|
withTimeout('clones', () => checkClones(cwd), 60_000),
|
|
371
|
+
withTimeout('context', () => checkContext(cwd)),
|
|
358
372
|
]);
|
|
359
373
|
// Git-dependent checks (diff + history) — parallel with each other
|
|
360
374
|
const [diffResult, historyResult] = await Promise.all([
|
|
@@ -369,7 +383,7 @@ async function runChecks() {
|
|
|
369
383
|
debt: [readyResult, historyResult, debtResult, bloatResult, clonesResult],
|
|
370
384
|
deps: [depsResult],
|
|
371
385
|
architecture: [architectureResult],
|
|
372
|
-
aiready: [aireadyResult, deepResult, semanticResult],
|
|
386
|
+
aiready: [aireadyResult, deepResult, semanticResult, contextResult],
|
|
373
387
|
history: [hotspotsResult],
|
|
374
388
|
});
|
|
375
389
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@safetnsr/vet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "vet your AI-generated code — one command, one score card, one letter grade",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"@safetnsr/model-graveyard": "^0.2.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@huggingface/transformers": "^3.8.1"
|
|
47
|
+
"@huggingface/transformers": "^3.8.1",
|
|
48
|
+
"js-tiktoken": "^1.0.21"
|
|
48
49
|
}
|
|
49
50
|
}
|