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.
Files changed (78) hide show
  1. package/CONTRIBUTING.md +127 -0
  2. package/LICENSE +21 -0
  3. package/README.md +305 -0
  4. package/dist/index.js +38016 -0
  5. package/esbuild.config.js +26 -0
  6. package/package.json +63 -0
  7. package/src/cli/commands.ts +382 -0
  8. package/src/core/adr-exporter.ts +253 -0
  9. package/src/core/architecture/architecture-enforcement.ts +228 -0
  10. package/src/core/architecture/duplicate-detector.ts +288 -0
  11. package/src/core/architecture/index.ts +6 -0
  12. package/src/core/architecture/pattern-learner.ts +306 -0
  13. package/src/core/architecture/pattern-library.ts +403 -0
  14. package/src/core/architecture/pattern-validator.ts +324 -0
  15. package/src/core/change-intelligence/bug-correlator.ts +444 -0
  16. package/src/core/change-intelligence/change-intelligence.ts +221 -0
  17. package/src/core/change-intelligence/change-tracker.ts +334 -0
  18. package/src/core/change-intelligence/fix-suggester.ts +340 -0
  19. package/src/core/change-intelligence/index.ts +5 -0
  20. package/src/core/code-verifier.ts +843 -0
  21. package/src/core/confidence/confidence-scorer.ts +251 -0
  22. package/src/core/confidence/conflict-checker.ts +289 -0
  23. package/src/core/confidence/index.ts +5 -0
  24. package/src/core/confidence/source-tracker.ts +263 -0
  25. package/src/core/confidence/warning-detector.ts +241 -0
  26. package/src/core/context-rot/compaction.ts +284 -0
  27. package/src/core/context-rot/context-health.ts +243 -0
  28. package/src/core/context-rot/context-rot-prevention.ts +213 -0
  29. package/src/core/context-rot/critical-context.ts +221 -0
  30. package/src/core/context-rot/drift-detector.ts +255 -0
  31. package/src/core/context-rot/index.ts +7 -0
  32. package/src/core/context.ts +263 -0
  33. package/src/core/decision-extractor.ts +339 -0
  34. package/src/core/decisions.ts +69 -0
  35. package/src/core/deja-vu.ts +421 -0
  36. package/src/core/engine.ts +1455 -0
  37. package/src/core/feature-context.ts +726 -0
  38. package/src/core/ghost-mode.ts +412 -0
  39. package/src/core/learning.ts +485 -0
  40. package/src/core/living-docs/activity-tracker.ts +296 -0
  41. package/src/core/living-docs/architecture-generator.ts +428 -0
  42. package/src/core/living-docs/changelog-generator.ts +348 -0
  43. package/src/core/living-docs/component-generator.ts +230 -0
  44. package/src/core/living-docs/doc-engine.ts +110 -0
  45. package/src/core/living-docs/doc-validator.ts +282 -0
  46. package/src/core/living-docs/index.ts +8 -0
  47. package/src/core/project-manager.ts +297 -0
  48. package/src/core/summarizer.ts +267 -0
  49. package/src/core/test-awareness/change-validator.ts +499 -0
  50. package/src/core/test-awareness/index.ts +5 -0
  51. package/src/index.ts +49 -0
  52. package/src/indexing/ast.ts +563 -0
  53. package/src/indexing/embeddings.ts +85 -0
  54. package/src/indexing/indexer.ts +245 -0
  55. package/src/indexing/watcher.ts +78 -0
  56. package/src/server/gateways/aggregator.ts +374 -0
  57. package/src/server/gateways/index.ts +473 -0
  58. package/src/server/gateways/memory-ghost.ts +343 -0
  59. package/src/server/gateways/memory-query.ts +452 -0
  60. package/src/server/gateways/memory-record.ts +346 -0
  61. package/src/server/gateways/memory-review.ts +410 -0
  62. package/src/server/gateways/memory-status.ts +517 -0
  63. package/src/server/gateways/memory-verify.ts +392 -0
  64. package/src/server/gateways/router.ts +434 -0
  65. package/src/server/gateways/types.ts +610 -0
  66. package/src/server/mcp.ts +154 -0
  67. package/src/server/resources.ts +85 -0
  68. package/src/server/tools.ts +2261 -0
  69. package/src/storage/database.ts +262 -0
  70. package/src/storage/tier1.ts +135 -0
  71. package/src/storage/tier2.ts +764 -0
  72. package/src/storage/tier3.ts +123 -0
  73. package/src/types/documentation.ts +619 -0
  74. package/src/types/index.ts +222 -0
  75. package/src/utils/config.ts +193 -0
  76. package/src/utils/files.ts +117 -0
  77. package/src/utils/time.ts +37 -0
  78. 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
+ }