@rigour-labs/core 3.0.4 → 3.0.6

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 (47) hide show
  1. package/dist/gates/deprecated-apis-rules-lang.d.ts +21 -0
  2. package/dist/gates/deprecated-apis-rules-lang.js +311 -0
  3. package/dist/gates/deprecated-apis-rules-node.d.ts +19 -0
  4. package/dist/gates/deprecated-apis-rules-node.js +199 -0
  5. package/dist/gates/deprecated-apis-rules.d.ts +6 -0
  6. package/dist/gates/deprecated-apis-rules.js +6 -0
  7. package/dist/gates/deprecated-apis.js +1 -502
  8. package/dist/gates/hallucinated-imports-lang.d.ts +16 -0
  9. package/dist/gates/hallucinated-imports-lang.js +374 -0
  10. package/dist/gates/hallucinated-imports-stdlib.d.ts +12 -0
  11. package/dist/gates/hallucinated-imports-stdlib.js +228 -0
  12. package/dist/gates/hallucinated-imports.d.ts +0 -98
  13. package/dist/gates/hallucinated-imports.js +10 -678
  14. package/dist/gates/phantom-apis-data.d.ts +33 -0
  15. package/dist/gates/phantom-apis-data.js +398 -0
  16. package/dist/gates/phantom-apis.js +1 -393
  17. package/dist/gates/phantom-apis.test.js +52 -0
  18. package/dist/gates/promise-safety-helpers.d.ts +19 -0
  19. package/dist/gates/promise-safety-helpers.js +101 -0
  20. package/dist/gates/promise-safety-rules.d.ts +7 -0
  21. package/dist/gates/promise-safety-rules.js +19 -0
  22. package/dist/gates/promise-safety.d.ts +1 -21
  23. package/dist/gates/promise-safety.js +51 -257
  24. package/dist/gates/test-quality-lang.d.ts +30 -0
  25. package/dist/gates/test-quality-lang.js +188 -0
  26. package/dist/gates/test-quality.d.ts +0 -14
  27. package/dist/gates/test-quality.js +13 -186
  28. package/dist/pattern-index/indexer-helpers.d.ts +38 -0
  29. package/dist/pattern-index/indexer-helpers.js +111 -0
  30. package/dist/pattern-index/indexer-lang.d.ts +13 -0
  31. package/dist/pattern-index/indexer-lang.js +244 -0
  32. package/dist/pattern-index/indexer-ts.d.ts +22 -0
  33. package/dist/pattern-index/indexer-ts.js +258 -0
  34. package/dist/pattern-index/indexer.d.ts +4 -106
  35. package/dist/pattern-index/indexer.js +58 -707
  36. package/dist/pattern-index/staleness-data.d.ts +6 -0
  37. package/dist/pattern-index/staleness-data.js +262 -0
  38. package/dist/pattern-index/staleness.js +1 -258
  39. package/dist/templates/index.d.ts +12 -16
  40. package/dist/templates/index.js +11 -527
  41. package/dist/templates/paradigms.d.ts +2 -0
  42. package/dist/templates/paradigms.js +46 -0
  43. package/dist/templates/presets.d.ts +14 -0
  44. package/dist/templates/presets.js +227 -0
  45. package/dist/templates/universal-config.d.ts +2 -0
  46. package/dist/templates/universal-config.js +171 -0
  47. package/package.json +1 -1
@@ -24,6 +24,8 @@ import { FileScanner } from '../utils/scanner.js';
24
24
  import { Logger } from '../utils/logger.js';
25
25
  import fs from 'fs-extra';
26
26
  import path from 'path';
27
+ import { isNodeBuiltin, isPythonStdlib } from './hallucinated-imports-stdlib.js';
28
+ import { checkGoImports, checkRubyImports, checkCSharpImports, checkRustImports, checkJavaKotlinImports, loadPackageJson } from './hallucinated-imports-lang.js';
27
29
  export class HallucinatedImportsGate extends Gate {
28
30
  config;
29
31
  constructor(config = {}) {
@@ -55,7 +57,7 @@ export class HallucinatedImportsGate extends Gate {
55
57
  Logger.info(`Hallucinated Imports: Scanning ${files.length} files`);
56
58
  // Build lookup sets for fast resolution
57
59
  const projectFiles = new Set(files.map(f => f.replace(/\\/g, '/')));
58
- const packageJson = await this.loadPackageJson(context.cwd);
60
+ const packageJson = await loadPackageJson(context.cwd);
59
61
  const allDeps = new Set([
60
62
  ...Object.keys(packageJson?.dependencies || {}),
61
63
  ...Object.keys(packageJson?.devDependencies || {}),
@@ -75,19 +77,19 @@ export class HallucinatedImportsGate extends Gate {
75
77
  await this.checkPyImports(content, file, context.cwd, projectFiles, hallucinated);
76
78
  }
77
79
  else if (ext === '.go') {
78
- this.checkGoImports(content, file, context.cwd, projectFiles, hallucinated);
80
+ checkGoImports(content, file, context.cwd, projectFiles, hallucinated);
79
81
  }
80
82
  else if (ext === '.rb') {
81
- this.checkRubyImports(content, file, context.cwd, projectFiles, hallucinated);
83
+ checkRubyImports(content, file, context.cwd, projectFiles, hallucinated);
82
84
  }
83
85
  else if (ext === '.cs') {
84
- this.checkCSharpImports(content, file, context.cwd, projectFiles, hallucinated);
86
+ checkCSharpImports(content, file, context.cwd, projectFiles, hallucinated);
85
87
  }
86
88
  else if (ext === '.rs') {
87
- this.checkRustImports(content, file, context.cwd, projectFiles, hallucinated);
89
+ checkRustImports(content, file, context.cwd, projectFiles, hallucinated);
88
90
  }
89
91
  else if (ext === '.java' || ext === '.kt') {
90
- this.checkJavaKotlinImports(content, file, ext, context.cwd, projectFiles, hallucinated);
92
+ checkJavaKotlinImports(content, file, ext, context.cwd, projectFiles, hallucinated);
91
93
  }
92
94
  }
93
95
  catch (e) { }
@@ -141,7 +143,7 @@ export class HallucinatedImportsGate extends Gate {
141
143
  if (this.config.check_packages) {
142
144
  const pkgName = this.extractPackageName(importPath);
143
145
  // Skip Node.js built-ins
144
- if (this.isNodeBuiltin(pkgName))
146
+ if (isNodeBuiltin(pkgName))
145
147
  continue;
146
148
  if (!allDeps.has(pkgName)) {
147
149
  // Double-check node_modules if available
@@ -172,7 +174,7 @@ export class HallucinatedImportsGate extends Gate {
172
174
  if (!modulePath)
173
175
  continue;
174
176
  // Skip standard library modules
175
- if (this.isPythonStdlib(modulePath))
177
+ if (isPythonStdlib(modulePath))
176
178
  continue;
177
179
  // Check if it's a relative project import
178
180
  if (modulePath.startsWith('.')) {
@@ -242,674 +244,4 @@ export class HallucinatedImportsGate extends Gate {
242
244
  shouldIgnore(importPath) {
243
245
  return this.config.ignore_patterns.some(pattern => new RegExp(pattern).test(importPath));
244
246
  }
245
- /**
246
- * Node.js built-in modules — covers Node.js 18/20/22 LTS
247
- * No third-party packages in this list (removed fs-extra hack).
248
- */
249
- isNodeBuiltin(name) {
250
- // Fast path: node: protocol prefix
251
- if (name.startsWith('node:'))
252
- return true;
253
- const builtins = new Set([
254
- 'assert', 'assert/strict', 'async_hooks', 'buffer', 'child_process',
255
- 'cluster', 'console', 'constants', 'crypto', 'dgram', 'diagnostics_channel',
256
- 'dns', 'dns/promises', 'domain', 'events', 'fs', 'fs/promises',
257
- 'http', 'http2', 'https', 'inspector', 'inspector/promises', 'module',
258
- 'net', 'os', 'path', 'path/posix', 'path/win32', 'perf_hooks',
259
- 'process', 'punycode', 'querystring', 'readline', 'readline/promises',
260
- 'repl', 'stream', 'stream/consumers', 'stream/promises', 'stream/web',
261
- 'string_decoder', 'sys', 'test', 'timers', 'timers/promises',
262
- 'tls', 'trace_events', 'tty', 'url', 'util', 'util/types',
263
- 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
264
- ]);
265
- return builtins.has(name);
266
- }
267
- isPythonStdlib(modulePath) {
268
- const topLevel = modulePath.split('.')[0];
269
- const stdlibs = new Set([
270
- 'abc', 'aifc', 'argparse', 'array', 'ast', 'asyncio', 'atexit',
271
- 'base64', 'bdb', 'binascii', 'binhex', 'bisect', 'builtins',
272
- 'calendar', 'cgi', 'cgitb', 'chunk', 'cmath', 'cmd', 'code',
273
- 'codecs', 'codeop', 'collections', 'colorsys', 'compileall',
274
- 'concurrent', 'configparser', 'contextlib', 'contextvars', 'copy',
275
- 'copyreg', 'cProfile', 'csv', 'ctypes', 'curses', 'dataclasses',
276
- 'datetime', 'dbm', 'decimal', 'difflib', 'dis', 'distutils',
277
- 'doctest', 'email', 'encodings', 'enum', 'errno', 'faulthandler',
278
- 'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'fractions', 'ftplib',
279
- 'functools', 'gc', 'getopt', 'getpass', 'gettext', 'glob', 'grp',
280
- 'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http', 'idlelib',
281
- 'imaplib', 'imghdr', 'importlib', 'inspect', 'io', 'ipaddress',
282
- 'itertools', 'json', 'keyword', 'lib2to3', 'linecache', 'locale',
283
- 'logging', 'lzma', 'mailbox', 'mailcap', 'marshal', 'math',
284
- 'mimetypes', 'mmap', 'modulefinder', 'multiprocessing', 'netrc',
285
- 'nis', 'nntplib', 'numbers', 'operator', 'optparse', 'os',
286
- 'ossaudiodev', 'parser', 'pathlib', 'pdb', 'pickle', 'pickletools',
287
- 'pipes', 'pkgutil', 'platform', 'plistlib', 'poplib', 'posix',
288
- 'posixpath', 'pprint', 'profile', 'pstats', 'pty', 'pwd', 'py_compile',
289
- 'pyclbr', 'pydoc', 'queue', 'quopri', 'random', 're', 'readline',
290
- 'reprlib', 'resource', 'rlcompleter', 'runpy', 'sched', 'secrets',
291
- 'select', 'selectors', 'shelve', 'shlex', 'shutil', 'signal',
292
- 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver',
293
- 'spwd', 'sqlite3', 'sre_compile', 'sre_constants', 'sre_parse',
294
- 'ssl', 'stat', 'statistics', 'string', 'stringprep', 'struct',
295
- 'subprocess', 'sunau', 'symtable', 'sys', 'sysconfig', 'syslog',
296
- 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'termios', 'test',
297
- 'textwrap', 'threading', 'time', 'timeit', 'tkinter', 'token',
298
- 'tokenize', 'tomllib', 'trace', 'traceback', 'tracemalloc', 'tty',
299
- 'turtle', 'turtledemo', 'types', 'typing', 'unicodedata', 'unittest',
300
- 'urllib', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref',
301
- 'webbrowser', 'winreg', 'winsound', 'wsgiref', 'xdrlib', 'xml',
302
- 'xmlrpc', 'zipapp', 'zipfile', 'zipimport', 'zlib',
303
- '_thread', '__future__', '__main__',
304
- ]);
305
- return stdlibs.has(topLevel);
306
- }
307
- /**
308
- * Check Go imports — verify project-relative package paths exist.
309
- *
310
- * Strategy:
311
- * 1. Skip Go standard library (comprehensive list of 150+ packages)
312
- * 2. Skip external modules (any path containing a dot → domain name)
313
- * 3. Parse go.mod for the project module path
314
- * 4. Only flag imports that match the project module prefix but don't resolve
315
- *
316
- * @since v3.0.1 — fixed false positives on Go stdlib (encoding/json, net/http, etc.)
317
- */
318
- checkGoImports(content, file, cwd, projectFiles, hallucinated) {
319
- const lines = content.split('\n');
320
- let inImportBlock = false;
321
- // Try to read go.mod for the module path
322
- const goModPath = path.join(cwd, 'go.mod');
323
- let modulePath = null;
324
- try {
325
- if (fs.pathExistsSync(goModPath)) {
326
- const goMod = fs.readFileSync(goModPath, 'utf-8');
327
- const moduleMatch = goMod.match(/^module\s+(\S+)/m);
328
- if (moduleMatch)
329
- modulePath = moduleMatch[1];
330
- }
331
- }
332
- catch { /* no go.mod — skip project-relative checks entirely */ }
333
- for (let i = 0; i < lines.length; i++) {
334
- const line = lines[i].trim();
335
- // Detect import block: import ( ... )
336
- if (/^import\s*\(/.test(line)) {
337
- inImportBlock = true;
338
- continue;
339
- }
340
- if (inImportBlock && line === ')') {
341
- inImportBlock = false;
342
- continue;
343
- }
344
- // Single import: import "path" or import alias "path"
345
- const singleMatch = line.match(/^import\s+(?:\w+\s+)?"([^"]+)"/);
346
- const blockMatch = inImportBlock ? line.match(/^\s*(?:\w+\s+)?"([^"]+)"/) : null;
347
- const importPath = singleMatch?.[1] || blockMatch?.[1];
348
- if (!importPath)
349
- continue;
350
- // 1. Skip Go standard library — comprehensive list
351
- if (this.isGoStdlib(importPath))
352
- continue;
353
- // 2. If we have a module path, check project-relative imports FIRST
354
- // (project imports like github.com/myorg/project/pkg also have dots)
355
- if (modulePath && importPath.startsWith(modulePath + '/')) {
356
- const relPath = importPath.slice(modulePath.length + 1);
357
- const hasMatchingFile = [...projectFiles].some(f => f.endsWith('.go') && f.startsWith(relPath));
358
- if (!hasMatchingFile) {
359
- hallucinated.push({
360
- file, line: i + 1, importPath, type: 'go',
361
- reason: `Go import '${importPath}' — package directory '${relPath}' not found in project`,
362
- });
363
- }
364
- continue;
365
- }
366
- // 3. Skip external modules — any import containing a dot is a domain
367
- // e.g. github.com/*, google.golang.org/*, go.uber.org/*
368
- if (importPath.includes('.'))
369
- continue;
370
- // 4. No dots, no go.mod match, not stdlib → likely an internal package
371
- // without go.mod context we can't verify, so skip to avoid false positives
372
- }
373
- }
374
- /**
375
- * Comprehensive Go standard library package list.
376
- * Includes all packages from Go 1.22+ (latest stable).
377
- * Go stdlib is identified by having NO dots in the import path.
378
- * We maintain an explicit list for packages with slashes (e.g. encoding/json).
379
- *
380
- * @since v3.0.1
381
- */
382
- isGoStdlib(importPath) {
383
- // Fast check: single-segment packages are always stdlib if no dots
384
- if (!importPath.includes('/') && !importPath.includes('.'))
385
- return true;
386
- // Check the full path against known stdlib packages with sub-paths
387
- const topLevel = importPath.split('/')[0];
388
- // All Go stdlib top-level packages (including those with sub-packages)
389
- const stdlibTopLevel = new Set([
390
- // Single-word packages
391
- 'archive', 'bufio', 'builtin', 'bytes', 'cmp', 'compress',
392
- 'container', 'context', 'crypto', 'database', 'debug',
393
- 'embed', 'encoding', 'errors', 'expvar', 'flag', 'fmt',
394
- 'go', 'hash', 'html', 'image', 'index', 'io', 'iter',
395
- 'log', 'maps', 'math', 'mime', 'net', 'os', 'path',
396
- 'plugin', 'reflect', 'regexp', 'runtime', 'slices', 'sort',
397
- 'strconv', 'strings', 'structs', 'sync', 'syscall',
398
- 'testing', 'text', 'time', 'unicode', 'unique', 'unsafe',
399
- // Internal packages (used by stdlib, sometimes by tools)
400
- 'internal', 'vendor',
401
- ]);
402
- if (stdlibTopLevel.has(topLevel))
403
- return true;
404
- // Explicit full-path list for maximum safety — covers all Go 1.22 stdlib paths
405
- // This catches any edge case the top-level check might miss
406
- const knownStdlibPaths = new Set([
407
- // archive/*
408
- 'archive/tar', 'archive/zip',
409
- // compress/*
410
- 'compress/bzip2', 'compress/flate', 'compress/gzip', 'compress/lzw', 'compress/zlib',
411
- // container/*
412
- 'container/heap', 'container/list', 'container/ring',
413
- // crypto/*
414
- 'crypto/aes', 'crypto/cipher', 'crypto/des', 'crypto/dsa',
415
- 'crypto/ecdh', 'crypto/ecdsa', 'crypto/ed25519', 'crypto/elliptic',
416
- 'crypto/hmac', 'crypto/md5', 'crypto/rand', 'crypto/rc4',
417
- 'crypto/rsa', 'crypto/sha1', 'crypto/sha256', 'crypto/sha512',
418
- 'crypto/subtle', 'crypto/tls', 'crypto/x509', 'crypto/x509/pkix',
419
- // database/*
420
- 'database/sql', 'database/sql/driver',
421
- // debug/*
422
- 'debug/buildinfo', 'debug/dwarf', 'debug/elf', 'debug/gosym',
423
- 'debug/macho', 'debug/pe', 'debug/plan9obj',
424
- // encoding/*
425
- 'encoding/ascii85', 'encoding/asn1', 'encoding/base32', 'encoding/base64',
426
- 'encoding/binary', 'encoding/csv', 'encoding/gob', 'encoding/hex',
427
- 'encoding/json', 'encoding/pem', 'encoding/xml',
428
- // go/*
429
- 'go/ast', 'go/build', 'go/build/constraint', 'go/constant',
430
- 'go/doc', 'go/doc/comment', 'go/format', 'go/importer',
431
- 'go/parser', 'go/printer', 'go/scanner', 'go/token', 'go/types', 'go/version',
432
- // hash/*
433
- 'hash/adler32', 'hash/crc32', 'hash/crc64', 'hash/fnv', 'hash/maphash',
434
- // html/*
435
- 'html/template',
436
- // image/*
437
- 'image/color', 'image/color/palette', 'image/draw',
438
- 'image/gif', 'image/jpeg', 'image/png',
439
- // index/*
440
- 'index/suffixarray',
441
- // io/*
442
- 'io/fs', 'io/ioutil',
443
- // log/*
444
- 'log/slog', 'log/syslog',
445
- // math/*
446
- 'math/big', 'math/bits', 'math/cmplx', 'math/rand', 'math/rand/v2',
447
- // mime/*
448
- 'mime/multipart', 'mime/quotedprintable',
449
- // net/*
450
- 'net/http', 'net/http/cgi', 'net/http/cookiejar', 'net/http/fcgi',
451
- 'net/http/httptest', 'net/http/httptrace', 'net/http/httputil',
452
- 'net/http/pprof', 'net/mail', 'net/netip', 'net/rpc',
453
- 'net/rpc/jsonrpc', 'net/smtp', 'net/textproto', 'net/url',
454
- // os/*
455
- 'os/exec', 'os/signal', 'os/user',
456
- // path/*
457
- 'path/filepath',
458
- // regexp/*
459
- 'regexp/syntax',
460
- // runtime/*
461
- 'runtime/cgo', 'runtime/coverage', 'runtime/debug', 'runtime/metrics',
462
- 'runtime/pprof', 'runtime/race', 'runtime/trace',
463
- // sync/*
464
- 'sync/atomic',
465
- // testing/*
466
- 'testing/fstest', 'testing/iotest', 'testing/quick', 'testing/slogtest',
467
- // text/*
468
- 'text/scanner', 'text/tabwriter', 'text/template', 'text/template/parse',
469
- // unicode/*
470
- 'unicode/utf16', 'unicode/utf8',
471
- ]);
472
- return knownStdlibPaths.has(importPath);
473
- }
474
- /**
475
- * Check Ruby imports — require, require_relative, Gemfile verification
476
- *
477
- * Strategy:
478
- * 1. require_relative: verify target .rb file exists in project
479
- * 2. require: skip stdlib, skip gems from Gemfile/gemspec, flag unknown local requires
480
- *
481
- * @since v3.0.1 — strengthened with stdlib whitelist and Gemfile parsing
482
- */
483
- checkRubyImports(content, file, cwd, projectFiles, hallucinated) {
484
- const lines = content.split('\n');
485
- // Parse Gemfile for known gem dependencies
486
- const gemDeps = this.loadRubyGems(cwd);
487
- for (let i = 0; i < lines.length; i++) {
488
- const line = lines[i].trim();
489
- // Skip comments
490
- if (line.startsWith('#'))
491
- continue;
492
- // require_relative 'path' — must resolve to a real file
493
- const relMatch = line.match(/require_relative\s+['"]([^'"]+)['"]/);
494
- if (relMatch) {
495
- const reqPath = relMatch[1];
496
- const dir = path.dirname(file);
497
- const resolved = path.join(dir, reqPath).replace(/\\/g, '/');
498
- const candidates = [resolved + '.rb', resolved];
499
- if (!candidates.some(c => projectFiles.has(c))) {
500
- hallucinated.push({
501
- file, line: i + 1, importPath: reqPath, type: 'ruby',
502
- reason: `require_relative '${reqPath}' — file not found in project`,
503
- });
504
- }
505
- continue;
506
- }
507
- // require 'something' — check stdlib, gems, then local
508
- const reqMatch = line.match(/^require\s+['"]([^'"]+)['"]/);
509
- if (reqMatch) {
510
- const reqPath = reqMatch[1];
511
- // Skip Ruby stdlib
512
- if (this.isRubyStdlib(reqPath))
513
- continue;
514
- // Skip gems listed in Gemfile
515
- const gemName = reqPath.split('/')[0];
516
- if (gemDeps.has(gemName))
517
- continue;
518
- // Check if it resolves to a project file
519
- const candidates = [
520
- reqPath + '.rb',
521
- reqPath,
522
- 'lib/' + reqPath + '.rb',
523
- 'lib/' + reqPath,
524
- ];
525
- const found = candidates.some(c => projectFiles.has(c));
526
- if (!found) {
527
- // If we have a Gemfile and it's not in it, it might be hallucinated
528
- if (gemDeps.size > 0) {
529
- hallucinated.push({
530
- file, line: i + 1, importPath: reqPath, type: 'ruby',
531
- reason: `require '${reqPath}' — not in stdlib, Gemfile, or project files`,
532
- });
533
- }
534
- }
535
- }
536
- }
537
- }
538
- /** Load gem names from Gemfile */
539
- loadRubyGems(cwd) {
540
- const gems = new Set();
541
- try {
542
- const gemfilePath = path.join(cwd, 'Gemfile');
543
- if (fs.pathExistsSync(gemfilePath)) {
544
- const content = fs.readFileSync(gemfilePath, 'utf-8');
545
- const gemPattern = /gem\s+['"]([^'"]+)['"]/g;
546
- let m;
547
- while ((m = gemPattern.exec(content)) !== null) {
548
- gems.add(m[1]);
549
- }
550
- }
551
- // Also check .gemspec
552
- const gemspecs = [...new Set()]; // placeholder
553
- const files = fs.readdirSync?.(cwd) || [];
554
- for (const f of files) {
555
- if (typeof f === 'string' && f.endsWith('.gemspec')) {
556
- try {
557
- const spec = fs.readFileSync(path.join(cwd, f), 'utf-8');
558
- const depPattern = /add_(?:runtime_)?dependency\s+['"]([^'"]+)['"]/g;
559
- let dm;
560
- while ((dm = depPattern.exec(spec)) !== null) {
561
- gems.add(dm[1]);
562
- }
563
- }
564
- catch { /* skip */ }
565
- }
566
- }
567
- }
568
- catch { /* no Gemfile */ }
569
- return gems;
570
- }
571
- /**
572
- * Ruby standard library — covers Ruby 3.3+ (MRI)
573
- * Includes both the default gems and bundled gems that ship with Ruby.
574
- */
575
- isRubyStdlib(name) {
576
- const topLevel = name.split('/')[0];
577
- const stdlibs = new Set([
578
- // Core libs (always available)
579
- 'abbrev', 'base64', 'benchmark', 'bigdecimal', 'cgi', 'csv',
580
- 'date', 'delegate', 'did_you_mean', 'digest', 'drb', 'english',
581
- 'erb', 'error_highlight', 'etc', 'fcntl', 'fiddle', 'fileutils',
582
- 'find', 'forwardable', 'getoptlong', 'io', 'ipaddr', 'irb',
583
- 'json', 'logger', 'matrix', 'minitest', 'monitor', 'mutex_m',
584
- 'net', 'nkf', 'objspace', 'observer', 'open3', 'open-uri',
585
- 'openssl', 'optparse', 'ostruct', 'pathname', 'pp', 'prettyprint',
586
- 'prime', 'pstore', 'psych', 'racc', 'rake', 'rdoc', 'readline',
587
- 'reline', 'resolv', 'resolv-replace', 'rinda', 'ruby2_keywords',
588
- 'rubygems', 'securerandom', 'set', 'shellwords', 'singleton',
589
- 'socket', 'stringio', 'strscan', 'syntax_suggest', 'syslog',
590
- 'tempfile', 'time', 'timeout', 'tmpdir', 'tsort', 'un',
591
- 'unicode_normalize', 'uri', 'weakref', 'yaml', 'zlib',
592
- // Default gems (ship with Ruby, can be overridden)
593
- 'bundler', 'debug', 'net-ftp', 'net-http', 'net-imap',
594
- 'net-pop', 'net-protocol', 'net-smtp', 'power_assert',
595
- 'test-unit', 'rexml', 'rss', 'typeprof',
596
- // Common C extensions
597
- 'stringio', 'io/console', 'io/nonblock', 'io/wait',
598
- 'rbconfig', 'mkmf', 'thread',
599
- // Rails-adjacent but actually stdlib
600
- 'webrick', 'cmath', 'complex', 'rational',
601
- 'coverage', 'ripper', 'win32ole', 'win32api',
602
- ]);
603
- return stdlibs.has(topLevel);
604
- }
605
- /**
606
- * Check C# imports — using directives against .NET framework, NuGet, and project
607
- *
608
- * Strategy:
609
- * 1. Skip .NET framework namespaces (System.*, Microsoft.*, etc.)
610
- * 2. Skip NuGet packages from .csproj PackageReference
611
- * 3. Flag project-relative namespaces that don't resolve
612
- *
613
- * @since v3.0.1 — .csproj NuGet parsing, comprehensive framework namespace list
614
- */
615
- checkCSharpImports(content, file, cwd, projectFiles, hallucinated) {
616
- const lines = content.split('\n');
617
- const nugetPackages = this.loadNuGetPackages(cwd);
618
- for (let i = 0; i < lines.length; i++) {
619
- const line = lines[i].trim();
620
- // Match: using Namespace; and using static Namespace.Class;
621
- // Skip: using alias = Namespace; and using (var x = ...) disposable
622
- const usingMatch = line.match(/^using\s+(?:static\s+)?([\w.]+)\s*;/);
623
- if (!usingMatch)
624
- continue;
625
- const namespace = usingMatch[1];
626
- // 1. Skip .NET framework and BCL namespaces
627
- if (this.isDotNetFramework(namespace))
628
- continue;
629
- // 2. Skip NuGet packages from .csproj
630
- const topLevel = namespace.split('.')[0];
631
- if (nugetPackages.has(topLevel) || nugetPackages.has(namespace.split('.').slice(0, 2).join('.')))
632
- continue;
633
- // 3. Check if the namespace maps to any .cs file in the project
634
- // C# namespaces often have a root prefix (project name) not in the directory tree
635
- // e.g. MyProject.Services.UserService → check Services/UserService AND MyProject/Services/UserService
636
- const nsParts = namespace.split('.');
637
- const nsPath = namespace.replace(/\./g, '/');
638
- // Also check without root prefix (common convention: namespace root != directory root)
639
- const nsPathNoRoot = nsParts.slice(1).join('/');
640
- const csFiles = [...projectFiles].filter(f => f.endsWith('.cs'));
641
- const hasMatch = csFiles.some(f => f.includes(nsPath) || (nsPathNoRoot && f.includes(nsPathNoRoot)));
642
- // Only flag if we have .csproj context (proves this is a real .NET project)
643
- if (!hasMatch && namespace.includes('.') && nugetPackages.size >= 0) {
644
- // Check if we actually have .csproj context (a real .NET project)
645
- const hasCsproj = this.hasCsprojFile(cwd);
646
- if (hasCsproj) {
647
- hallucinated.push({
648
- file, line: i + 1, importPath: namespace, type: 'csharp',
649
- reason: `Namespace '${namespace}' — no matching files in project, not in NuGet packages`,
650
- });
651
- }
652
- }
653
- }
654
- }
655
- /** Check if any .csproj file exists in the project root */
656
- hasCsprojFile(cwd) {
657
- try {
658
- const files = fs.readdirSync?.(cwd) || [];
659
- return files.some((f) => typeof f === 'string' && f.endsWith('.csproj'));
660
- }
661
- catch {
662
- return false;
663
- }
664
- }
665
- /** Parse .csproj files for PackageReference names */
666
- loadNuGetPackages(cwd) {
667
- const packages = new Set();
668
- try {
669
- const files = fs.readdirSync?.(cwd) || [];
670
- for (const f of files) {
671
- if (typeof f === 'string' && f.endsWith('.csproj')) {
672
- try {
673
- const content = fs.readFileSync(path.join(cwd, f), 'utf-8');
674
- const pkgPattern = /PackageReference\s+Include="([^"]+)"/g;
675
- let m;
676
- while ((m = pkgPattern.exec(content)) !== null) {
677
- packages.add(m[1]);
678
- // Also add top-level namespace (e.g. Newtonsoft.Json → Newtonsoft)
679
- packages.add(m[1].split('.')[0]);
680
- }
681
- }
682
- catch { /* skip */ }
683
- }
684
- }
685
- }
686
- catch { /* no .csproj */ }
687
- return packages;
688
- }
689
- /**
690
- * .NET 8 framework and common ecosystem namespaces
691
- * Covers BCL, ASP.NET, EF Core, and major ecosystem packages
692
- */
693
- isDotNetFramework(namespace) {
694
- const topLevel = namespace.split('.')[0];
695
- const frameworkPrefixes = new Set([
696
- // BCL / .NET Runtime
697
- 'System', 'Microsoft', 'Windows',
698
- // Common ecosystem (NuGet defaults everyone uses)
699
- 'Newtonsoft', 'NUnit', 'Xunit', 'Moq', 'AutoMapper',
700
- 'FluentAssertions', 'FluentValidation', 'Serilog', 'NLog',
701
- 'Dapper', 'MediatR', 'Polly', 'Swashbuckle', 'Hangfire',
702
- 'StackExchange', 'Npgsql', 'MongoDB', 'MySql', 'Oracle',
703
- 'Amazon', 'Google', 'Azure', 'Grpc',
704
- 'Bogus', 'Humanizer', 'CsvHelper', 'MailKit', 'MimeKit',
705
- 'RestSharp', 'Refit', 'AutoFixture', 'Shouldly',
706
- 'IdentityModel', 'IdentityServer4',
707
- ]);
708
- return frameworkPrefixes.has(topLevel);
709
- }
710
- /**
711
- * Check Rust imports — use/extern crate against std/core/alloc and Cargo.toml
712
- *
713
- * Strategy:
714
- * 1. Skip Rust std, core, alloc crates
715
- * 2. Skip crates listed in Cargo.toml [dependencies]
716
- * 3. Flag unknown extern crate and use statements for project modules that don't exist
717
- *
718
- * @since v3.0.1
719
- */
720
- checkRustImports(content, file, cwd, projectFiles, hallucinated) {
721
- const lines = content.split('\n');
722
- const cargoDeps = this.loadCargoDeps(cwd);
723
- for (let i = 0; i < lines.length; i++) {
724
- const line = lines[i].trim();
725
- if (line.startsWith('//') || line.startsWith('/*'))
726
- continue;
727
- // extern crate foo;
728
- const externMatch = line.match(/^extern\s+crate\s+(\w+)/);
729
- if (externMatch) {
730
- const crateName = externMatch[1];
731
- if (this.isRustStdCrate(crateName))
732
- continue;
733
- if (cargoDeps.has(crateName))
734
- continue;
735
- hallucinated.push({
736
- file, line: i + 1, importPath: crateName, type: 'rust',
737
- reason: `extern crate '${crateName}' — not in Cargo.toml or Rust std`,
738
- });
739
- continue;
740
- }
741
- // use foo::bar::baz; or use foo::{bar, baz};
742
- const useMatch = line.match(/^(?:pub\s+)?use\s+(\w+)::/);
743
- if (useMatch) {
744
- const crateName = useMatch[1];
745
- if (this.isRustStdCrate(crateName))
746
- continue;
747
- if (cargoDeps.has(crateName))
748
- continue;
749
- // 'crate' and 'self' and 'super' are Rust path keywords
750
- if (['crate', 'self', 'super'].includes(crateName))
751
- continue;
752
- hallucinated.push({
753
- file, line: i + 1, importPath: crateName, type: 'rust',
754
- reason: `use ${crateName}:: — crate not in Cargo.toml or Rust std`,
755
- });
756
- }
757
- }
758
- }
759
- /** Load dependency names from Cargo.toml */
760
- loadCargoDeps(cwd) {
761
- const deps = new Set();
762
- try {
763
- const cargoPath = path.join(cwd, 'Cargo.toml');
764
- if (fs.pathExistsSync(cargoPath)) {
765
- const content = fs.readFileSync(cargoPath, 'utf-8');
766
- // Match [dependencies] section entries: name = "version" or name = { ... }
767
- const depPattern = /^\s*(\w[\w-]*)\s*=/gm;
768
- let inDeps = false;
769
- for (const line of content.split('\n')) {
770
- if (/^\[(?:.*-)?dependencies/.test(line.trim())) {
771
- inDeps = true;
772
- continue;
773
- }
774
- if (/^\[/.test(line.trim()) && inDeps) {
775
- inDeps = false;
776
- continue;
777
- }
778
- if (inDeps) {
779
- const m = line.match(/^\s*([\w][\w-]*)\s*=/);
780
- if (m)
781
- deps.add(m[1].replace(/-/g, '_')); // Rust uses _ in code for - in Cargo
782
- }
783
- }
784
- }
785
- }
786
- catch { /* no Cargo.toml */ }
787
- return deps;
788
- }
789
- /** Rust standard crates — std, core, alloc, proc_macro, and common test crates */
790
- isRustStdCrate(name) {
791
- const stdCrates = new Set([
792
- 'std', 'core', 'alloc', 'proc_macro', 'test',
793
- // Common proc-macro / compiler crates
794
- 'proc_macro2', 'syn', 'quote',
795
- ]);
796
- return stdCrates.has(name);
797
- }
798
- /**
799
- * Check Java/Kotlin imports — against stdlib and build dependencies
800
- *
801
- * Strategy:
802
- * 1. Skip java.*, javax.*, jakarta.* (Java stdlib/EE)
803
- * 2. Skip kotlin.*, kotlinx.* (Kotlin stdlib)
804
- * 3. Skip deps from build.gradle or pom.xml
805
- * 4. Flag project-relative imports that don't resolve
806
- *
807
- * @since v3.0.1
808
- */
809
- checkJavaKotlinImports(content, file, ext, cwd, projectFiles, hallucinated) {
810
- const lines = content.split('\n');
811
- const buildDeps = this.loadJavaDeps(cwd);
812
- const isKotlin = ext === '.kt';
813
- for (let i = 0; i < lines.length; i++) {
814
- const line = lines[i].trim();
815
- // import com.example.package.Class
816
- const importMatch = line.match(/^import\s+(?:static\s+)?([\w.]+)/);
817
- if (!importMatch)
818
- continue;
819
- const importPath = importMatch[1];
820
- // Skip Java stdlib
821
- if (this.isJavaStdlib(importPath))
822
- continue;
823
- // Skip Kotlin stdlib
824
- if (isKotlin && this.isKotlinStdlib(importPath))
825
- continue;
826
- // Skip known build dependencies (by group prefix)
827
- const parts = importPath.split('.');
828
- const group2 = parts.slice(0, 2).join('.');
829
- const group3 = parts.slice(0, 3).join('.');
830
- if (buildDeps.has(group2) || buildDeps.has(group3))
831
- continue;
832
- // Check if it resolves to a project file
833
- const javaPath = importPath.replace(/\./g, '/');
834
- const candidates = [
835
- javaPath + '.java',
836
- javaPath + '.kt',
837
- 'src/main/java/' + javaPath + '.java',
838
- 'src/main/kotlin/' + javaPath + '.kt',
839
- ];
840
- const found = candidates.some(c => projectFiles.has(c)) ||
841
- [...projectFiles].some(f => f.includes(javaPath));
842
- if (!found) {
843
- // Only flag if we have build deps context (Gradle/Maven project)
844
- if (buildDeps.size > 0) {
845
- hallucinated.push({
846
- file, line: i + 1, importPath, type: isKotlin ? 'kotlin' : 'java',
847
- reason: `import '${importPath}' — not in stdlib, build deps, or project files`,
848
- });
849
- }
850
- }
851
- }
852
- }
853
- /** Load dependency group IDs from build.gradle or pom.xml */
854
- loadJavaDeps(cwd) {
855
- const deps = new Set();
856
- try {
857
- // Gradle: build.gradle or build.gradle.kts
858
- for (const gradleFile of ['build.gradle', 'build.gradle.kts']) {
859
- const gradlePath = path.join(cwd, gradleFile);
860
- if (fs.pathExistsSync(gradlePath)) {
861
- const content = fs.readFileSync(gradlePath, 'utf-8');
862
- // Match: implementation 'group:artifact:version' or "group:artifact:version"
863
- const depPattern = /(?:implementation|api|compile|testImplementation|runtimeOnly)\s*[('"]([^:'"]+)/g;
864
- let m;
865
- while ((m = depPattern.exec(content)) !== null) {
866
- deps.add(m[1]); // group ID like "com.google.guava"
867
- }
868
- }
869
- }
870
- // Maven: pom.xml
871
- const pomPath = path.join(cwd, 'pom.xml');
872
- if (fs.pathExistsSync(pomPath)) {
873
- const content = fs.readFileSync(pomPath, 'utf-8');
874
- const groupPattern = /<groupId>([^<]+)<\/groupId>/g;
875
- let m;
876
- while ((m = groupPattern.exec(content)) !== null) {
877
- deps.add(m[1]);
878
- }
879
- }
880
- }
881
- catch { /* no build files */ }
882
- return deps;
883
- }
884
- /** Java standard library and Jakarta EE namespaces */
885
- isJavaStdlib(importPath) {
886
- const prefixes = [
887
- 'java.', 'javax.', 'jakarta.',
888
- 'sun.', 'com.sun.', 'jdk.',
889
- // Android SDK
890
- 'android.', 'androidx.',
891
- // Common ecosystem (so ubiquitous they're basically stdlib)
892
- 'org.junit.', 'org.slf4j.', 'org.apache.logging.',
893
- ];
894
- return prefixes.some(p => importPath.startsWith(p));
895
- }
896
- /** Kotlin standard library namespaces */
897
- isKotlinStdlib(importPath) {
898
- const prefixes = [
899
- 'kotlin.', 'kotlinx.',
900
- // Java interop (Kotlin can use Java stdlib directly)
901
- 'java.', 'javax.', 'jakarta.',
902
- ];
903
- return prefixes.some(p => importPath.startsWith(p));
904
- }
905
- async loadPackageJson(cwd) {
906
- try {
907
- const pkgPath = path.join(cwd, 'package.json');
908
- if (await fs.pathExists(pkgPath)) {
909
- return await fs.readJson(pkgPath);
910
- }
911
- }
912
- catch (e) { }
913
- return null;
914
- }
915
247
  }