@safetnsr/vet 1.0.0 → 1.1.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/deps.js +6 -4
- package/dist/checks/integrity.js +65 -13
- package/dist/checks/memory.d.ts +2 -0
- package/dist/checks/memory.js +275 -0
- package/dist/checks/models.js +5 -0
- package/dist/checks/owasp-checks.d.ts +51 -0
- package/dist/checks/owasp-checks.js +670 -0
- package/dist/checks/owasp.js +2 -739
- package/dist/checks/scan.js +5 -33
- package/dist/cli.js +4 -1
- package/dist/reporter.js +9 -5
- package/dist/util.d.ts +8 -1
- package/dist/util.js +40 -9
- package/package.json +1 -1
package/dist/checks/deps.js
CHANGED
|
@@ -57,18 +57,18 @@ export function levenshtein(a, b) {
|
|
|
57
57
|
// ── Import extraction ────────────────────────────────────────────────────────
|
|
58
58
|
export function extractImports(source) {
|
|
59
59
|
const imports = new Set();
|
|
60
|
-
// import
|
|
60
|
+
// static import: import X from <specifier>
|
|
61
61
|
const importFrom = /import\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
62
62
|
let match;
|
|
63
63
|
while ((match = importFrom.exec(source)) !== null) {
|
|
64
64
|
imports.add(match[1]);
|
|
65
65
|
}
|
|
66
|
-
// require
|
|
66
|
+
// CommonJS require
|
|
67
67
|
const requirePat = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
68
68
|
while ((match = requirePat.exec(source)) !== null) {
|
|
69
69
|
imports.add(match[1]);
|
|
70
70
|
}
|
|
71
|
-
// import(
|
|
71
|
+
// dynamic import()
|
|
72
72
|
const dynamicImport = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
73
73
|
while ((match = dynamicImport.exec(source)) !== null) {
|
|
74
74
|
imports.add(match[1]);
|
|
@@ -204,9 +204,11 @@ export async function checkDeps(cwd) {
|
|
|
204
204
|
// ── 3 & 4. Dead deps + phantom imports ─────────────────────────────────────
|
|
205
205
|
const sourceExts = new Set(['.ts', '.js', '.tsx', '.jsx', '.mts', '.mjs', '.cts', '.cjs']);
|
|
206
206
|
const allFiles = walkFiles(cwd);
|
|
207
|
+
const isTestFile = (f) => /\.(test|spec)\.[jt]sx?$/.test(f) || f.includes('__tests__') || /^test[/\\]/.test(f);
|
|
207
208
|
const sourceFiles = allFiles.filter(f => {
|
|
208
209
|
const ext = f.substring(f.lastIndexOf('.'));
|
|
209
|
-
|
|
210
|
+
// Skip test files — they contain import strings as test fixtures, not real imports
|
|
211
|
+
return sourceExts.has(ext) && !isTestFile(f);
|
|
210
212
|
});
|
|
211
213
|
const importedPackages = new Set();
|
|
212
214
|
for (const file of sourceFiles) {
|
package/dist/checks/integrity.js
CHANGED
|
@@ -36,25 +36,68 @@ function resolveRelativeImport(importPath, fromFile, cwd) {
|
|
|
36
36
|
}
|
|
37
37
|
return false;
|
|
38
38
|
}
|
|
39
|
+
function isInsideStringLiteral(line, matchIndex) {
|
|
40
|
+
// Check if the match position is inside a string literal (template literal, quote)
|
|
41
|
+
// by counting unescaped quotes before the match
|
|
42
|
+
let inSingle = false;
|
|
43
|
+
let inDouble = false;
|
|
44
|
+
let inTemplate = false;
|
|
45
|
+
for (let i = 0; i < matchIndex && i < line.length; i++) {
|
|
46
|
+
const ch = line[i];
|
|
47
|
+
if (ch === '\\') {
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (ch === "'" && !inDouble && !inTemplate)
|
|
52
|
+
inSingle = !inSingle;
|
|
53
|
+
else if (ch === '"' && !inSingle && !inTemplate)
|
|
54
|
+
inDouble = !inDouble;
|
|
55
|
+
else if (ch === '`' && !inSingle && !inDouble)
|
|
56
|
+
inTemplate = !inTemplate;
|
|
57
|
+
}
|
|
58
|
+
// If we're inside a string context AND the line itself is not an import/require statement,
|
|
59
|
+
// then this is likely a string literal containing import-like text
|
|
60
|
+
return inSingle || inDouble || inTemplate;
|
|
61
|
+
}
|
|
62
|
+
function isCommentLine(line) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
return trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*');
|
|
65
|
+
}
|
|
39
66
|
function extractRelativeImports(source) {
|
|
40
67
|
const imports = [];
|
|
41
68
|
const lines = source.split('\n');
|
|
42
69
|
for (let i = 0; i < lines.length; i++) {
|
|
43
70
|
const line = lines[i];
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
71
|
+
// Skip comment lines
|
|
72
|
+
if (isCommentLine(line))
|
|
73
|
+
continue;
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
// import ... from './foo' or '../bar' — must be an actual import statement
|
|
76
|
+
if (/^\s*(?:import|export)\s/.test(line)) {
|
|
77
|
+
const fromMatch = line.match(/from\s+['"](\.[^'"]+)['"]/);
|
|
78
|
+
if (fromMatch) {
|
|
79
|
+
imports.push({ path: fromMatch[1], line: i + 1 });
|
|
80
|
+
}
|
|
48
81
|
}
|
|
49
|
-
// require('./foo')
|
|
82
|
+
// require('./foo') — must be at statement level, not inside a string
|
|
50
83
|
const reqMatch = line.match(/require\s*\(\s*['"](\.[^'"]+)['"]\s*\)/);
|
|
51
|
-
if (reqMatch) {
|
|
52
|
-
|
|
84
|
+
if (reqMatch && !isInsideStringLiteral(line, line.indexOf(reqMatch[0]))) {
|
|
85
|
+
// Skip if the require is inside a string literal (test fixtures)
|
|
86
|
+
const beforeReq = line.substring(0, line.indexOf(reqMatch[0]));
|
|
87
|
+
if (!/['"`]/.test(beforeReq.slice(-1))) {
|
|
88
|
+
imports.push({ path: reqMatch[1], line: i + 1 });
|
|
89
|
+
}
|
|
53
90
|
}
|
|
54
|
-
// import('./foo')
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
91
|
+
// Dynamic import('./foo') — actual import() call, not in string
|
|
92
|
+
if (/^\s*(?:const|let|var|await|return)?\s*/.test(line)) {
|
|
93
|
+
const dynMatch = line.match(/import\s*\(\s*['"](\.[^'"]+)['"]\s*\)/);
|
|
94
|
+
if (dynMatch && !isCommentLine(line)) {
|
|
95
|
+
// Make sure it's not inside a string literal (e.g. a test describing imports)
|
|
96
|
+
const matchIdx = line.indexOf(dynMatch[0]);
|
|
97
|
+
if (!isInsideStringLiteral(line, matchIdx)) {
|
|
98
|
+
imports.push({ path: dynMatch[1], line: i + 1 });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
58
101
|
}
|
|
59
102
|
}
|
|
60
103
|
return imports;
|
|
@@ -89,19 +132,25 @@ function checkHallucinatedImports(cwd, files) {
|
|
|
89
132
|
return issues;
|
|
90
133
|
}
|
|
91
134
|
// ── Empty catch blocks ───────────────────────────────────────────────────────
|
|
135
|
+
function isTestFile(file) {
|
|
136
|
+
return /\.(test|spec)\.[jt]sx?$/.test(file) || file.includes('__tests__') || /^test[/\\]/.test(file);
|
|
137
|
+
}
|
|
92
138
|
function checkEmptyCatch(cwd, files) {
|
|
93
139
|
const issues = [];
|
|
94
140
|
const sourceExts = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.mjs']);
|
|
95
141
|
for (const file of files) {
|
|
96
142
|
if (!sourceExts.has(extname(file)))
|
|
97
143
|
continue;
|
|
144
|
+
// Skip test files — empty catches in tests are usually intentional (testing error paths)
|
|
145
|
+
if (isTestFile(file))
|
|
146
|
+
continue;
|
|
98
147
|
const content = readFile(join(cwd, file));
|
|
99
148
|
if (!content)
|
|
100
149
|
continue;
|
|
101
150
|
const lines = content.split('\n');
|
|
102
151
|
for (let i = 0; i < lines.length; i++) {
|
|
103
152
|
const line = lines[i];
|
|
104
|
-
// catch
|
|
153
|
+
// single-line catch with param and empty body — error silently swallowed
|
|
105
154
|
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line)) {
|
|
106
155
|
issues.push({
|
|
107
156
|
severity: 'error',
|
|
@@ -113,7 +162,7 @@ function checkEmptyCatch(cwd, files) {
|
|
|
113
162
|
});
|
|
114
163
|
continue;
|
|
115
164
|
}
|
|
116
|
-
// catch
|
|
165
|
+
// single-line catch without param and empty body
|
|
117
166
|
if (/catch\s*\{\s*\}/.test(line)) {
|
|
118
167
|
issues.push({
|
|
119
168
|
severity: 'error',
|
|
@@ -234,6 +283,9 @@ function checkUnhandledAsync(cwd, files) {
|
|
|
234
283
|
for (const file of files) {
|
|
235
284
|
if (!sourceExts.has(extname(file)))
|
|
236
285
|
continue;
|
|
286
|
+
// Skip test files — test runners handle errors at the framework level
|
|
287
|
+
if (isTestFile(file))
|
|
288
|
+
continue;
|
|
237
289
|
const content = readFile(join(cwd, file));
|
|
238
290
|
if (!content)
|
|
239
291
|
continue;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
// ── Memory file targets ──────────────────────────────────────────────────────
|
|
4
|
+
const ROOT_FILES = ['CLAUDE.md', 'AGENTS.md', 'SOUL.md', '.cursorrules', 'codex.md'];
|
|
5
|
+
const MEMORY_DIR = 'memory';
|
|
6
|
+
const DAILY_DIR = join(MEMORY_DIR, 'daily');
|
|
7
|
+
const MAX_DAILY_FILES = 30;
|
|
8
|
+
// ── Tool categories for contradiction detection ─────────────────────────────
|
|
9
|
+
const TOOL_CATEGORIES = {
|
|
10
|
+
'test framework': [/\bvitest\b/i, /\bjest\b/i, /\bmocha\b/i, /\bnode:test\b/i, /\bava\b/i],
|
|
11
|
+
'package manager': [/\bnpm\b/, /\byarn\b/, /\bpnpm\b/, /\bbun\b/],
|
|
12
|
+
};
|
|
13
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
14
|
+
function safeRead(path) {
|
|
15
|
+
try {
|
|
16
|
+
return readFileSync(path, 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function collectMemoryFiles(cwd) {
|
|
23
|
+
const files = [];
|
|
24
|
+
// Root-level memory files
|
|
25
|
+
for (const name of ROOT_FILES) {
|
|
26
|
+
const full = join(cwd, name);
|
|
27
|
+
if (existsSync(full))
|
|
28
|
+
files.push(full);
|
|
29
|
+
}
|
|
30
|
+
// memory/*.md
|
|
31
|
+
const memDir = join(cwd, MEMORY_DIR);
|
|
32
|
+
if (existsSync(memDir) && statSync(memDir).isDirectory()) {
|
|
33
|
+
try {
|
|
34
|
+
for (const entry of readdirSync(memDir)) {
|
|
35
|
+
if (!entry.endsWith('.md'))
|
|
36
|
+
continue;
|
|
37
|
+
const full = join(memDir, entry);
|
|
38
|
+
try {
|
|
39
|
+
if (statSync(full).isFile())
|
|
40
|
+
files.push(full);
|
|
41
|
+
}
|
|
42
|
+
catch { /* skip */ }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch { /* skip */ }
|
|
46
|
+
}
|
|
47
|
+
// memory/daily/*.md (capped)
|
|
48
|
+
const dailyDir = join(cwd, DAILY_DIR);
|
|
49
|
+
if (existsSync(dailyDir) && statSync(dailyDir).isDirectory()) {
|
|
50
|
+
try {
|
|
51
|
+
const entries = readdirSync(dailyDir).filter(e => e.endsWith('.md')).sort().reverse();
|
|
52
|
+
for (const entry of entries.slice(0, MAX_DAILY_FILES)) {
|
|
53
|
+
const full = join(dailyDir, entry);
|
|
54
|
+
try {
|
|
55
|
+
if (statSync(full).isFile())
|
|
56
|
+
files.push(full);
|
|
57
|
+
}
|
|
58
|
+
catch { /* skip */ }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { /* skip */ }
|
|
62
|
+
}
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
/** Extract @scope/package references */
|
|
66
|
+
function extractScopedPackages(content) {
|
|
67
|
+
const results = [];
|
|
68
|
+
const lines = content.split('\n');
|
|
69
|
+
for (let i = 0; i < lines.length; i++) {
|
|
70
|
+
const matches = lines[i].matchAll(/@[a-zA-Z0-9_-]+\/[a-zA-Z0-9._-]+/g);
|
|
71
|
+
for (const m of matches) {
|
|
72
|
+
results.push({ pkg: m[0], line: i + 1 });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
/** Extract file/dir path references */
|
|
78
|
+
function extractPaths(content) {
|
|
79
|
+
const results = [];
|
|
80
|
+
const lines = content.split('\n');
|
|
81
|
+
for (let i = 0; i < lines.length; i++) {
|
|
82
|
+
const line = lines[i];
|
|
83
|
+
// Skip lines that are purely URLs
|
|
84
|
+
// Match absolute paths
|
|
85
|
+
const absMatches = line.matchAll(/(?:^|\s|["`'(])(\/((?:var|home|usr|etc|opt|tmp|root|srv|mnt)[^\s"'`),;]*))(?=[\s"'`),;]|$)/g);
|
|
86
|
+
for (const m of absMatches) {
|
|
87
|
+
const p = m[1].replace(/[.,:;)]+$/, '');
|
|
88
|
+
if (p.startsWith('//') || p.includes('://'))
|
|
89
|
+
continue;
|
|
90
|
+
if (p.length < 4)
|
|
91
|
+
continue;
|
|
92
|
+
results.push({ path: p, line: i + 1 });
|
|
93
|
+
}
|
|
94
|
+
// Match relative paths starting with ./ or ../
|
|
95
|
+
const relMatches = line.matchAll(/(?:^|\s|["`'(])(\.\.?\/[^\s"'`),;]+)/g);
|
|
96
|
+
for (const m of relMatches) {
|
|
97
|
+
const p = m[1].replace(/[.,:;)]+$/, '');
|
|
98
|
+
if (p.includes('://'))
|
|
99
|
+
continue;
|
|
100
|
+
results.push({ path: p, line: i + 1 });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
/** Extract tool mentions per category */
|
|
106
|
+
function extractToolMentions(content, fileName) {
|
|
107
|
+
const mentions = new Map();
|
|
108
|
+
const lines = content.split('\n');
|
|
109
|
+
for (const [category, patterns] of Object.entries(TOOL_CATEGORIES)) {
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
for (const regex of patterns) {
|
|
112
|
+
if (regex.test(lines[i])) {
|
|
113
|
+
const toolMatch = lines[i].match(regex);
|
|
114
|
+
if (toolMatch) {
|
|
115
|
+
const existing = mentions.get(category) || [];
|
|
116
|
+
existing.push({ tool: toolMatch[0].toLowerCase(), file: fileName, line: i + 1 });
|
|
117
|
+
mentions.set(category, existing);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return mentions;
|
|
124
|
+
}
|
|
125
|
+
/** Count meaningful fact claims in a file */
|
|
126
|
+
function countFacts(content) {
|
|
127
|
+
let count = 0;
|
|
128
|
+
const lines = content.split('\n');
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
const trimmed = line.trim();
|
|
131
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('---') || trimmed.startsWith('```'))
|
|
132
|
+
continue;
|
|
133
|
+
// A "fact" is a line with at least some substantive content
|
|
134
|
+
if (trimmed.length > 15 && /[a-zA-Z]/.test(trimmed)) {
|
|
135
|
+
// Contains a keyword-like pattern (assignment, instruction, reference)
|
|
136
|
+
if (/[:=→\->]|use |stack|requires?|install|run |npm |config|version|path|file|dir|tool/i.test(trimmed)) {
|
|
137
|
+
count++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return count;
|
|
142
|
+
}
|
|
143
|
+
// ── Main check ───────────────────────────────────────────────────────────────
|
|
144
|
+
export function checkMemory(cwd) {
|
|
145
|
+
const memoryFiles = collectMemoryFiles(cwd);
|
|
146
|
+
const issues = [];
|
|
147
|
+
let deductions = 0;
|
|
148
|
+
if (memoryFiles.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
name: 'memory',
|
|
151
|
+
score: 100,
|
|
152
|
+
maxScore: 100,
|
|
153
|
+
issues: [],
|
|
154
|
+
summary: 'no agent memory files found',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Load package.json deps
|
|
158
|
+
const pkgPath = join(cwd, 'package.json');
|
|
159
|
+
const allDeps = new Set();
|
|
160
|
+
if (existsSync(pkgPath)) {
|
|
161
|
+
try {
|
|
162
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
163
|
+
for (const key of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
|
|
164
|
+
if (pkg[key]) {
|
|
165
|
+
for (const name of Object.keys(pkg[key])) {
|
|
166
|
+
allDeps.add(name);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch { /* skip */ }
|
|
172
|
+
}
|
|
173
|
+
// Collect all tool mentions across files for contradiction detection
|
|
174
|
+
const globalToolMentions = new Map();
|
|
175
|
+
for (const filePath of memoryFiles) {
|
|
176
|
+
const content = safeRead(filePath);
|
|
177
|
+
if (!content)
|
|
178
|
+
continue;
|
|
179
|
+
const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
180
|
+
// 1. Stale package references
|
|
181
|
+
if (allDeps.size > 0) {
|
|
182
|
+
const pkgRefs = extractScopedPackages(content);
|
|
183
|
+
for (const { pkg, line } of pkgRefs) {
|
|
184
|
+
if (!allDeps.has(pkg)) {
|
|
185
|
+
issues.push({
|
|
186
|
+
severity: 'warning',
|
|
187
|
+
message: `Stale package: ${pkg} not in package.json`,
|
|
188
|
+
file: relPath,
|
|
189
|
+
line,
|
|
190
|
+
fixable: false,
|
|
191
|
+
fixHint: 'Remove or update this reference',
|
|
192
|
+
});
|
|
193
|
+
deductions += 10;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// 2. Broken path references
|
|
198
|
+
const pathRefs = extractPaths(content);
|
|
199
|
+
for (const { path: p, line } of pathRefs) {
|
|
200
|
+
const resolved = p.startsWith('/') ? p : resolve(cwd, p);
|
|
201
|
+
if (!existsSync(resolved)) {
|
|
202
|
+
issues.push({
|
|
203
|
+
severity: 'error',
|
|
204
|
+
message: `Broken path reference: ${p}`,
|
|
205
|
+
file: relPath,
|
|
206
|
+
line,
|
|
207
|
+
fixable: false,
|
|
208
|
+
fixHint: 'Remove or update this path reference',
|
|
209
|
+
});
|
|
210
|
+
deductions += 15;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// 3. Collect tool mentions for contradiction check
|
|
214
|
+
const toolMentions = extractToolMentions(content, relPath);
|
|
215
|
+
for (const [category, mentions] of toolMentions) {
|
|
216
|
+
const existing = globalToolMentions.get(category) || [];
|
|
217
|
+
existing.push(...mentions);
|
|
218
|
+
globalToolMentions.set(category, existing);
|
|
219
|
+
}
|
|
220
|
+
// 4. Bloat check
|
|
221
|
+
if (content.length > 5000) {
|
|
222
|
+
const factCount = countFacts(content);
|
|
223
|
+
if (factCount < 3) {
|
|
224
|
+
issues.push({
|
|
225
|
+
severity: 'info',
|
|
226
|
+
message: `Bloated memory file: ${content.length} chars but only ${factCount} fact claims`,
|
|
227
|
+
file: relPath,
|
|
228
|
+
line: 1,
|
|
229
|
+
fixable: false,
|
|
230
|
+
fixHint: 'Trim this file to only essential facts',
|
|
231
|
+
});
|
|
232
|
+
deductions += 5;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// 3. Contradiction detection
|
|
237
|
+
for (const [category, mentions] of globalToolMentions) {
|
|
238
|
+
const uniqueTools = new Map();
|
|
239
|
+
for (const m of mentions) {
|
|
240
|
+
if (!uniqueTools.has(m.tool)) {
|
|
241
|
+
uniqueTools.set(m.tool, { file: m.file, line: m.line });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (uniqueTools.size > 1) {
|
|
245
|
+
const tools = [...uniqueTools.entries()];
|
|
246
|
+
for (let i = 0; i < tools.length; i++) {
|
|
247
|
+
for (let j = i + 1; j < tools.length; j++) {
|
|
248
|
+
// Only flag if they're in different files
|
|
249
|
+
if (tools[i][1].file !== tools[j][1].file) {
|
|
250
|
+
issues.push({
|
|
251
|
+
severity: 'warning',
|
|
252
|
+
message: `Contradiction in ${category}: "${tools[i][0]}" in ${tools[i][1].file} vs "${tools[j][0]}" in ${tools[j][1].file}`,
|
|
253
|
+
file: tools[i][1].file,
|
|
254
|
+
line: tools[i][1].line,
|
|
255
|
+
fixable: false,
|
|
256
|
+
fixHint: `Standardize on one ${category} across memory files`,
|
|
257
|
+
});
|
|
258
|
+
deductions += 10;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const finalScore = Math.max(0, 100 - deductions);
|
|
265
|
+
const issueCount = issues.length;
|
|
266
|
+
return {
|
|
267
|
+
name: 'memory',
|
|
268
|
+
score: finalScore,
|
|
269
|
+
maxScore: 100,
|
|
270
|
+
issues,
|
|
271
|
+
summary: issueCount === 0
|
|
272
|
+
? `${memoryFiles.length} memory file${memoryFiles.length !== 1 ? 's' : ''} scanned, clean`
|
|
273
|
+
: `${issueCount} stale fact${issueCount !== 1 ? 's' : ''} found in agent memory files`,
|
|
274
|
+
};
|
|
275
|
+
}
|
package/dist/checks/models.js
CHANGED
|
@@ -8,9 +8,14 @@ async function tryModelGraveyard(cwd) {
|
|
|
8
8
|
return null;
|
|
9
9
|
const report = await mod.scan(cwd);
|
|
10
10
|
const issues = [];
|
|
11
|
+
// Files that define deprecated model registries should not be flagged
|
|
12
|
+
const SELF_FILES = ['models.ts', 'models.js', 'model-graveyard', 'model-registry', 'sunset', 'fix/models'];
|
|
11
13
|
for (const match of report.matches) {
|
|
12
14
|
if (!match.model)
|
|
13
15
|
continue;
|
|
16
|
+
// Skip self-referencing files (model definition/fix files)
|
|
17
|
+
if (match.file && SELF_FILES.some(s => match.file.toLowerCase().includes(s)))
|
|
18
|
+
continue;
|
|
14
19
|
if (match.model.status === 'deprecated' || match.model.status === 'eol') {
|
|
15
20
|
issues.push({
|
|
16
21
|
severity: 'error',
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export declare function collectAgentConfigFiles(cwd: string): string[];
|
|
2
|
+
export declare function collectMcpConfigFiles(cwd: string): string[];
|
|
3
|
+
export declare function readTextFile(filePath: string): string | null;
|
|
4
|
+
export interface OwaspFinding {
|
|
5
|
+
asiId: string;
|
|
6
|
+
severity: 'error' | 'warning' | 'info';
|
|
7
|
+
message: string;
|
|
8
|
+
file?: string;
|
|
9
|
+
line?: number;
|
|
10
|
+
fixHint?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function checkASI01(cwd: string, configFiles: string[]): {
|
|
13
|
+
findings: OwaspFinding[];
|
|
14
|
+
deduction: number;
|
|
15
|
+
};
|
|
16
|
+
export declare function checkASI02(cwd: string, mcpFiles: string[]): {
|
|
17
|
+
findings: OwaspFinding[];
|
|
18
|
+
deduction: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function checkASI03(cwd: string, configFiles: string[]): {
|
|
21
|
+
findings: OwaspFinding[];
|
|
22
|
+
deduction: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function checkASI04(cwd: string, mcpFiles: string[], agentConfigFiles: string[]): {
|
|
25
|
+
findings: OwaspFinding[];
|
|
26
|
+
deduction: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function checkASI05(cwd: string, configFiles: string[]): {
|
|
29
|
+
findings: OwaspFinding[];
|
|
30
|
+
deduction: number;
|
|
31
|
+
};
|
|
32
|
+
export declare function checkASI06(cwd: string): {
|
|
33
|
+
findings: OwaspFinding[];
|
|
34
|
+
deduction: number;
|
|
35
|
+
};
|
|
36
|
+
export declare function checkASI07(cwd: string, configFiles: string[]): {
|
|
37
|
+
findings: OwaspFinding[];
|
|
38
|
+
deduction: number;
|
|
39
|
+
};
|
|
40
|
+
export declare function checkASI08(cwd: string, configFiles: string[]): {
|
|
41
|
+
findings: OwaspFinding[];
|
|
42
|
+
deduction: number;
|
|
43
|
+
};
|
|
44
|
+
export declare function checkASI09(cwd: string, configFiles: string[]): {
|
|
45
|
+
findings: OwaspFinding[];
|
|
46
|
+
deduction: number;
|
|
47
|
+
};
|
|
48
|
+
export declare function checkASI10(cwd: string, configFiles: string[]): {
|
|
49
|
+
findings: OwaspFinding[];
|
|
50
|
+
deduction: number;
|
|
51
|
+
};
|