neuronlayer 0.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/CONTRIBUTING.md +127 -0
- package/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/index.js +38016 -0
- package/esbuild.config.js +26 -0
- package/package.json +63 -0
- package/src/cli/commands.ts +382 -0
- package/src/core/adr-exporter.ts +253 -0
- package/src/core/architecture/architecture-enforcement.ts +228 -0
- package/src/core/architecture/duplicate-detector.ts +288 -0
- package/src/core/architecture/index.ts +6 -0
- package/src/core/architecture/pattern-learner.ts +306 -0
- package/src/core/architecture/pattern-library.ts +403 -0
- package/src/core/architecture/pattern-validator.ts +324 -0
- package/src/core/change-intelligence/bug-correlator.ts +444 -0
- package/src/core/change-intelligence/change-intelligence.ts +221 -0
- package/src/core/change-intelligence/change-tracker.ts +334 -0
- package/src/core/change-intelligence/fix-suggester.ts +340 -0
- package/src/core/change-intelligence/index.ts +5 -0
- package/src/core/code-verifier.ts +843 -0
- package/src/core/confidence/confidence-scorer.ts +251 -0
- package/src/core/confidence/conflict-checker.ts +289 -0
- package/src/core/confidence/index.ts +5 -0
- package/src/core/confidence/source-tracker.ts +263 -0
- package/src/core/confidence/warning-detector.ts +241 -0
- package/src/core/context-rot/compaction.ts +284 -0
- package/src/core/context-rot/context-health.ts +243 -0
- package/src/core/context-rot/context-rot-prevention.ts +213 -0
- package/src/core/context-rot/critical-context.ts +221 -0
- package/src/core/context-rot/drift-detector.ts +255 -0
- package/src/core/context-rot/index.ts +7 -0
- package/src/core/context.ts +263 -0
- package/src/core/decision-extractor.ts +339 -0
- package/src/core/decisions.ts +69 -0
- package/src/core/deja-vu.ts +421 -0
- package/src/core/engine.ts +1455 -0
- package/src/core/feature-context.ts +726 -0
- package/src/core/ghost-mode.ts +412 -0
- package/src/core/learning.ts +485 -0
- package/src/core/living-docs/activity-tracker.ts +296 -0
- package/src/core/living-docs/architecture-generator.ts +428 -0
- package/src/core/living-docs/changelog-generator.ts +348 -0
- package/src/core/living-docs/component-generator.ts +230 -0
- package/src/core/living-docs/doc-engine.ts +110 -0
- package/src/core/living-docs/doc-validator.ts +282 -0
- package/src/core/living-docs/index.ts +8 -0
- package/src/core/project-manager.ts +297 -0
- package/src/core/summarizer.ts +267 -0
- package/src/core/test-awareness/change-validator.ts +499 -0
- package/src/core/test-awareness/index.ts +5 -0
- package/src/index.ts +49 -0
- package/src/indexing/ast.ts +563 -0
- package/src/indexing/embeddings.ts +85 -0
- package/src/indexing/indexer.ts +245 -0
- package/src/indexing/watcher.ts +78 -0
- package/src/server/gateways/aggregator.ts +374 -0
- package/src/server/gateways/index.ts +473 -0
- package/src/server/gateways/memory-ghost.ts +343 -0
- package/src/server/gateways/memory-query.ts +452 -0
- package/src/server/gateways/memory-record.ts +346 -0
- package/src/server/gateways/memory-review.ts +410 -0
- package/src/server/gateways/memory-status.ts +517 -0
- package/src/server/gateways/memory-verify.ts +392 -0
- package/src/server/gateways/router.ts +434 -0
- package/src/server/gateways/types.ts +610 -0
- package/src/server/mcp.ts +154 -0
- package/src/server/resources.ts +85 -0
- package/src/server/tools.ts +2261 -0
- package/src/storage/database.ts +262 -0
- package/src/storage/tier1.ts +135 -0
- package/src/storage/tier2.ts +764 -0
- package/src/storage/tier3.ts +123 -0
- package/src/types/documentation.ts +619 -0
- package/src/types/index.ts +222 -0
- package/src/utils/config.ts +193 -0
- package/src/utils/files.ts +117 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/tokens.ts +52 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Verifier - Pre-Commit Quality Gate
|
|
3
|
+
*
|
|
4
|
+
* Verifies AI-generated code for common issues before commit:
|
|
5
|
+
* - Import verification: Do imports exist? Is the API being used correctly?
|
|
6
|
+
* - Security scan: Common vulnerability patterns
|
|
7
|
+
* - Dependency check: Is package in package.json? Is version compatible?
|
|
8
|
+
* - Pattern compliance: Does code follow project patterns?
|
|
9
|
+
* - Decision conflicts: Does code conflict with past decisions?
|
|
10
|
+
*
|
|
11
|
+
* Solves: Hallucination detection, Security vulnerabilities (1.7x more in AI code)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync } from 'fs';
|
|
15
|
+
import { join, dirname, extname } from 'path';
|
|
16
|
+
import type { PatternValidationResult, ConflictResult } from '../types/documentation.js';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface ImportVerification {
|
|
23
|
+
valid: boolean;
|
|
24
|
+
issues: ImportIssue[];
|
|
25
|
+
warnings: ImportWarning[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ImportIssue {
|
|
29
|
+
import: string;
|
|
30
|
+
type: 'missing_package' | 'missing_file' | 'invalid_export' | 'deprecated' | 'hallucinated';
|
|
31
|
+
message: string;
|
|
32
|
+
suggestion?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ImportWarning {
|
|
36
|
+
import: string;
|
|
37
|
+
type: 'outdated' | 'security' | 'deprecated_api';
|
|
38
|
+
message: string;
|
|
39
|
+
suggestion?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SecurityScanResult {
|
|
43
|
+
safe: boolean;
|
|
44
|
+
issues: SecurityIssue[];
|
|
45
|
+
score: number; // 0-100, 100 = safest
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SecurityIssue {
|
|
49
|
+
type: SecurityIssueType;
|
|
50
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
51
|
+
line?: number;
|
|
52
|
+
code?: string;
|
|
53
|
+
message: string;
|
|
54
|
+
cwe?: string;
|
|
55
|
+
suggestion?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type SecurityIssueType =
|
|
59
|
+
| 'sql_injection'
|
|
60
|
+
| 'xss'
|
|
61
|
+
| 'command_injection'
|
|
62
|
+
| 'path_traversal'
|
|
63
|
+
| 'hardcoded_secret'
|
|
64
|
+
| 'insecure_random'
|
|
65
|
+
| 'weak_crypto'
|
|
66
|
+
| 'prototype_pollution'
|
|
67
|
+
| 'regex_dos'
|
|
68
|
+
| 'unsafe_eval'
|
|
69
|
+
| 'insecure_deserialization'
|
|
70
|
+
| 'ssrf'
|
|
71
|
+
| 'open_redirect';
|
|
72
|
+
|
|
73
|
+
export interface DependencyCheckResult {
|
|
74
|
+
valid: boolean;
|
|
75
|
+
issues: DependencyIssue[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface DependencyIssue {
|
|
79
|
+
package: string;
|
|
80
|
+
type: 'not_installed' | 'version_mismatch' | 'deprecated' | 'vulnerable' | 'unlisted';
|
|
81
|
+
message: string;
|
|
82
|
+
suggestion?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface VerificationResult {
|
|
86
|
+
verdict: 'pass' | 'warning' | 'fail';
|
|
87
|
+
score: number; // 0-100, higher is better
|
|
88
|
+
imports?: ImportVerification;
|
|
89
|
+
security?: SecurityScanResult;
|
|
90
|
+
dependencies?: DependencyCheckResult;
|
|
91
|
+
patterns?: PatternValidationResult;
|
|
92
|
+
conflicts?: ConflictResult;
|
|
93
|
+
summary: string;
|
|
94
|
+
suggestions: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type VerificationCheck = 'imports' | 'security' | 'dependencies' | 'patterns' | 'all';
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Security Patterns (OWASP Top 10 focused)
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
interface SecurityPattern {
|
|
104
|
+
type: SecurityIssueType;
|
|
105
|
+
pattern: RegExp;
|
|
106
|
+
severity: SecurityIssue['severity'];
|
|
107
|
+
message: string;
|
|
108
|
+
cwe?: string;
|
|
109
|
+
suggestion?: string;
|
|
110
|
+
languages?: string[]; // Limit to specific languages
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const SECURITY_PATTERNS: SecurityPattern[] = [
|
|
114
|
+
// SQL Injection
|
|
115
|
+
{
|
|
116
|
+
type: 'sql_injection',
|
|
117
|
+
pattern: /\b(?:query|execute|raw|exec)\s*\(\s*[`'"].*\$\{|(?:\.query|\.execute)\s*\(\s*(?:['"`].*\+|\`.*\$\{)/i,
|
|
118
|
+
severity: 'critical',
|
|
119
|
+
message: 'Potential SQL injection: string interpolation in SQL query',
|
|
120
|
+
cwe: 'CWE-89',
|
|
121
|
+
suggestion: 'Use parameterized queries instead of string interpolation',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'sql_injection',
|
|
125
|
+
pattern: /\bSELECT\b.*\bFROM\b.*\bWHERE\b.*[\+\$\{]/i,
|
|
126
|
+
severity: 'high',
|
|
127
|
+
message: 'SQL query with dynamic values - verify parameterization',
|
|
128
|
+
cwe: 'CWE-89',
|
|
129
|
+
suggestion: 'Use parameterized queries with placeholders',
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// XSS
|
|
133
|
+
{
|
|
134
|
+
type: 'xss',
|
|
135
|
+
pattern: /innerHTML\s*=|outerHTML\s*=|document\.write\s*\(/,
|
|
136
|
+
severity: 'high',
|
|
137
|
+
message: 'Potential XSS: unsafe DOM manipulation',
|
|
138
|
+
cwe: 'CWE-79',
|
|
139
|
+
suggestion: 'Use textContent or a sanitization library',
|
|
140
|
+
languages: ['javascript', 'typescript', 'jsx', 'tsx'],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'xss',
|
|
144
|
+
pattern: /dangerouslySetInnerHTML\s*=/,
|
|
145
|
+
severity: 'high',
|
|
146
|
+
message: 'dangerouslySetInnerHTML can lead to XSS',
|
|
147
|
+
cwe: 'CWE-79',
|
|
148
|
+
suggestion: 'Ensure content is sanitized before use',
|
|
149
|
+
languages: ['jsx', 'tsx'],
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// Command Injection
|
|
153
|
+
{
|
|
154
|
+
type: 'command_injection',
|
|
155
|
+
pattern: /\bexec\s*\(\s*(?:[`'"].*\$\{|\`.*\$\{|['"].*\+)/,
|
|
156
|
+
severity: 'critical',
|
|
157
|
+
message: 'Potential command injection: dynamic command execution',
|
|
158
|
+
cwe: 'CWE-78',
|
|
159
|
+
suggestion: 'Use execFile with argument array instead of exec with string',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: 'command_injection',
|
|
163
|
+
pattern: /child_process.*\bexec(?:Sync)?\s*\(/,
|
|
164
|
+
severity: 'medium',
|
|
165
|
+
message: 'Command execution detected - verify input validation',
|
|
166
|
+
cwe: 'CWE-78',
|
|
167
|
+
suggestion: 'Prefer execFile/spawn with argument arrays',
|
|
168
|
+
languages: ['javascript', 'typescript'],
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// Path Traversal
|
|
172
|
+
{
|
|
173
|
+
type: 'path_traversal',
|
|
174
|
+
pattern: /\bpath\.join\s*\([^)]*(?:req\.|input|params|query|body)/i,
|
|
175
|
+
severity: 'high',
|
|
176
|
+
message: 'Potential path traversal: user input in file path',
|
|
177
|
+
cwe: 'CWE-22',
|
|
178
|
+
suggestion: 'Validate and sanitize file paths, use path.resolve and verify against base directory',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: 'path_traversal',
|
|
182
|
+
pattern: /\breadFileSync\s*\([^)]*(?:\+|`)/,
|
|
183
|
+
severity: 'medium',
|
|
184
|
+
message: 'Dynamic file path in file read - verify path validation',
|
|
185
|
+
cwe: 'CWE-22',
|
|
186
|
+
suggestion: 'Ensure path is validated against a whitelist or base directory',
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// Hardcoded Secrets
|
|
190
|
+
{
|
|
191
|
+
type: 'hardcoded_secret',
|
|
192
|
+
pattern: /(?:password|secret|api[_-]?key|token|auth|credential)s?\s*[=:]\s*['"][^'"]{8,}['"]/i,
|
|
193
|
+
severity: 'critical',
|
|
194
|
+
message: 'Hardcoded secret detected',
|
|
195
|
+
cwe: 'CWE-798',
|
|
196
|
+
suggestion: 'Use environment variables or a secrets manager',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'hardcoded_secret',
|
|
200
|
+
pattern: /(?:AWS|AZURE|GCP|GITHUB|STRIPE|TWILIO)[_-]?(?:SECRET|KEY|TOKEN)\s*[=:]\s*['"][^'"]+['"]/i,
|
|
201
|
+
severity: 'critical',
|
|
202
|
+
message: 'Hardcoded cloud/service credential',
|
|
203
|
+
cwe: 'CWE-798',
|
|
204
|
+
suggestion: 'Use environment variables or a secrets manager',
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
// Insecure Random
|
|
208
|
+
{
|
|
209
|
+
type: 'insecure_random',
|
|
210
|
+
pattern: /Math\.random\s*\(\s*\)/,
|
|
211
|
+
severity: 'medium',
|
|
212
|
+
message: 'Math.random() is not cryptographically secure',
|
|
213
|
+
cwe: 'CWE-330',
|
|
214
|
+
suggestion: 'Use crypto.randomBytes() or crypto.getRandomValues() for security-sensitive operations',
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// Weak Crypto
|
|
218
|
+
{
|
|
219
|
+
type: 'weak_crypto',
|
|
220
|
+
pattern: /createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/i,
|
|
221
|
+
severity: 'medium',
|
|
222
|
+
message: 'Weak hash algorithm (MD5/SHA1)',
|
|
223
|
+
cwe: 'CWE-328',
|
|
224
|
+
suggestion: 'Use SHA-256 or stronger for security purposes',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
type: 'weak_crypto',
|
|
228
|
+
pattern: /createCipher\s*\(/,
|
|
229
|
+
severity: 'high',
|
|
230
|
+
message: 'Deprecated createCipher method',
|
|
231
|
+
cwe: 'CWE-327',
|
|
232
|
+
suggestion: 'Use createCipheriv with proper IV handling',
|
|
233
|
+
languages: ['javascript', 'typescript'],
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// Prototype Pollution
|
|
237
|
+
{
|
|
238
|
+
type: 'prototype_pollution',
|
|
239
|
+
pattern: /\[['"]__proto__['"]\]|\[['"]constructor['"]\]|\[['"]prototype['"]\]/,
|
|
240
|
+
severity: 'high',
|
|
241
|
+
message: 'Potential prototype pollution vulnerability',
|
|
242
|
+
cwe: 'CWE-1321',
|
|
243
|
+
suggestion: 'Validate object keys before assignment',
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// Regex DoS
|
|
247
|
+
{
|
|
248
|
+
type: 'regex_dos',
|
|
249
|
+
pattern: /new\s+RegExp\s*\([^)]*(?:\+|`|\$)/,
|
|
250
|
+
severity: 'medium',
|
|
251
|
+
message: 'Dynamic regex with user input - potential ReDoS',
|
|
252
|
+
cwe: 'CWE-1333',
|
|
253
|
+
suggestion: 'Validate and escape user input in regex patterns',
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// Unsafe eval
|
|
257
|
+
{
|
|
258
|
+
type: 'unsafe_eval',
|
|
259
|
+
pattern: /\beval\s*\(|\bnew\s+Function\s*\(/,
|
|
260
|
+
severity: 'high',
|
|
261
|
+
message: 'Use of eval/Function constructor - potential code injection',
|
|
262
|
+
cwe: 'CWE-95',
|
|
263
|
+
suggestion: 'Avoid eval; use JSON.parse for data or safer alternatives',
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// SSRF
|
|
267
|
+
{
|
|
268
|
+
type: 'ssrf',
|
|
269
|
+
pattern: /\b(?:fetch|axios|request|http\.get)\s*\([^)]*(?:\+|`.*\$\{|req\.|input|params)/i,
|
|
270
|
+
severity: 'high',
|
|
271
|
+
message: 'Potential SSRF: user-controlled URL',
|
|
272
|
+
cwe: 'CWE-918',
|
|
273
|
+
suggestion: 'Validate and whitelist allowed URLs/hosts',
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// Open Redirect
|
|
277
|
+
{
|
|
278
|
+
type: 'open_redirect',
|
|
279
|
+
pattern: /\b(?:res\.redirect|location\.href|window\.location)\s*[=\(]\s*(?:req\.|input|params)/i,
|
|
280
|
+
severity: 'medium',
|
|
281
|
+
message: 'Potential open redirect vulnerability',
|
|
282
|
+
cwe: 'CWE-601',
|
|
283
|
+
suggestion: 'Validate redirect URLs against a whitelist',
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// Import Patterns
|
|
289
|
+
// ============================================================================
|
|
290
|
+
|
|
291
|
+
// Common Node.js built-in modules
|
|
292
|
+
const NODE_BUILTINS = new Set([
|
|
293
|
+
'assert', 'buffer', 'child_process', 'cluster', 'console', 'constants',
|
|
294
|
+
'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2',
|
|
295
|
+
'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks',
|
|
296
|
+
'process', 'punycode', 'querystring', 'readline', 'repl', 'stream',
|
|
297
|
+
'string_decoder', 'timers', 'tls', 'trace_events', 'tty', 'url',
|
|
298
|
+
'util', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
|
|
299
|
+
// Node.js prefixed versions
|
|
300
|
+
'node:assert', 'node:buffer', 'node:child_process', 'node:cluster',
|
|
301
|
+
'node:crypto', 'node:dns', 'node:events', 'node:fs', 'node:http',
|
|
302
|
+
'node:http2', 'node:https', 'node:net', 'node:os', 'node:path',
|
|
303
|
+
'node:process', 'node:querystring', 'node:readline', 'node:stream',
|
|
304
|
+
'node:timers', 'node:tls', 'node:url', 'node:util', 'node:v8',
|
|
305
|
+
'node:vm', 'node:worker_threads', 'node:zlib',
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
// Import extraction patterns
|
|
309
|
+
const IMPORT_PATTERNS = {
|
|
310
|
+
// ES6 imports
|
|
311
|
+
esImport: /import\s+(?:(?:\{[^}]+\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]+\}|\*\s+as\s+\w+|\w+))*)\s+from\s+['"]([^'"]+)['"]/g,
|
|
312
|
+
// Dynamic imports
|
|
313
|
+
dynamicImport: /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
314
|
+
// CommonJS require
|
|
315
|
+
require: /(?:const|let|var)\s+(?:\{[^}]+\}|\w+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// Code Verifier Class
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
export class CodeVerifier {
|
|
323
|
+
private projectPath: string;
|
|
324
|
+
private packageJson: Record<string, unknown> | null = null;
|
|
325
|
+
private nodeModulesPath: string;
|
|
326
|
+
|
|
327
|
+
constructor(projectPath: string) {
|
|
328
|
+
this.projectPath = projectPath;
|
|
329
|
+
this.nodeModulesPath = join(projectPath, 'node_modules');
|
|
330
|
+
this.loadPackageJson();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private loadPackageJson(): void {
|
|
334
|
+
const packageJsonPath = join(this.projectPath, 'package.json');
|
|
335
|
+
if (existsSync(packageJsonPath)) {
|
|
336
|
+
try {
|
|
337
|
+
this.packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
338
|
+
} catch {
|
|
339
|
+
this.packageJson = null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Run full verification on code
|
|
346
|
+
*/
|
|
347
|
+
async verify(
|
|
348
|
+
code: string,
|
|
349
|
+
file?: string,
|
|
350
|
+
checks: VerificationCheck[] = ['all']
|
|
351
|
+
): Promise<VerificationResult> {
|
|
352
|
+
const runAll = checks.includes('all');
|
|
353
|
+
const results: Partial<VerificationResult> = {};
|
|
354
|
+
const suggestions: string[] = [];
|
|
355
|
+
let totalScore = 100;
|
|
356
|
+
|
|
357
|
+
// Detect language from file extension or code patterns
|
|
358
|
+
const language = file ? this.detectLanguage(file) : this.detectLanguageFromCode(code);
|
|
359
|
+
|
|
360
|
+
// Run requested checks
|
|
361
|
+
if (runAll || checks.includes('imports')) {
|
|
362
|
+
results.imports = this.verifyImports(code, file);
|
|
363
|
+
if (!results.imports.valid) {
|
|
364
|
+
totalScore -= results.imports.issues.length * 15;
|
|
365
|
+
suggestions.push(...results.imports.issues.map(i => i.suggestion || i.message));
|
|
366
|
+
}
|
|
367
|
+
if (results.imports.warnings.length > 0) {
|
|
368
|
+
totalScore -= results.imports.warnings.length * 5;
|
|
369
|
+
suggestions.push(...results.imports.warnings.map(w => w.suggestion || w.message));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (runAll || checks.includes('security')) {
|
|
374
|
+
results.security = this.scanSecurity(code, language);
|
|
375
|
+
totalScore = Math.min(totalScore, results.security.score);
|
|
376
|
+
if (!results.security.safe) {
|
|
377
|
+
suggestions.push(...results.security.issues.map(i => i.suggestion || i.message));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (runAll || checks.includes('dependencies')) {
|
|
382
|
+
results.dependencies = this.checkDependencies(code);
|
|
383
|
+
if (!results.dependencies.valid) {
|
|
384
|
+
totalScore -= results.dependencies.issues.length * 10;
|
|
385
|
+
suggestions.push(...results.dependencies.issues.map(d => d.suggestion || d.message));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Calculate verdict
|
|
390
|
+
const verdict = this.calculateVerdict(totalScore, results);
|
|
391
|
+
|
|
392
|
+
// Build summary
|
|
393
|
+
const summary = this.buildSummary(results, verdict);
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
verdict,
|
|
397
|
+
score: Math.max(0, Math.min(100, totalScore)),
|
|
398
|
+
...results,
|
|
399
|
+
summary,
|
|
400
|
+
suggestions: [...new Set(suggestions)].slice(0, 10), // Dedupe and limit
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Verify imports in code
|
|
406
|
+
*/
|
|
407
|
+
verifyImports(code: string, file?: string): ImportVerification {
|
|
408
|
+
const issues: ImportIssue[] = [];
|
|
409
|
+
const warnings: ImportWarning[] = [];
|
|
410
|
+
const imports = this.extractImports(code);
|
|
411
|
+
|
|
412
|
+
for (const importPath of imports) {
|
|
413
|
+
// Skip built-in modules
|
|
414
|
+
if (NODE_BUILTINS.has(importPath) || NODE_BUILTINS.has(importPath.split('/')[0] || '')) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Check if it's a relative import
|
|
419
|
+
if (importPath.startsWith('.') || importPath.startsWith('/')) {
|
|
420
|
+
this.verifyRelativeImport(importPath, file, issues);
|
|
421
|
+
} else {
|
|
422
|
+
// It's a package import
|
|
423
|
+
this.verifyPackageImport(importPath, issues, warnings);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
valid: issues.length === 0,
|
|
429
|
+
issues,
|
|
430
|
+
warnings,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Scan code for security vulnerabilities
|
|
436
|
+
*/
|
|
437
|
+
scanSecurity(code: string, language?: string): SecurityScanResult {
|
|
438
|
+
const issues: SecurityIssue[] = [];
|
|
439
|
+
const lines = code.split('\n');
|
|
440
|
+
|
|
441
|
+
for (const pattern of SECURITY_PATTERNS) {
|
|
442
|
+
// Skip patterns not applicable to this language
|
|
443
|
+
if (pattern.languages && language && !pattern.languages.includes(language)) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Check each line
|
|
448
|
+
for (let i = 0; i < lines.length; i++) {
|
|
449
|
+
const line = lines[i];
|
|
450
|
+
if (line && pattern.pattern.test(line)) {
|
|
451
|
+
issues.push({
|
|
452
|
+
type: pattern.type,
|
|
453
|
+
severity: pattern.severity,
|
|
454
|
+
line: i + 1,
|
|
455
|
+
code: line.trim().slice(0, 100),
|
|
456
|
+
message: pattern.message,
|
|
457
|
+
cwe: pattern.cwe,
|
|
458
|
+
suggestion: pattern.suggestion,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Also check the full code for multi-line patterns
|
|
464
|
+
if (pattern.pattern.test(code)) {
|
|
465
|
+
const existingIssue = issues.find(i => i.type === pattern.type && i.message === pattern.message);
|
|
466
|
+
if (!existingIssue) {
|
|
467
|
+
issues.push({
|
|
468
|
+
type: pattern.type,
|
|
469
|
+
severity: pattern.severity,
|
|
470
|
+
message: pattern.message,
|
|
471
|
+
cwe: pattern.cwe,
|
|
472
|
+
suggestion: pattern.suggestion,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Calculate score
|
|
479
|
+
let score = 100;
|
|
480
|
+
for (const issue of issues) {
|
|
481
|
+
switch (issue.severity) {
|
|
482
|
+
case 'critical':
|
|
483
|
+
score -= 30;
|
|
484
|
+
break;
|
|
485
|
+
case 'high':
|
|
486
|
+
score -= 20;
|
|
487
|
+
break;
|
|
488
|
+
case 'medium':
|
|
489
|
+
score -= 10;
|
|
490
|
+
break;
|
|
491
|
+
case 'low':
|
|
492
|
+
score -= 5;
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
safe: issues.length === 0,
|
|
499
|
+
issues,
|
|
500
|
+
score: Math.max(0, score),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Check dependencies used in code
|
|
506
|
+
*/
|
|
507
|
+
checkDependencies(code: string): DependencyCheckResult {
|
|
508
|
+
const issues: DependencyIssue[] = [];
|
|
509
|
+
const imports = this.extractImports(code);
|
|
510
|
+
const packages = this.getPackageDependencies();
|
|
511
|
+
|
|
512
|
+
for (const importPath of imports) {
|
|
513
|
+
// Skip relative imports and built-ins
|
|
514
|
+
if (importPath.startsWith('.') || importPath.startsWith('/')) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (NODE_BUILTINS.has(importPath) || NODE_BUILTINS.has(importPath.split('/')[0] || '')) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Get package name (handle scoped packages)
|
|
522
|
+
const packageName = this.getPackageName(importPath);
|
|
523
|
+
|
|
524
|
+
// Check if package is in package.json
|
|
525
|
+
if (!packages.has(packageName)) {
|
|
526
|
+
// Check if it's installed in node_modules even if not in package.json
|
|
527
|
+
const nodeModulePath = join(this.nodeModulesPath, packageName);
|
|
528
|
+
if (existsSync(nodeModulePath)) {
|
|
529
|
+
issues.push({
|
|
530
|
+
package: packageName,
|
|
531
|
+
type: 'unlisted',
|
|
532
|
+
message: `Package "${packageName}" is installed but not listed in package.json`,
|
|
533
|
+
suggestion: `Add "${packageName}" to dependencies with: npm install ${packageName}`,
|
|
534
|
+
});
|
|
535
|
+
} else {
|
|
536
|
+
issues.push({
|
|
537
|
+
package: packageName,
|
|
538
|
+
type: 'not_installed',
|
|
539
|
+
message: `Package "${packageName}" is not installed`,
|
|
540
|
+
suggestion: `Install with: npm install ${packageName}`,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
valid: issues.length === 0,
|
|
548
|
+
issues,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ========== Helper Methods ==========
|
|
553
|
+
|
|
554
|
+
private extractImports(code: string): Set<string> {
|
|
555
|
+
const imports = new Set<string>();
|
|
556
|
+
|
|
557
|
+
// ES6 imports
|
|
558
|
+
let match;
|
|
559
|
+
const esPattern = new RegExp(IMPORT_PATTERNS.esImport.source, 'g');
|
|
560
|
+
while ((match = esPattern.exec(code)) !== null) {
|
|
561
|
+
if (match[1]) imports.add(match[1]);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Dynamic imports
|
|
565
|
+
const dynamicPattern = new RegExp(IMPORT_PATTERNS.dynamicImport.source, 'g');
|
|
566
|
+
while ((match = dynamicPattern.exec(code)) !== null) {
|
|
567
|
+
if (match[1]) imports.add(match[1]);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// CommonJS require
|
|
571
|
+
const requirePattern = new RegExp(IMPORT_PATTERNS.require.source, 'g');
|
|
572
|
+
while ((match = requirePattern.exec(code)) !== null) {
|
|
573
|
+
if (match[1]) imports.add(match[1]);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return imports;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private verifyRelativeImport(importPath: string, file: string | undefined, issues: ImportIssue[]): void {
|
|
580
|
+
if (!file) return;
|
|
581
|
+
|
|
582
|
+
// Try to resolve the import
|
|
583
|
+
const baseDir = dirname(join(this.projectPath, file));
|
|
584
|
+
const possibleExtensions = ['.ts', '.tsx', '.js', '.jsx', '.json', ''];
|
|
585
|
+
const possibleIndexes = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
|
|
586
|
+
|
|
587
|
+
let found = false;
|
|
588
|
+
|
|
589
|
+
for (const ext of possibleExtensions) {
|
|
590
|
+
const fullPath = join(baseDir, importPath + ext);
|
|
591
|
+
if (existsSync(fullPath)) {
|
|
592
|
+
found = true;
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Check for directory with index file
|
|
598
|
+
if (!found) {
|
|
599
|
+
const dirPath = join(baseDir, importPath);
|
|
600
|
+
if (existsSync(dirPath)) {
|
|
601
|
+
for (const indexFile of possibleIndexes) {
|
|
602
|
+
if (existsSync(join(dirPath, indexFile))) {
|
|
603
|
+
found = true;
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (!found) {
|
|
611
|
+
issues.push({
|
|
612
|
+
import: importPath,
|
|
613
|
+
type: 'missing_file',
|
|
614
|
+
message: `Cannot resolve import "${importPath}" - file not found`,
|
|
615
|
+
suggestion: `Check if the file exists or if the path is correct`,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
private verifyPackageImport(importPath: string, issues: ImportIssue[], warnings: ImportWarning[]): void {
|
|
621
|
+
const packageName = this.getPackageName(importPath);
|
|
622
|
+
const nodeModulePath = join(this.nodeModulesPath, packageName);
|
|
623
|
+
|
|
624
|
+
if (!existsSync(nodeModulePath)) {
|
|
625
|
+
issues.push({
|
|
626
|
+
import: importPath,
|
|
627
|
+
type: 'missing_package',
|
|
628
|
+
message: `Package "${packageName}" is not installed`,
|
|
629
|
+
suggestion: `Install with: npm install ${packageName}`,
|
|
630
|
+
});
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// If importing a subpath, check if it exists
|
|
635
|
+
if (importPath !== packageName) {
|
|
636
|
+
const subpath = importPath.slice(packageName.length + 1);
|
|
637
|
+
const subpathFull = join(nodeModulePath, subpath);
|
|
638
|
+
const possibleExtensions = ['.js', '.json', ''];
|
|
639
|
+
|
|
640
|
+
let found = false;
|
|
641
|
+
for (const ext of possibleExtensions) {
|
|
642
|
+
if (existsSync(subpathFull + ext)) {
|
|
643
|
+
found = true;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Check package.json exports
|
|
649
|
+
const pkgJsonPath = join(nodeModulePath, 'package.json');
|
|
650
|
+
if (!found && existsSync(pkgJsonPath)) {
|
|
651
|
+
try {
|
|
652
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
653
|
+
if (pkgJson.exports) {
|
|
654
|
+
// Package has exports field - subpath might be valid
|
|
655
|
+
const exportKey = './' + subpath;
|
|
656
|
+
if (pkgJson.exports[exportKey] || pkgJson.exports['./' + subpath + '.js']) {
|
|
657
|
+
found = true;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
} catch {
|
|
661
|
+
// Ignore parse errors
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (!found) {
|
|
666
|
+
issues.push({
|
|
667
|
+
import: importPath,
|
|
668
|
+
type: 'invalid_export',
|
|
669
|
+
message: `Subpath "${subpath}" not found in package "${packageName}"`,
|
|
670
|
+
suggestion: `Check package documentation for correct import path`,
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private getPackageName(importPath: string): string {
|
|
677
|
+
// Handle scoped packages (@org/package)
|
|
678
|
+
if (importPath.startsWith('@')) {
|
|
679
|
+
const parts = importPath.split('/');
|
|
680
|
+
return parts.slice(0, 2).join('/');
|
|
681
|
+
}
|
|
682
|
+
// Regular package
|
|
683
|
+
return importPath.split('/')[0] || importPath;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
private getPackageDependencies(): Set<string> {
|
|
687
|
+
const deps = new Set<string>();
|
|
688
|
+
|
|
689
|
+
if (!this.packageJson) return deps;
|
|
690
|
+
|
|
691
|
+
const allDeps = {
|
|
692
|
+
...(this.packageJson.dependencies as Record<string, string> || {}),
|
|
693
|
+
...(this.packageJson.devDependencies as Record<string, string> || {}),
|
|
694
|
+
...(this.packageJson.peerDependencies as Record<string, string> || {}),
|
|
695
|
+
...(this.packageJson.optionalDependencies as Record<string, string> || {}),
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
for (const pkg of Object.keys(allDeps)) {
|
|
699
|
+
deps.add(pkg);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return deps;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private detectLanguage(file: string): string {
|
|
706
|
+
const ext = extname(file).toLowerCase();
|
|
707
|
+
const langMap: Record<string, string> = {
|
|
708
|
+
'.ts': 'typescript',
|
|
709
|
+
'.tsx': 'tsx',
|
|
710
|
+
'.js': 'javascript',
|
|
711
|
+
'.jsx': 'jsx',
|
|
712
|
+
'.py': 'python',
|
|
713
|
+
'.rb': 'ruby',
|
|
714
|
+
'.go': 'go',
|
|
715
|
+
'.rs': 'rust',
|
|
716
|
+
'.java': 'java',
|
|
717
|
+
'.cpp': 'cpp',
|
|
718
|
+
'.c': 'c',
|
|
719
|
+
'.cs': 'csharp',
|
|
720
|
+
'.php': 'php',
|
|
721
|
+
};
|
|
722
|
+
return langMap[ext] || 'unknown';
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private detectLanguageFromCode(code: string): string {
|
|
726
|
+
// Simple heuristics
|
|
727
|
+
if (/import\s+.*from\s+['"]|export\s+(default\s+)?/m.test(code)) {
|
|
728
|
+
return code.includes('React') || code.includes('tsx') || /<\w+.*\/?>/.test(code)
|
|
729
|
+
? 'tsx'
|
|
730
|
+
: 'typescript';
|
|
731
|
+
}
|
|
732
|
+
if (/require\s*\(|module\.exports/.test(code)) {
|
|
733
|
+
return 'javascript';
|
|
734
|
+
}
|
|
735
|
+
if (/def\s+\w+\s*\(|import\s+\w+|from\s+\w+\s+import/.test(code)) {
|
|
736
|
+
return 'python';
|
|
737
|
+
}
|
|
738
|
+
if (/func\s+\w+\s*\(|package\s+\w+/.test(code)) {
|
|
739
|
+
return 'go';
|
|
740
|
+
}
|
|
741
|
+
return 'unknown';
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private calculateVerdict(
|
|
745
|
+
score: number,
|
|
746
|
+
results: Partial<VerificationResult>
|
|
747
|
+
): 'pass' | 'warning' | 'fail' {
|
|
748
|
+
// Critical security issues = fail
|
|
749
|
+
if (results.security?.issues.some(i => i.severity === 'critical')) {
|
|
750
|
+
return 'fail';
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Multiple high severity issues = fail
|
|
754
|
+
const highSeverityCount = (results.security?.issues.filter(i => i.severity === 'high').length || 0) +
|
|
755
|
+
(results.imports?.issues.filter(i => i.type === 'hallucinated').length || 0);
|
|
756
|
+
if (highSeverityCount >= 2) {
|
|
757
|
+
return 'fail';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Score-based
|
|
761
|
+
if (score >= 70) return 'pass';
|
|
762
|
+
if (score >= 40) return 'warning';
|
|
763
|
+
return 'fail';
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private buildSummary(results: Partial<VerificationResult>, verdict: 'pass' | 'warning' | 'fail'): string {
|
|
767
|
+
const parts: string[] = [];
|
|
768
|
+
|
|
769
|
+
if (results.imports) {
|
|
770
|
+
if (results.imports.valid && results.imports.warnings.length === 0) {
|
|
771
|
+
parts.push('Imports: OK');
|
|
772
|
+
} else {
|
|
773
|
+
parts.push(`Imports: ${results.imports.issues.length} issues, ${results.imports.warnings.length} warnings`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (results.security) {
|
|
778
|
+
if (results.security.safe) {
|
|
779
|
+
parts.push('Security: OK');
|
|
780
|
+
} else {
|
|
781
|
+
const critical = results.security.issues.filter(i => i.severity === 'critical').length;
|
|
782
|
+
const high = results.security.issues.filter(i => i.severity === 'high').length;
|
|
783
|
+
parts.push(`Security: ${critical} critical, ${high} high severity issues`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (results.dependencies) {
|
|
788
|
+
if (results.dependencies.valid) {
|
|
789
|
+
parts.push('Dependencies: OK');
|
|
790
|
+
} else {
|
|
791
|
+
parts.push(`Dependencies: ${results.dependencies.issues.length} issues`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return `[${verdict.toUpperCase()}] ${parts.join(' | ')}`;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Quick security scan (standalone function for convenience)
|
|
801
|
+
*/
|
|
802
|
+
export function quickSecurityScan(code: string, language?: string): SecurityScanResult {
|
|
803
|
+
const issues: SecurityIssue[] = [];
|
|
804
|
+
const lines = code.split('\n');
|
|
805
|
+
|
|
806
|
+
for (const pattern of SECURITY_PATTERNS) {
|
|
807
|
+
if (pattern.languages && language && !pattern.languages.includes(language)) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
for (let i = 0; i < lines.length; i++) {
|
|
812
|
+
const line = lines[i];
|
|
813
|
+
if (line && pattern.pattern.test(line)) {
|
|
814
|
+
issues.push({
|
|
815
|
+
type: pattern.type,
|
|
816
|
+
severity: pattern.severity,
|
|
817
|
+
line: i + 1,
|
|
818
|
+
code: line.trim().slice(0, 100),
|
|
819
|
+
message: pattern.message,
|
|
820
|
+
cwe: pattern.cwe,
|
|
821
|
+
suggestion: pattern.suggestion,
|
|
822
|
+
});
|
|
823
|
+
break; // One issue per pattern
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
let score = 100;
|
|
829
|
+
for (const issue of issues) {
|
|
830
|
+
switch (issue.severity) {
|
|
831
|
+
case 'critical': score -= 30; break;
|
|
832
|
+
case 'high': score -= 20; break;
|
|
833
|
+
case 'medium': score -= 10; break;
|
|
834
|
+
case 'low': score -= 5; break;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
safe: issues.length === 0,
|
|
840
|
+
issues,
|
|
841
|
+
score: Math.max(0, score),
|
|
842
|
+
};
|
|
843
|
+
}
|