@rigour-labs/core 3.0.2 → 3.0.4

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.
@@ -0,0 +1,675 @@
1
+ /**
2
+ * Phantom APIs Gate
3
+ *
4
+ * Detects calls to non-existent methods/properties on known stdlib modules.
5
+ * AI models confidently generate method names that look correct but don't exist —
6
+ * e.g. fs.readFileAsync(), path.combine(), crypto.generateHash().
7
+ *
8
+ * This is the #2 most dangerous AI hallucination after package hallucination.
9
+ * Unlike type checkers, this gate catches phantom APIs even in plain JS, Python,
10
+ * and other dynamically-typed languages where the call would silently fail at runtime.
11
+ *
12
+ * Supported languages:
13
+ * JS/TS — Node.js 22.x builtins (fs, path, crypto, http, os, child_process, etc.)
14
+ * Python — stdlib modules (os, json, sys, re, datetime, pathlib, subprocess, etc.)
15
+ * Go — Common hallucinated stdlib patterns (strings vs bytes, os vs io, etc.)
16
+ * C# — Common .NET hallucinated APIs (LINQ, File I/O, string methods)
17
+ * Java — Common hallucinated JDK APIs (Collections, String, Stream, Files)
18
+ *
19
+ * @since v3.0.0
20
+ * @since v3.0.3 — Go, C#, Java pattern-based detection added
21
+ */
22
+ import { Gate } from './base.js';
23
+ import { FileScanner } from '../utils/scanner.js';
24
+ import { Logger } from '../utils/logger.js';
25
+ import fs from 'fs-extra';
26
+ import path from 'path';
27
+ export class PhantomApisGate extends Gate {
28
+ config;
29
+ constructor(config = {}) {
30
+ super('phantom-apis', 'Phantom API Detection');
31
+ this.config = {
32
+ enabled: config.enabled ?? true,
33
+ check_node: config.check_node ?? true,
34
+ check_python: config.check_python ?? true,
35
+ check_go: config.check_go ?? true,
36
+ check_csharp: config.check_csharp ?? true,
37
+ check_java: config.check_java ?? true,
38
+ ignore_patterns: config.ignore_patterns ?? [],
39
+ };
40
+ }
41
+ get provenance() { return 'ai-drift'; }
42
+ async run(context) {
43
+ if (!this.config.enabled)
44
+ return [];
45
+ const failures = [];
46
+ const phantoms = [];
47
+ const files = await FileScanner.findFiles({
48
+ cwd: context.cwd,
49
+ patterns: ['**/*.{ts,js,tsx,jsx,py,go,cs,java,kt}'],
50
+ ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/build/**',
51
+ '**/.venv/**', '**/venv/**', '**/vendor/**', '**/__pycache__/**',
52
+ '**/bin/Debug/**', '**/bin/Release/**', '**/obj/**',
53
+ '**/target/**', '**/.gradle/**', '**/out/**'],
54
+ });
55
+ Logger.info(`Phantom APIs: Scanning ${files.length} files`);
56
+ for (const file of files) {
57
+ try {
58
+ const fullPath = path.join(context.cwd, file);
59
+ const content = await fs.readFile(fullPath, 'utf-8');
60
+ const ext = path.extname(file);
61
+ if (['.ts', '.js', '.tsx', '.jsx'].includes(ext) && this.config.check_node) {
62
+ this.checkNodePhantomApis(content, file, phantoms);
63
+ }
64
+ else if (ext === '.py' && this.config.check_python) {
65
+ this.checkPythonPhantomApis(content, file, phantoms);
66
+ }
67
+ else if (ext === '.go' && this.config.check_go) {
68
+ this.checkGoPhantomApis(content, file, phantoms);
69
+ }
70
+ else if (ext === '.cs' && this.config.check_csharp) {
71
+ this.checkCSharpPhantomApis(content, file, phantoms);
72
+ }
73
+ else if ((ext === '.java' || ext === '.kt') && this.config.check_java) {
74
+ this.checkJavaPhantomApis(content, file, phantoms);
75
+ }
76
+ }
77
+ catch { /* skip unreadable files */ }
78
+ }
79
+ // Group by file
80
+ const byFile = new Map();
81
+ for (const p of phantoms) {
82
+ const existing = byFile.get(p.file) || [];
83
+ existing.push(p);
84
+ byFile.set(p.file, existing);
85
+ }
86
+ for (const [file, apis] of byFile) {
87
+ const details = apis.map(a => ` L${a.line}: ${a.module}.${a.method}() — ${a.reason}`).join('\n');
88
+ failures.push(this.createFailure(`Phantom API calls in ${file}:\n${details}`, [file], `These method calls reference functions that don't exist on the target module. AI models confidently hallucinate plausible-sounding method names. Check the official API docs.`, 'Phantom APIs', apis[0].line, undefined, 'high'));
89
+ }
90
+ return failures;
91
+ }
92
+ /**
93
+ * Node.js stdlib method verification.
94
+ * For each known module, we maintain the actual exported methods.
95
+ * Any call like fs.readFileAsync() that doesn't match is flagged.
96
+ */
97
+ checkNodePhantomApis(content, file, phantoms) {
98
+ const lines = content.split('\n');
99
+ // Detect which stdlib modules are imported and their local aliases
100
+ const moduleAliases = new Map(); // alias → module name
101
+ for (const line of lines) {
102
+ // import fs from 'fs' / import * as fs from 'fs'
103
+ const defaultImport = line.match(/import\s+(?:\*\s+as\s+)?(\w+)\s+from\s+['"](?:node:)?(fs|path|crypto|os|child_process|http|https|url|util|stream|events|buffer|querystring|net|dns|tls|zlib|readline|cluster|worker_threads|timers|perf_hooks|assert)['"]/);
104
+ if (defaultImport) {
105
+ moduleAliases.set(defaultImport[1], defaultImport[2]);
106
+ continue;
107
+ }
108
+ // const fs = require('fs')
109
+ const requireImport = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"](?:node:)?(fs|path|crypto|os|child_process|http|https|url|util|stream|events|buffer|querystring|net|dns|tls|zlib|readline|cluster|worker_threads|timers|perf_hooks|assert)['"]\s*\)/);
110
+ if (requireImport) {
111
+ moduleAliases.set(requireImport[1], requireImport[2]);
112
+ }
113
+ }
114
+ if (moduleAliases.size === 0)
115
+ return;
116
+ // Scan for method calls on imported modules
117
+ for (let i = 0; i < lines.length; i++) {
118
+ const line = lines[i];
119
+ for (const [alias, moduleName] of moduleAliases) {
120
+ // Match: alias.methodName( or alias.property.something(
121
+ const callPattern = new RegExp(`\\b${this.escapeRegex(alias)}\\.(\\w+)\\s*\\(`, 'g');
122
+ let match;
123
+ while ((match = callPattern.exec(line)) !== null) {
124
+ const method = match[1];
125
+ const knownMethods = NODE_STDLIB_METHODS[moduleName];
126
+ if (knownMethods && !knownMethods.has(method)) {
127
+ // Check if it's a common hallucinated method
128
+ const suggestion = this.suggestNodeMethod(moduleName, method);
129
+ phantoms.push({
130
+ file, line: i + 1, module: moduleName, method,
131
+ reason: `'${method}' does not exist on '${moduleName}'${suggestion ? `. Did you mean '${suggestion}'?` : ''}`,
132
+ });
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ /**
139
+ * Python stdlib method verification.
140
+ */
141
+ checkPythonPhantomApis(content, file, phantoms) {
142
+ const lines = content.split('\n');
143
+ // Detect imported modules: import os / import json as j / from os import path
144
+ const moduleAliases = new Map();
145
+ for (const line of lines) {
146
+ const trimmed = line.trim();
147
+ // import os
148
+ const simpleImport = trimmed.match(/^import\s+(os|json|sys|re|math|datetime|pathlib|subprocess|shutil|collections|itertools|functools|typing|io|hashlib|base64|urllib|http|socket|threading|logging|argparse|csv|sqlite3|random|time|copy|glob|tempfile|struct|pickle|gzip|zipfile)\s*$/);
149
+ if (simpleImport) {
150
+ moduleAliases.set(simpleImport[1], simpleImport[1]);
151
+ continue;
152
+ }
153
+ // import os as operating_system
154
+ const aliasImport = trimmed.match(/^import\s+(os|json|sys|re|math|datetime|pathlib|subprocess|shutil|collections|itertools|functools|typing|io|hashlib|base64|urllib|http|socket|threading|logging|argparse|csv|sqlite3|random|time|copy|glob|tempfile|struct|pickle|gzip|zipfile)\s+as\s+(\w+)/);
155
+ if (aliasImport) {
156
+ moduleAliases.set(aliasImport[2], aliasImport[1]);
157
+ }
158
+ }
159
+ if (moduleAliases.size === 0)
160
+ return;
161
+ for (let i = 0; i < lines.length; i++) {
162
+ const line = lines[i];
163
+ for (const [alias, moduleName] of moduleAliases) {
164
+ const callPattern = new RegExp(`\\b${this.escapeRegex(alias)}\\.(\\w+)\\s*\\(`, 'g');
165
+ let match;
166
+ while ((match = callPattern.exec(line)) !== null) {
167
+ const method = match[1];
168
+ const knownMethods = PYTHON_STDLIB_METHODS[moduleName];
169
+ if (knownMethods && !knownMethods.has(method)) {
170
+ const suggestion = this.suggestPythonMethod(moduleName, method);
171
+ phantoms.push({
172
+ file, line: i + 1, module: moduleName, method,
173
+ reason: `'${method}' does not exist on '${moduleName}'${suggestion ? `. Did you mean '${suggestion}'?` : ''}`,
174
+ });
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ /** Suggest the closest real method name (Levenshtein distance ≤ 3) */
181
+ suggestNodeMethod(module, phantom) {
182
+ const methods = NODE_STDLIB_METHODS[module];
183
+ if (!methods)
184
+ return null;
185
+ return this.findClosest(phantom, [...methods]);
186
+ }
187
+ suggestPythonMethod(module, phantom) {
188
+ const methods = PYTHON_STDLIB_METHODS[module];
189
+ if (!methods)
190
+ return null;
191
+ return this.findClosest(phantom, [...methods]);
192
+ }
193
+ findClosest(target, candidates) {
194
+ let best = null;
195
+ let bestDist = 4; // max distance threshold
196
+ for (const c of candidates) {
197
+ const dist = this.levenshtein(target.toLowerCase(), c.toLowerCase());
198
+ if (dist < bestDist) {
199
+ bestDist = dist;
200
+ best = c;
201
+ }
202
+ }
203
+ return best;
204
+ }
205
+ levenshtein(a, b) {
206
+ const m = a.length, n = b.length;
207
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
208
+ for (let i = 0; i <= m; i++)
209
+ dp[i][0] = i;
210
+ for (let j = 0; j <= n; j++)
211
+ dp[0][j] = j;
212
+ for (let i = 1; i <= m; i++) {
213
+ for (let j = 1; j <= n; j++) {
214
+ dp[i][j] = a[i - 1] === b[j - 1]
215
+ ? dp[i - 1][j - 1]
216
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
217
+ }
218
+ }
219
+ return dp[m][n];
220
+ }
221
+ /**
222
+ * Go phantom API detection — pattern-based.
223
+ * AI commonly hallucinates Python/JS-style method names on Go packages.
224
+ * e.g. strings.Contains() exists, but strings.includes() doesn't.
225
+ */
226
+ checkGoPhantomApis(content, file, phantoms) {
227
+ const lines = content.split('\n');
228
+ for (let i = 0; i < lines.length; i++) {
229
+ const line = lines[i];
230
+ for (const rule of GO_PHANTOM_RULES) {
231
+ if (rule.pattern.test(line)) {
232
+ phantoms.push({
233
+ file, line: i + 1,
234
+ module: rule.module, method: rule.phantom,
235
+ reason: `'${rule.phantom}' does not exist on '${rule.module}'. ${rule.suggestion}`,
236
+ });
237
+ }
238
+ }
239
+ }
240
+ }
241
+ /**
242
+ * C# phantom API detection — pattern-based.
243
+ * AI hallucinates Java/Python-style method names on .NET types.
244
+ */
245
+ checkCSharpPhantomApis(content, file, phantoms) {
246
+ const lines = content.split('\n');
247
+ for (let i = 0; i < lines.length; i++) {
248
+ const line = lines[i];
249
+ for (const rule of CSHARP_PHANTOM_RULES) {
250
+ if (rule.pattern.test(line)) {
251
+ phantoms.push({
252
+ file, line: i + 1,
253
+ module: rule.module, method: rule.phantom,
254
+ reason: `'${rule.phantom}' does not exist in C#. ${rule.suggestion}`,
255
+ });
256
+ }
257
+ }
258
+ }
259
+ }
260
+ /**
261
+ * Java/Kotlin phantom API detection — pattern-based.
262
+ * AI hallucinates Python/JS-style APIs on JDK classes.
263
+ */
264
+ checkJavaPhantomApis(content, file, phantoms) {
265
+ const lines = content.split('\n');
266
+ for (let i = 0; i < lines.length; i++) {
267
+ const line = lines[i];
268
+ for (const rule of JAVA_PHANTOM_RULES) {
269
+ if (rule.pattern.test(line)) {
270
+ phantoms.push({
271
+ file, line: i + 1,
272
+ module: rule.module, method: rule.phantom,
273
+ reason: `'${rule.phantom}' does not exist in Java. ${rule.suggestion}`,
274
+ });
275
+ }
276
+ }
277
+ }
278
+ }
279
+ escapeRegex(s) {
280
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
281
+ }
282
+ }
283
+ /**
284
+ * Go commonly hallucinated APIs — AI mixes up Python/JS idioms with Go.
285
+ */
286
+ const GO_PHANTOM_RULES = [
287
+ { pattern: /\bstrings\.includes\s*\(/, module: 'strings', phantom: 'includes', suggestion: "Use strings.Contains()" },
288
+ { pattern: /\bstrings\.lower\s*\(/, module: 'strings', phantom: 'lower', suggestion: "Use strings.ToLower()" },
289
+ { pattern: /\bstrings\.upper\s*\(/, module: 'strings', phantom: 'upper', suggestion: "Use strings.ToUpper()" },
290
+ { pattern: /\bstrings\.strip\s*\(/, module: 'strings', phantom: 'strip', suggestion: "Use strings.TrimSpace()" },
291
+ { pattern: /\bstrings\.find\s*\(/, module: 'strings', phantom: 'find', suggestion: "Use strings.Index()" },
292
+ { pattern: /\bstrings\.startswith\s*\(/, module: 'strings', phantom: 'startswith', suggestion: "Use strings.HasPrefix()" },
293
+ { pattern: /\bstrings\.endswith\s*\(/, module: 'strings', phantom: 'endswith', suggestion: "Use strings.HasSuffix()" },
294
+ { pattern: /\bos\.ReadFile\s*\(/, module: 'os', phantom: 'ReadFile', suggestion: "Use os.ReadFile() (Go 1.16+) or ioutil.ReadFile() (legacy)" },
295
+ { pattern: /\bos\.Exists\s*\(/, module: 'os', phantom: 'Exists', suggestion: "Use os.Stat() and check os.IsNotExist(err)" },
296
+ { pattern: /\bos\.isdir\s*\(/, module: 'os', phantom: 'isdir', suggestion: "Use os.Stat() then .IsDir()" },
297
+ { pattern: /\bos\.listdir\s*\(/, module: 'os', phantom: 'listdir', suggestion: "Use os.ReadDir()" },
298
+ { pattern: /\bfmt\.Format\s*\(/, module: 'fmt', phantom: 'Format', suggestion: "Use fmt.Sprintf()" },
299
+ { pattern: /\bfmt\.Print\s*\((?!ln|f)/, module: 'fmt', phantom: 'Print', suggestion: "fmt.Print() exists but did you mean fmt.Println() or fmt.Printf()?" },
300
+ { pattern: /\bhttp\.Get\s*\([^)]*\)\s*\.\s*Body/, module: 'http', phantom: 'Get().Body', suggestion: "http.Get() returns (*Response, error) — must check error first" },
301
+ { pattern: /\bjson\.parse\s*\(/, module: 'json', phantom: 'parse', suggestion: "Use json.Unmarshal()" },
302
+ { pattern: /\bjson\.stringify\s*\(/, module: 'json', phantom: 'stringify', suggestion: "Use json.Marshal()" },
303
+ { pattern: /\bfilepath\.Combine\s*\(/, module: 'filepath', phantom: 'Combine', suggestion: "Use filepath.Join()" },
304
+ { pattern: /\bmath\.Max\s*\(/, module: 'math', phantom: 'Max (pre-1.21)', suggestion: "math.Max() only works on float64. For int, use max() builtin (Go 1.21+)" },
305
+ { pattern: /\bsort\.Sort\s*\(\s*\[\]/, module: 'sort', phantom: 'Sort([]T)', suggestion: "Use slices.Sort() (Go 1.21+) or sort.Slice()" },
306
+ ];
307
+ /**
308
+ * C# commonly hallucinated APIs — AI mixes up Java/Python idioms with .NET.
309
+ */
310
+ const CSHARP_PHANTOM_RULES = [
311
+ { pattern: /\.length\b(?!\s*\()/, module: 'String', phantom: '.length', suggestion: "Use .Length (capital L) in C#" },
312
+ { pattern: /\.equals\s*\(/, module: 'Object', phantom: '.equals()', suggestion: "Use .Equals() (capital E) in C#" },
313
+ { pattern: /\.toString\s*\(/, module: 'Object', phantom: '.toString()', suggestion: "Use .ToString() (capital T) in C#" },
314
+ { pattern: /\.hashCode\s*\(/, module: 'Object', phantom: '.hashCode()', suggestion: "Use .GetHashCode() in C#" },
315
+ { pattern: /\.getClass\s*\(/, module: 'Object', phantom: '.getClass()', suggestion: "Use .GetType() in C#" },
316
+ { pattern: /\.isEmpty\s*\(/, module: 'String', phantom: '.isEmpty()', suggestion: "Use string.IsNullOrEmpty() or .Length == 0 in C#" },
317
+ { pattern: /\.charAt\s*\(/, module: 'String', phantom: '.charAt()', suggestion: "Use string[index] indexer in C#" },
318
+ { pattern: /\.substring\s*\(/, module: 'String', phantom: '.substring()', suggestion: "Use .Substring() (capital S) in C#" },
319
+ { pattern: /\.indexOf\s*\(/, module: 'String', phantom: '.indexOf()', suggestion: "Use .IndexOf() (capital I) in C#" },
320
+ { pattern: /List<[^>]+>\s+\w+\s*=\s*new\s+ArrayList/, module: 'Collections', phantom: 'ArrayList', suggestion: "Use new List<T>() in C# — ArrayList is Java" },
321
+ { pattern: /HashMap</, module: 'Collections', phantom: 'HashMap', suggestion: "Use Dictionary<TKey, TValue> in C#" },
322
+ { pattern: /System\.out\.println/, module: 'System', phantom: 'System.out.println', suggestion: "Use Console.WriteLine() in C#" },
323
+ { pattern: /\.stream\s*\(\)\s*\./, module: 'Collections', phantom: '.stream()', suggestion: "Use LINQ (.Select, .Where, etc.) in C# — .stream() is Java" },
324
+ { pattern: /\.forEach\s*\(\s*\w+\s*->/, module: 'Collections', phantom: '.forEach(x ->)', suggestion: "Use .ForEach(x =>) or foreach loop in C#" },
325
+ { pattern: /File\.readAllText\s*\(/, module: 'File', phantom: 'readAllText', suggestion: "Use File.ReadAllText() (capital R) in C#" },
326
+ { pattern: /throws\s+\w+Exception/, module: 'method', phantom: 'throws', suggestion: "C# doesn't use checked exceptions — remove throws clause" },
327
+ ];
328
+ /**
329
+ * Java commonly hallucinated APIs — AI mixes up Python/JS/C# idioms with JDK.
330
+ */
331
+ const JAVA_PHANTOM_RULES = [
332
+ { pattern: /\.len\s*\(/, module: 'Object', phantom: '.len()', suggestion: "Use .length() for String, .size() for Collection, .length for arrays in Java" },
333
+ { pattern: /\bprint\s*\(\s*['"]/, module: 'IO', phantom: 'print()', suggestion: "Use System.out.println() in Java" },
334
+ { pattern: /\.push\s*\(/, module: 'List', phantom: '.push()', suggestion: "Use .add() for List in Java" },
335
+ { pattern: /\.append\s*\((?!.*StringBuilder|.*StringBuffer)/, module: 'List', phantom: '.append()', suggestion: "Use .add() for List in Java — .append() is for StringBuilder" },
336
+ { pattern: /\.include(?:s)?\s*\(/, module: 'Collection', phantom: '.includes()', suggestion: "Use .contains() in Java" },
337
+ { pattern: /\.slice\s*\(/, module: 'List', phantom: '.slice()', suggestion: "Use .subList() for List in Java" },
338
+ { pattern: /\.map\s*\(\s*\w+\s*=>/, module: 'Collection', phantom: '.map(x =>)', suggestion: "Use .stream().map(x ->) in Java — arrow is -> not =>" },
339
+ { pattern: /\.filter\s*\(\s*\w+\s*=>/, module: 'Collection', phantom: '.filter(x =>)', suggestion: "Use .stream().filter(x ->) in Java — arrow is -> not =>" },
340
+ { pattern: /Console\.(?:Write|Read)/, module: 'IO', phantom: 'Console', suggestion: "Console is C# — use System.out.println() or Scanner in Java" },
341
+ { pattern: /\bvar\s+\w+\s*:\s*\w+\s*=/, module: 'syntax', phantom: 'var x: Type =', suggestion: "Java var doesn't use type annotation: use 'var x =' or 'Type x ='" },
342
+ { pattern: /\.sorted\s*\(\s*\)(?!\s*\.)/, module: 'List', phantom: '.sorted()', suggestion: "Use Collections.sort() or .stream().sorted() in Java" },
343
+ { pattern: /\.reversed\s*\(/, module: 'List', phantom: '.reversed()', suggestion: "Use Collections.reverse() in Java (pre-21) or .reversed() (Java 21+)" },
344
+ { pattern: /String\.format\s*\(\s*\$"/, module: 'String', phantom: 'String.format($"...")', suggestion: "String interpolation $\"\" is C# — use String.format(\"%s\", ...) in Java" },
345
+ { pattern: /\bnew\s+Map\s*[<(]/, module: 'Collections', phantom: 'new Map()', suggestion: "Use new HashMap<>() in Java — Map is an interface" },
346
+ { pattern: /\bnew\s+List\s*[<(]/, module: 'Collections', phantom: 'new List()', suggestion: "Use new ArrayList<>() in Java — List is an interface" },
347
+ ];
348
+ /**
349
+ * Node.js 22.x stdlib method signatures.
350
+ * Only the most commonly hallucinated modules are covered.
351
+ * Each set contains ALL public methods/properties accessible on the module.
352
+ */
353
+ const NODE_STDLIB_METHODS = {
354
+ fs: new Set([
355
+ // Sync methods
356
+ 'readFileSync', 'writeFileSync', 'appendFileSync', 'copyFileSync', 'renameSync',
357
+ 'unlinkSync', 'mkdirSync', 'rmdirSync', 'rmSync', 'readdirSync', 'statSync',
358
+ 'lstatSync', 'existsSync', 'accessSync', 'chmodSync', 'chownSync', 'closeSync',
359
+ 'fchmodSync', 'fchownSync', 'fdatasyncSync', 'fstatSync', 'fsyncSync',
360
+ 'ftruncateSync', 'futimesSync', 'linkSync', 'lutimesSync', 'mkdtempSync',
361
+ 'openSync', 'opendirSync', 'readSync', 'readlinkSync', 'realpathSync',
362
+ 'symlinkSync', 'truncateSync', 'utimesSync', 'writeSync', 'cpSync',
363
+ 'statfsSync', 'globSync',
364
+ // Async callback methods
365
+ 'readFile', 'writeFile', 'appendFile', 'copyFile', 'rename', 'unlink',
366
+ 'mkdir', 'rmdir', 'rm', 'readdir', 'stat', 'lstat', 'access', 'chmod',
367
+ 'chown', 'close', 'fchmod', 'fchown', 'fdatasync', 'fstat', 'fsync',
368
+ 'ftruncate', 'futimes', 'link', 'lutimes', 'mkdtemp', 'open', 'opendir',
369
+ 'read', 'readlink', 'realpath', 'symlink', 'truncate', 'utimes', 'write',
370
+ 'cp', 'statfs', 'glob',
371
+ // Streams
372
+ 'createReadStream', 'createWriteStream',
373
+ // Watch
374
+ 'watch', 'watchFile', 'unwatchFile',
375
+ // Constants & promises
376
+ 'constants', 'promises',
377
+ ]),
378
+ path: new Set([
379
+ 'basename', 'delimiter', 'dirname', 'extname', 'format', 'isAbsolute',
380
+ 'join', 'normalize', 'parse', 'posix', 'relative', 'resolve', 'sep',
381
+ 'toNamespacedPath', 'win32', 'matchesGlob',
382
+ ]),
383
+ crypto: new Set([
384
+ 'createHash', 'createHmac', 'createCipheriv', 'createDecipheriv',
385
+ 'createSign', 'createVerify', 'createDiffieHellman', 'createDiffieHellmanGroup',
386
+ 'createECDH', 'createSecretKey', 'createPublicKey', 'createPrivateKey',
387
+ 'generateKey', 'generateKeyPair', 'generateKeyPairSync', 'generateKeySync',
388
+ 'generatePrime', 'generatePrimeSync',
389
+ 'getCiphers', 'getCurves', 'getDiffieHellman', 'getFips', 'getHashes',
390
+ 'getRandomValues', 'hash',
391
+ 'hkdf', 'hkdfSync',
392
+ 'pbkdf2', 'pbkdf2Sync',
393
+ 'privateDecrypt', 'privateEncrypt', 'publicDecrypt', 'publicEncrypt',
394
+ 'randomBytes', 'randomFillSync', 'randomFill', 'randomInt', 'randomUUID',
395
+ 'scrypt', 'scryptSync',
396
+ 'setEngine', 'setFips',
397
+ 'sign', 'verify',
398
+ 'subtle', 'timingSafeEqual',
399
+ 'constants', 'webcrypto', 'X509Certificate',
400
+ 'checkPrime', 'checkPrimeSync',
401
+ 'Certificate', 'Cipher', 'Decipher', 'DiffieHellman', 'DiffieHellmanGroup',
402
+ 'ECDH', 'Hash', 'Hmac', 'KeyObject', 'Sign', 'Verify',
403
+ ]),
404
+ os: new Set([
405
+ 'arch', 'availableParallelism', 'constants', 'cpus', 'devNull',
406
+ 'endianness', 'EOL', 'freemem', 'getPriority', 'homedir',
407
+ 'hostname', 'loadavg', 'machine', 'networkInterfaces', 'platform',
408
+ 'release', 'setPriority', 'tmpdir', 'totalmem', 'type',
409
+ 'uptime', 'userInfo', 'version',
410
+ ]),
411
+ child_process: new Set([
412
+ 'exec', 'execFile', 'execFileSync', 'execSync',
413
+ 'fork', 'spawn', 'spawnSync',
414
+ ]),
415
+ http: new Set([
416
+ 'createServer', 'get', 'globalAgent', 'request',
417
+ 'Agent', 'ClientRequest', 'Server', 'ServerResponse', 'IncomingMessage',
418
+ 'METHODS', 'STATUS_CODES', 'maxHeaderSize', 'validateHeaderName', 'validateHeaderValue',
419
+ 'setMaxIdleHTTPParsers',
420
+ ]),
421
+ https: new Set([
422
+ 'createServer', 'get', 'globalAgent', 'request',
423
+ 'Agent', 'Server',
424
+ ]),
425
+ url: new Set([
426
+ 'domainToASCII', 'domainToUnicode', 'fileURLToPath', 'format',
427
+ 'pathToFileURL', 'resolve', 'URL', 'URLSearchParams',
428
+ // Deprecated but still exist
429
+ 'parse', 'Url',
430
+ ]),
431
+ util: new Set([
432
+ 'callbackify', 'debuglog', 'deprecate', 'format', 'formatWithOptions',
433
+ 'getSystemErrorName', 'getSystemErrorMap', 'inherits', 'inspect',
434
+ 'isDeepStrictEqual', 'parseArgs', 'parseEnv', 'promisify',
435
+ 'stripVTControlCharacters', 'styleText',
436
+ 'TextDecoder', 'TextEncoder', 'MIMEType', 'MIMEParams',
437
+ 'types', 'toUSVString', 'transferableAbortController', 'transferableAbortSignal',
438
+ 'aborted',
439
+ ]),
440
+ stream: new Set([
441
+ 'Readable', 'Writable', 'Duplex', 'Transform', 'PassThrough',
442
+ 'pipeline', 'finished', 'compose', 'addAbortSignal',
443
+ 'getDefaultHighWaterMark', 'setDefaultHighWaterMark',
444
+ 'promises', 'consumers',
445
+ ]),
446
+ events: new Set([
447
+ 'EventEmitter', 'once', 'on', 'getEventListeners',
448
+ 'setMaxListeners', 'listenerCount', 'addAbortListener',
449
+ 'getMaxListeners', 'EventEmitterAsyncResource',
450
+ ]),
451
+ buffer: new Set([
452
+ 'Buffer', 'SlowBuffer', 'transcode',
453
+ 'constants', 'kMaxLength', 'kStringMaxLength',
454
+ 'atob', 'btoa', 'isAscii', 'isUtf8', 'resolveObjectURL',
455
+ 'Blob', 'File',
456
+ ]),
457
+ querystring: new Set([
458
+ 'decode', 'encode', 'escape', 'parse', 'stringify', 'unescape',
459
+ ]),
460
+ net: new Set([
461
+ 'createServer', 'createConnection', 'connect',
462
+ 'isIP', 'isIPv4', 'isIPv6',
463
+ 'Server', 'Socket', 'BlockList', 'SocketAddress',
464
+ 'getDefaultAutoSelectFamily', 'setDefaultAutoSelectFamily',
465
+ 'getDefaultAutoSelectFamilyAttemptTimeout', 'setDefaultAutoSelectFamilyAttemptTimeout',
466
+ ]),
467
+ dns: new Set([
468
+ 'lookup', 'lookupService', 'resolve', 'resolve4', 'resolve6',
469
+ 'resolveAny', 'resolveCname', 'resolveCaa', 'resolveMx', 'resolveNaptr',
470
+ 'resolveNs', 'resolvePtr', 'resolveSoa', 'resolveSrv', 'resolveTxt',
471
+ 'reverse', 'setServers', 'getServers', 'setDefaultResultOrder',
472
+ 'getDefaultResultOrder',
473
+ 'promises', 'Resolver', 'ADDRCONFIG', 'V4MAPPED', 'ALL',
474
+ ]),
475
+ tls: new Set([
476
+ 'createServer', 'connect', 'createSecureContext', 'createSecurePair',
477
+ 'getCiphers', 'rootCertificates', 'DEFAULT_ECDH_CURVE', 'DEFAULT_MAX_VERSION',
478
+ 'DEFAULT_MIN_VERSION', 'DEFAULT_CIPHERS',
479
+ 'Server', 'TLSSocket', 'SecureContext',
480
+ ]),
481
+ zlib: new Set([
482
+ 'createGzip', 'createGunzip', 'createDeflate', 'createInflate',
483
+ 'createDeflateRaw', 'createInflateRaw', 'createBrotliCompress',
484
+ 'createBrotliDecompress', 'createUnzip',
485
+ 'gzip', 'gunzip', 'deflate', 'inflate', 'deflateRaw', 'inflateRaw',
486
+ 'brotliCompress', 'brotliDecompress', 'unzip',
487
+ 'gzipSync', 'gunzipSync', 'deflateSync', 'inflateSync',
488
+ 'deflateRawSync', 'inflateRawSync', 'brotliCompressSync',
489
+ 'brotliDecompressSync', 'unzipSync',
490
+ 'constants',
491
+ ]),
492
+ readline: new Set([
493
+ 'createInterface', 'clearLine', 'clearScreenDown', 'cursorTo',
494
+ 'moveCursor', 'emitKeypressEvents',
495
+ 'Interface', 'InterfaceConstructor', 'promises',
496
+ ]),
497
+ cluster: new Set([
498
+ 'disconnect', 'fork', 'isMaster', 'isPrimary', 'isWorker',
499
+ 'schedulingPolicy', 'settings', 'setupMaster', 'setupPrimary',
500
+ 'worker', 'workers',
501
+ 'Worker', 'SCHED_NONE', 'SCHED_RR',
502
+ ]),
503
+ worker_threads: new Set([
504
+ 'isMainThread', 'parentPort', 'resourceLimits', 'threadId',
505
+ 'workerData', 'getEnvironmentData', 'setEnvironmentData',
506
+ 'markAsUntransferable', 'moveMessagePortToContext', 'receiveMessageOnPort',
507
+ 'BroadcastChannel', 'MessageChannel', 'MessagePort', 'Worker',
508
+ 'SHARE_ENV',
509
+ ]),
510
+ timers: new Set([
511
+ 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval',
512
+ 'setImmediate', 'clearImmediate',
513
+ 'promises',
514
+ ]),
515
+ perf_hooks: new Set([
516
+ 'performance', 'PerformanceObserver', 'PerformanceEntry',
517
+ 'PerformanceMark', 'PerformanceMeasure', 'PerformanceNodeTiming',
518
+ 'PerformanceResourceTiming', 'monitorEventLoopDelay',
519
+ 'createHistogram',
520
+ ]),
521
+ assert: new Set([
522
+ 'ok', 'fail', 'equal', 'notEqual', 'deepEqual', 'notDeepEqual',
523
+ 'deepStrictEqual', 'notDeepStrictEqual', 'strictEqual', 'notStrictEqual',
524
+ 'throws', 'doesNotThrow', 'rejects', 'doesNotReject',
525
+ 'ifError', 'match', 'doesNotMatch',
526
+ 'strict', 'AssertionError', 'CallTracker',
527
+ ]),
528
+ };
529
+ /**
530
+ * Python 3.12+ stdlib method signatures.
531
+ * Covers the most commonly hallucinated modules.
532
+ */
533
+ const PYTHON_STDLIB_METHODS = {
534
+ os: new Set([
535
+ 'getcwd', 'chdir', 'listdir', 'scandir', 'mkdir', 'makedirs',
536
+ 'rmdir', 'removedirs', 'remove', 'unlink', 'rename', 'renames',
537
+ 'replace', 'stat', 'lstat', 'fstat', 'chmod', 'chown', 'link',
538
+ 'symlink', 'readlink', 'walk', 'fwalk', 'path', 'environ',
539
+ 'getenv', 'putenv', 'unsetenv', 'getpid', 'getppid', 'getuid',
540
+ 'getgid', 'system', 'popen', 'execv', 'execve', 'execvp',
541
+ 'execvpe', '_exit', 'fork', 'kill', 'wait', 'waitpid',
542
+ 'cpu_count', 'urandom', 'sep', 'linesep', 'devnull', 'curdir',
543
+ 'pardir', 'extsep', 'altsep', 'pathsep', 'name', 'access',
544
+ 'open', 'close', 'read', 'write', 'pipe', 'dup', 'dup2',
545
+ 'ftruncate', 'isatty', 'lseek', 'terminal_size', 'get_terminal_size',
546
+ 'get_blocking', 'set_blocking', 'add_dll_directory',
547
+ 'get_exec_path', 'getlogin', 'strerror', 'umask',
548
+ 'truncate', 'fchdir', 'fchmod', 'fchown',
549
+ ]),
550
+ json: new Set([
551
+ 'dump', 'dumps', 'load', 'loads',
552
+ 'JSONDecoder', 'JSONEncoder', 'JSONDecodeError',
553
+ 'tool',
554
+ ]),
555
+ sys: new Set([
556
+ 'argv', 'exit', 'path', 'modules', 'stdin', 'stdout', 'stderr',
557
+ 'version', 'version_info', 'platform', 'executable', 'prefix',
558
+ 'exec_prefix', 'maxsize', 'maxunicode', 'byteorder', 'builtin_module_names',
559
+ 'flags', 'float_info', 'hash_info', 'implementation', 'int_info',
560
+ 'getdefaultencoding', 'getfilesystemencoding', 'getrecursionlimit',
561
+ 'getrefcount', 'getsizeof', 'gettrace', 'getprofile',
562
+ 'setrecursionlimit', 'settrace', 'setprofile',
563
+ 'exc_info', 'last_type', 'last_value', 'last_traceback',
564
+ 'api_version', 'copyright', 'dont_write_bytecode',
565
+ 'ps1', 'ps2', 'intern', 'is_finalizing',
566
+ 'orig_argv', 'platlibdir', 'stdlib_module_names',
567
+ 'thread_info', 'unraisablehook', 'winver',
568
+ 'addaudithook', 'audit', 'breakpointhook',
569
+ 'call_tracing', 'displayhook', 'excepthook',
570
+ 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
571
+ 'getallocatedblocks', 'getwindowsversion',
572
+ 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth',
573
+ 'set_int_max_str_digits', 'get_int_max_str_digits',
574
+ 'activate_stack_trampoline', 'deactivate_stack_trampoline',
575
+ 'is_stack_trampoline_active', 'last_exc',
576
+ 'monitoring', 'exception',
577
+ ]),
578
+ re: new Set([
579
+ 'compile', 'search', 'match', 'fullmatch', 'split', 'findall',
580
+ 'finditer', 'sub', 'subn', 'escape', 'purge', 'error',
581
+ 'Pattern', 'Match',
582
+ 'A', 'ASCII', 'DEBUG', 'DOTALL', 'I', 'IGNORECASE',
583
+ 'L', 'LOCALE', 'M', 'MULTILINE', 'NOFLAG',
584
+ 'S', 'U', 'UNICODE', 'VERBOSE', 'X',
585
+ ]),
586
+ math: new Set([
587
+ 'ceil', 'comb', 'copysign', 'fabs', 'factorial', 'floor',
588
+ 'fmod', 'frexp', 'fsum', 'gcd', 'isclose', 'isfinite',
589
+ 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'log', 'log10',
590
+ 'log1p', 'log2', 'modf', 'perm', 'pow', 'prod', 'remainder',
591
+ 'trunc', 'ulp', 'nextafter', 'sumprod',
592
+ 'exp', 'exp2', 'expm1', 'sqrt', 'cbrt',
593
+ 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh',
594
+ 'cos', 'cosh', 'degrees', 'dist', 'hypot', 'radians',
595
+ 'sin', 'sinh', 'tan', 'tanh',
596
+ 'erf', 'erfc', 'gamma', 'lgamma',
597
+ 'pi', 'e', 'tau', 'inf', 'nan',
598
+ ]),
599
+ datetime: new Set([
600
+ 'date', 'time', 'datetime', 'timedelta', 'timezone', 'tzinfo',
601
+ 'MINYEAR', 'MAXYEAR', 'UTC',
602
+ ]),
603
+ pathlib: new Set([
604
+ 'Path', 'PurePath', 'PurePosixPath', 'PureWindowsPath',
605
+ 'PosixPath', 'WindowsPath',
606
+ ]),
607
+ subprocess: new Set([
608
+ 'run', 'call', 'check_call', 'check_output', 'Popen',
609
+ 'PIPE', 'STDOUT', 'DEVNULL', 'CompletedProcess',
610
+ 'CalledProcessError', 'SubprocessError', 'TimeoutExpired',
611
+ 'getoutput', 'getstatusoutput',
612
+ ]),
613
+ shutil: new Set([
614
+ 'copy', 'copy2', 'copyfile', 'copyfileobj', 'copymode', 'copystat',
615
+ 'copytree', 'rmtree', 'move', 'disk_usage', 'chown', 'which',
616
+ 'make_archive', 'get_archive_formats', 'register_archive_format',
617
+ 'unregister_archive_format', 'unpack_archive', 'get_unpack_formats',
618
+ 'register_unpack_format', 'unregister_unpack_format',
619
+ 'get_terminal_size', 'SameFileError',
620
+ ]),
621
+ collections: new Set([
622
+ 'ChainMap', 'Counter', 'OrderedDict', 'UserDict', 'UserList',
623
+ 'UserString', 'abc', 'defaultdict', 'deque', 'namedtuple',
624
+ ]),
625
+ hashlib: new Set([
626
+ 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
627
+ 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512',
628
+ 'blake2b', 'blake2s', 'shake_128', 'shake_256',
629
+ 'new', 'algorithms_available', 'algorithms_guaranteed',
630
+ 'pbkdf2_hmac', 'scrypt', 'file_digest',
631
+ ]),
632
+ random: new Set([
633
+ 'seed', 'getstate', 'setstate', 'getrandbits',
634
+ 'randrange', 'randint', 'choice', 'choices', 'shuffle', 'sample',
635
+ 'random', 'uniform', 'triangular', 'betavariate', 'expovariate',
636
+ 'gammavariate', 'gauss', 'lognormvariate', 'normalvariate',
637
+ 'vonmisesvariate', 'paretovariate', 'weibullvariate',
638
+ 'Random', 'SystemRandom', 'randbytes',
639
+ ]),
640
+ time: new Set([
641
+ 'time', 'time_ns', 'clock_gettime', 'clock_gettime_ns',
642
+ 'clock_settime', 'clock_settime_ns', 'clock_getres',
643
+ 'gmtime', 'localtime', 'mktime', 'asctime', 'ctime',
644
+ 'strftime', 'strptime', 'sleep', 'monotonic', 'monotonic_ns',
645
+ 'perf_counter', 'perf_counter_ns', 'process_time', 'process_time_ns',
646
+ 'thread_time', 'thread_time_ns', 'get_clock_info',
647
+ 'struct_time', 'timezone', 'altzone', 'daylight', 'tzname',
648
+ 'CLOCK_BOOTTIME', 'CLOCK_MONOTONIC', 'CLOCK_MONOTONIC_RAW',
649
+ 'CLOCK_PROCESS_CPUTIME_ID', 'CLOCK_REALTIME',
650
+ 'CLOCK_TAI', 'CLOCK_THREAD_CPUTIME_ID',
651
+ ]),
652
+ csv: new Set([
653
+ 'reader', 'writer', 'DictReader', 'DictWriter',
654
+ 'Sniffer', 'register_dialect', 'unregister_dialect',
655
+ 'get_dialect', 'list_dialects', 'field_size_limit',
656
+ 'QUOTE_ALL', 'QUOTE_MINIMAL', 'QUOTE_NONNUMERIC', 'QUOTE_NONE',
657
+ 'QUOTE_NOTNULL', 'QUOTE_STRINGS',
658
+ 'Dialect', 'Error', 'excel', 'excel_tab', 'unix_dialect',
659
+ ]),
660
+ logging: new Set([
661
+ 'getLogger', 'basicConfig', 'shutdown', 'setLoggerClass',
662
+ 'setLogRecordFactory', 'lastResort', 'captureWarnings',
663
+ 'debug', 'info', 'warning', 'error', 'critical', 'exception',
664
+ 'log', 'disable', 'addLevelName', 'getLevelName', 'getLevelNamesMapping',
665
+ 'makeLogRecord', 'getHandlerByName', 'getHandlerNames',
666
+ 'Logger', 'Handler', 'Formatter', 'Filter', 'LogRecord',
667
+ 'StreamHandler', 'FileHandler', 'NullHandler',
668
+ 'BufferingFormatter', 'LoggerAdapter', 'PercentStyle',
669
+ 'StrFormatStyle', 'StringTemplateStyle',
670
+ 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL',
671
+ 'FATAL', 'WARN', 'NOTSET',
672
+ 'raiseExceptions', 'root',
673
+ 'handlers', 'config',
674
+ ]),
675
+ };