@rigour-labs/core 2.22.0 → 3.0.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 (103) hide show
  1. package/README.md +58 -0
  2. package/dist/context.test.js +2 -3
  3. package/dist/environment.test.js +2 -1
  4. package/dist/gates/agent-team.d.ts +2 -1
  5. package/dist/gates/agent-team.js +1 -0
  6. package/dist/gates/base.d.ts +3 -1
  7. package/dist/gates/base.js +3 -0
  8. package/dist/gates/checkpoint.d.ts +2 -1
  9. package/dist/gates/checkpoint.js +3 -2
  10. package/dist/gates/context-window-artifacts.d.ts +2 -1
  11. package/dist/gates/context-window-artifacts.js +6 -3
  12. package/dist/gates/context.d.ts +2 -1
  13. package/dist/gates/context.js +1 -0
  14. package/dist/gates/coverage.js +3 -1
  15. package/dist/gates/dependency.js +5 -5
  16. package/dist/gates/duplication-drift.d.ts +2 -1
  17. package/dist/gates/duplication-drift.js +4 -1
  18. package/dist/gates/environment.js +4 -4
  19. package/dist/gates/hallucinated-imports.d.ts +21 -2
  20. package/dist/gates/hallucinated-imports.js +116 -2
  21. package/dist/gates/inconsistent-error-handling.d.ts +2 -1
  22. package/dist/gates/inconsistent-error-handling.js +21 -7
  23. package/dist/gates/promise-safety.d.ts +68 -0
  24. package/dist/gates/promise-safety.js +509 -0
  25. package/dist/gates/retry-loop-breaker.d.ts +2 -1
  26. package/dist/gates/retry-loop-breaker.js +2 -1
  27. package/dist/gates/runner.js +34 -1
  28. package/dist/gates/safety.d.ts +2 -1
  29. package/dist/gates/safety.js +2 -1
  30. package/dist/gates/security-patterns.d.ts +2 -1
  31. package/dist/gates/security-patterns.js +1 -0
  32. package/dist/gates/structure.js +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +1 -0
  35. package/dist/services/fix-packet-service.d.ts +0 -1
  36. package/dist/services/fix-packet-service.js +9 -14
  37. package/dist/services/score-history.d.ts +54 -0
  38. package/dist/services/score-history.js +122 -0
  39. package/dist/templates/index.js +169 -0
  40. package/dist/types/fix-packet.d.ts +5 -5
  41. package/dist/types/fix-packet.js +1 -1
  42. package/dist/types/index.d.ts +153 -0
  43. package/dist/types/index.js +19 -0
  44. package/package.json +21 -1
  45. package/src/context.test.ts +0 -256
  46. package/src/discovery.test.ts +0 -88
  47. package/src/discovery.ts +0 -112
  48. package/src/environment.test.ts +0 -115
  49. package/src/gates/agent-team.test.ts +0 -134
  50. package/src/gates/agent-team.ts +0 -210
  51. package/src/gates/ast-handlers/base.ts +0 -13
  52. package/src/gates/ast-handlers/python.ts +0 -145
  53. package/src/gates/ast-handlers/python_parser.py +0 -181
  54. package/src/gates/ast-handlers/typescript.ts +0 -264
  55. package/src/gates/ast-handlers/universal.ts +0 -184
  56. package/src/gates/ast.ts +0 -54
  57. package/src/gates/base.ts +0 -28
  58. package/src/gates/checkpoint.test.ts +0 -135
  59. package/src/gates/checkpoint.ts +0 -311
  60. package/src/gates/content.ts +0 -51
  61. package/src/gates/context-window-artifacts.ts +0 -277
  62. package/src/gates/context.ts +0 -270
  63. package/src/gates/coverage.ts +0 -74
  64. package/src/gates/dependency.ts +0 -108
  65. package/src/gates/duplication-drift.ts +0 -231
  66. package/src/gates/environment.ts +0 -94
  67. package/src/gates/file.ts +0 -46
  68. package/src/gates/hallucinated-imports.ts +0 -361
  69. package/src/gates/inconsistent-error-handling.ts +0 -254
  70. package/src/gates/retry-loop-breaker.ts +0 -151
  71. package/src/gates/runner.ts +0 -188
  72. package/src/gates/safety.ts +0 -56
  73. package/src/gates/security-patterns.test.ts +0 -162
  74. package/src/gates/security-patterns.ts +0 -306
  75. package/src/gates/structure.ts +0 -36
  76. package/src/index.ts +0 -13
  77. package/src/pattern-index/embeddings.ts +0 -84
  78. package/src/pattern-index/index.ts +0 -59
  79. package/src/pattern-index/indexer.test.ts +0 -276
  80. package/src/pattern-index/indexer.ts +0 -1023
  81. package/src/pattern-index/matcher.test.ts +0 -293
  82. package/src/pattern-index/matcher.ts +0 -493
  83. package/src/pattern-index/overrides.ts +0 -235
  84. package/src/pattern-index/security.ts +0 -151
  85. package/src/pattern-index/staleness.test.ts +0 -313
  86. package/src/pattern-index/staleness.ts +0 -568
  87. package/src/pattern-index/types.ts +0 -339
  88. package/src/safety.test.ts +0 -53
  89. package/src/services/adaptive-thresholds.test.ts +0 -189
  90. package/src/services/adaptive-thresholds.ts +0 -275
  91. package/src/services/context-engine.ts +0 -104
  92. package/src/services/fix-packet-service.ts +0 -42
  93. package/src/services/state-service.ts +0 -138
  94. package/src/smoke.test.ts +0 -18
  95. package/src/templates/index.ts +0 -338
  96. package/src/types/fix-packet.ts +0 -32
  97. package/src/types/index.ts +0 -200
  98. package/src/utils/logger.ts +0 -43
  99. package/src/utils/scanner.test.ts +0 -37
  100. package/src/utils/scanner.ts +0 -43
  101. package/tsconfig.json +0 -10
  102. package/vitest.config.ts +0 -7
  103. package/vitest.setup.ts +0 -30
@@ -1,94 +0,0 @@
1
- import { Gate, GateContext } from './base.js';
2
- import { Failure, Gates } from '../types/index.js';
3
- import { execa } from 'execa';
4
- import semver from 'semver';
5
- import fs from 'fs-extra';
6
- import path from 'path';
7
-
8
- export class EnvironmentGate extends Gate {
9
- constructor(private config: Gates) {
10
- super('environment-alignment', 'Environment & Tooling Alignment');
11
- }
12
-
13
- async run(context: GateContext): Promise<Failure[]> {
14
- const failures: Failure[] = [];
15
- const envConfig = this.config.environment;
16
- if (!envConfig || !envConfig.enabled) return [];
17
-
18
- const contracts = envConfig.enforce_contracts ? await this.discoverContracts(context.cwd) : {};
19
- const toolsToCheck = { ...contracts, ...(envConfig.tools || {}) };
20
-
21
- // 1. Verify Tool Versions
22
- for (const [tool, range] of Object.entries(toolsToCheck)) {
23
- // Ensure range is a string
24
- const semverRange = String(range);
25
- try {
26
- const { stdout } = await execa(tool, ['--version'], { shell: true });
27
- const versionMatch = stdout.match(/(\d+\.\d+\.\d+)/);
28
-
29
- if (versionMatch) {
30
- const version = versionMatch[1];
31
- if (!semver.satisfies(version, semverRange)) {
32
- failures.push(this.createFailure(
33
- `Environment Alignment: Tool '${tool}' version mismatch.`,
34
- [],
35
- `Project requires '${tool} ${semverRange}' (discovered from contract), but found version '${version}'. Please align your local environment to prevent drift.`
36
- ));
37
- }
38
- } else {
39
- failures.push(this.createFailure(
40
- `Environment Alignment: Could not determine version for '${tool}'.`,
41
- [],
42
- `Ensure '${tool} --version' returns a standard SemVer string.`
43
- ));
44
- }
45
- } catch (e) {
46
- failures.push(this.createFailure(
47
- `Environment Alignment: Required tool '${tool}' is missing.`,
48
- [],
49
- `Install '${tool}' and ensure it is in your $PATH.`
50
- ));
51
- }
52
- }
53
-
54
- // 2. Verify Required Env Vars
55
- const requiredEnv = envConfig.required_env || [];
56
- for (const envVar of requiredEnv) {
57
- if (!process.env[envVar]) {
58
- failures.push(this.createFailure(
59
- `Environment Alignment: Missing required environment variable '${envVar}'.`,
60
- [],
61
- `Ensure '${envVar}' is defined in your environment or .env file.`
62
- ));
63
- }
64
- }
65
-
66
- return failures;
67
- }
68
-
69
- private async discoverContracts(cwd: string): Promise<Record<string, string>> {
70
- const contracts: Record<string, string> = {};
71
-
72
- // 1. Scan pyproject.toml (for ruff, mypy)
73
- const pyprojectPath = path.join(cwd, 'pyproject.toml');
74
- if (await fs.pathExists(pyprojectPath)) {
75
- const content = await fs.readFile(pyprojectPath, 'utf-8');
76
- // SME Logic: Look for ruff and mypy version constraints
77
- // Handle both ruff = "^0.14.0" and ruff = { version = "^0.14.0" }
78
- const ruffMatch = content.match(/ruff\s*=\s*(?:['"]([^'"]+)['"]|\{\s*version\s*=\s*['"]([^'"]+)['"]\s*\})/);
79
- if (ruffMatch) contracts['ruff'] = ruffMatch[1] || ruffMatch[2];
80
-
81
- const mypyMatch = content.match(/mypy\s*=\s*(?:['"]([^'"]+)['"]|\{\s*version\s*=\s*['"]([^'"]+)['"]\s*\})/);
82
- if (mypyMatch) contracts['mypy'] = mypyMatch[1] || mypyMatch[2];
83
- }
84
-
85
- // 2. Scan package.json (for node/npm/pnpm)
86
- const pkgPath = path.join(cwd, 'package.json');
87
- if (await fs.pathExists(pkgPath)) {
88
- const pkg = await fs.readJson(pkgPath);
89
- if (pkg.engines?.node) contracts['node'] = pkg.engines.node;
90
- }
91
-
92
- return contracts;
93
- }
94
- }
package/src/gates/file.ts DELETED
@@ -1,46 +0,0 @@
1
- import { Gate, GateContext } from './base.js';
2
- import { Failure } from '../types/index.js';
3
- import { FileScanner } from '../utils/scanner.js';
4
-
5
- export interface FileGateConfig {
6
- maxLines: number;
7
- }
8
-
9
- export class FileGate extends Gate {
10
- constructor(private config: FileGateConfig) {
11
- super('file-size', 'File Size Limit');
12
- }
13
-
14
- async run(context: GateContext): Promise<Failure[]> {
15
- const files = await FileScanner.findFiles({
16
- cwd: context.cwd,
17
- ignore: context.ignore,
18
- patterns: context.patterns
19
- });
20
- const contents = await FileScanner.readFiles(context.cwd, files);
21
-
22
- const violations: string[] = [];
23
- for (const [file, content] of contents) {
24
- const lines = content.split('\n').length;
25
- if (lines > this.config.maxLines) {
26
- violations.push(`${file} (${lines} lines)`);
27
- }
28
- }
29
-
30
- if (violations.length > 0) {
31
- return [
32
- this.createFailure(
33
- `The following files exceed the maximum limit of ${this.config.maxLines} lines:`,
34
- violations,
35
- 'Break these files into smaller, more modular components to improve maintainability (SOLID - Single Responsibility Principle).',
36
- undefined,
37
- undefined,
38
- undefined,
39
- 'low'
40
- ),
41
- ];
42
- }
43
-
44
- return [];
45
- }
46
- }
@@ -1,361 +0,0 @@
1
- /**
2
- * Hallucinated Imports Gate
3
- *
4
- * Detects imports that reference modules which don't exist in the project.
5
- * This is an AI-specific failure mode — LLMs confidently generate import
6
- * statements for packages, files, or modules that were never installed
7
- * or created.
8
- *
9
- * Detection strategy:
10
- * 1. Parse all import/require statements
11
- * 2. For relative imports: verify the target file exists
12
- * 3. For package imports: verify the package exists in node_modules or package.json
13
- * 4. For Python imports: verify the module exists in the project or site-packages
14
- *
15
- * @since v2.16.0
16
- */
17
-
18
- import { Gate, GateContext } from './base.js';
19
- import { Failure } from '../types/index.js';
20
- import { FileScanner } from '../utils/scanner.js';
21
- import { Logger } from '../utils/logger.js';
22
- import fs from 'fs-extra';
23
- import path from 'path';
24
-
25
- export interface HallucinatedImport {
26
- file: string;
27
- line: number;
28
- importPath: string;
29
- type: 'relative' | 'package' | 'python';
30
- reason: string;
31
- }
32
-
33
- export interface HallucinatedImportsConfig {
34
- enabled?: boolean;
35
- check_relative?: boolean; // Check relative imports resolve to real files
36
- check_packages?: boolean; // Check npm/pip packages exist
37
- ignore_patterns?: string[]; // Import patterns to ignore (e.g. asset imports)
38
- }
39
-
40
- export class HallucinatedImportsGate extends Gate {
41
- private config: Required<Omit<HallucinatedImportsConfig, 'ignore_patterns'>> & { ignore_patterns: string[] };
42
-
43
- constructor(config: HallucinatedImportsConfig = {}) {
44
- super('hallucinated-imports', 'Hallucinated Import Detection');
45
- this.config = {
46
- enabled: config.enabled ?? true,
47
- check_relative: config.check_relative ?? true,
48
- check_packages: config.check_packages ?? true,
49
- ignore_patterns: config.ignore_patterns ?? [
50
- '\\.css$', '\\.scss$', '\\.less$', '\\.svg$', '\\.png$', '\\.jpg$',
51
- '\\.json$', '\\.wasm$', '\\.graphql$', '\\.gql$',
52
- ],
53
- };
54
- }
55
-
56
- async run(context: GateContext): Promise<Failure[]> {
57
- if (!this.config.enabled) return [];
58
-
59
- const failures: Failure[] = [];
60
- const hallucinated: HallucinatedImport[] = [];
61
-
62
- const files = await FileScanner.findFiles({
63
- cwd: context.cwd,
64
- patterns: ['**/*.{ts,js,tsx,jsx,py}'],
65
- ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/build/**', '**/.venv/**'],
66
- });
67
-
68
- Logger.info(`Hallucinated Imports: Scanning ${files.length} files`);
69
-
70
- // Build lookup sets for fast resolution
71
- const projectFiles = new Set(files.map(f => f.replace(/\\/g, '/')));
72
- const packageJson = await this.loadPackageJson(context.cwd);
73
- const allDeps = new Set([
74
- ...Object.keys(packageJson?.dependencies || {}),
75
- ...Object.keys(packageJson?.devDependencies || {}),
76
- ...Object.keys(packageJson?.peerDependencies || {}),
77
- ]);
78
-
79
- // Check if node_modules exists (for package verification)
80
- const hasNodeModules = await fs.pathExists(path.join(context.cwd, 'node_modules'));
81
-
82
- for (const file of files) {
83
- try {
84
- const fullPath = path.join(context.cwd, file);
85
- const content = await fs.readFile(fullPath, 'utf-8');
86
- const ext = path.extname(file);
87
-
88
- if (['.ts', '.js', '.tsx', '.jsx'].includes(ext)) {
89
- await this.checkJSImports(content, file, context.cwd, projectFiles, allDeps, hasNodeModules, hallucinated);
90
- } else if (ext === '.py') {
91
- await this.checkPyImports(content, file, context.cwd, projectFiles, hallucinated);
92
- }
93
- } catch (e) { }
94
- }
95
-
96
- // Group hallucinated imports by file for cleaner output
97
- const byFile = new Map<string, HallucinatedImport[]>();
98
- for (const h of hallucinated) {
99
- const existing = byFile.get(h.file) || [];
100
- existing.push(h);
101
- byFile.set(h.file, existing);
102
- }
103
-
104
- for (const [file, imports] of byFile) {
105
- const details = imports.map(i => ` L${i.line}: import '${i.importPath}' — ${i.reason}`).join('\n');
106
-
107
- failures.push(this.createFailure(
108
- `Hallucinated imports in ${file}:\n${details}`,
109
- [file],
110
- `These imports reference modules that don't exist. Remove or replace with real modules. AI models often "hallucinate" package names or file paths.`,
111
- 'Hallucinated Imports',
112
- imports[0].line,
113
- undefined,
114
- 'critical'
115
- ));
116
- }
117
-
118
- return failures;
119
- }
120
-
121
- private async checkJSImports(
122
- content: string,
123
- file: string,
124
- cwd: string,
125
- projectFiles: Set<string>,
126
- allDeps: Set<string>,
127
- hasNodeModules: boolean,
128
- hallucinated: HallucinatedImport[]
129
- ): Promise<void> {
130
- const lines = content.split('\n');
131
-
132
- // Match: import ... from '...', require('...'), import('...')
133
- const importPatterns = [
134
- /import\s+(?:{[^}]*}|\*\s+as\s+\w+|\w+(?:\s*,\s*{[^}]*})?)\s+from\s+['"]([^'"]+)['"]/g,
135
- /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
136
- /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
137
- /export\s+(?:{[^}]*}|\*)\s+from\s+['"]([^'"]+)['"]/g,
138
- ];
139
-
140
- for (let i = 0; i < lines.length; i++) {
141
- const line = lines[i];
142
-
143
- for (const pattern of importPatterns) {
144
- pattern.lastIndex = 0;
145
- let match;
146
- while ((match = pattern.exec(line)) !== null) {
147
- const importPath = match[1];
148
-
149
- // Skip ignored patterns (assets, etc.)
150
- if (this.shouldIgnore(importPath)) continue;
151
-
152
- if (importPath.startsWith('.')) {
153
- // Relative import — check file exists
154
- if (this.config.check_relative) {
155
- const resolved = this.resolveRelativeImport(file, importPath, projectFiles);
156
- if (!resolved) {
157
- hallucinated.push({
158
- file, line: i + 1, importPath, type: 'relative',
159
- reason: `File not found: ${importPath}`,
160
- });
161
- }
162
- }
163
- } else {
164
- // Package import — check it exists
165
- if (this.config.check_packages) {
166
- const pkgName = this.extractPackageName(importPath);
167
-
168
- // Skip Node.js built-ins
169
- if (this.isNodeBuiltin(pkgName)) continue;
170
-
171
- if (!allDeps.has(pkgName)) {
172
- // Double-check node_modules if available
173
- if (hasNodeModules) {
174
- const pkgPath = path.join(cwd, 'node_modules', pkgName);
175
- if (await fs.pathExists(pkgPath)) continue;
176
- }
177
-
178
- hallucinated.push({
179
- file, line: i + 1, importPath, type: 'package',
180
- reason: `Package '${pkgName}' not in package.json dependencies`,
181
- });
182
- }
183
- }
184
- }
185
- }
186
- }
187
- }
188
- }
189
-
190
- private async checkPyImports(
191
- content: string,
192
- file: string,
193
- cwd: string,
194
- projectFiles: Set<string>,
195
- hallucinated: HallucinatedImport[]
196
- ): Promise<void> {
197
- const lines = content.split('\n');
198
-
199
- for (let i = 0; i < lines.length; i++) {
200
- const line = lines[i].trim();
201
-
202
- // Match: from X import Y, import X
203
- const fromMatch = line.match(/^from\s+([\w.]+)\s+import/);
204
- const importMatch = line.match(/^import\s+([\w.]+)/);
205
-
206
- const modulePath = fromMatch?.[1] || importMatch?.[1];
207
- if (!modulePath) continue;
208
-
209
- // Skip standard library modules
210
- if (this.isPythonStdlib(modulePath)) continue;
211
-
212
- // Check if it's a relative project import
213
- if (modulePath.startsWith('.')) {
214
- // Relative Python import
215
- const pyFile = modulePath.replace(/\./g, '/') + '.py';
216
- const pyInit = modulePath.replace(/\./g, '/') + '/__init__.py';
217
- const fileDir = path.dirname(file);
218
- const resolved1 = path.join(fileDir, pyFile).replace(/\\/g, '/');
219
- const resolved2 = path.join(fileDir, pyInit).replace(/\\/g, '/');
220
-
221
- if (!projectFiles.has(resolved1) && !projectFiles.has(resolved2)) {
222
- hallucinated.push({
223
- file, line: i + 1, importPath: modulePath, type: 'python',
224
- reason: `Relative module '${modulePath}' not found in project`,
225
- });
226
- }
227
- } else {
228
- // Absolute import — check if it's a project module
229
- const topLevel = modulePath.split('.')[0];
230
- const pyFile = topLevel + '.py';
231
- const pyInit = topLevel + '/__init__.py';
232
-
233
- // If it matches a project file, it's a local import — verify it exists
234
- const isLocalModule = projectFiles.has(pyFile) || projectFiles.has(pyInit) ||
235
- [...projectFiles].some(f => f.startsWith(topLevel + '/'));
236
-
237
- // If not local and not stdlib, we can't easily verify pip packages
238
- // without a requirements.txt or pyproject.toml check
239
- if (isLocalModule) {
240
- // It's referencing a local module — verify the full path
241
- const fullModulePath = modulePath.replace(/\./g, '/');
242
- const candidates = [
243
- fullModulePath + '.py',
244
- fullModulePath + '/__init__.py',
245
- ];
246
- const exists = candidates.some(c => projectFiles.has(c));
247
- if (!exists && modulePath.includes('.')) {
248
- // Only flag deep module paths that partially resolve
249
- hallucinated.push({
250
- file, line: i + 1, importPath: modulePath, type: 'python',
251
- reason: `Module '${modulePath}' partially resolves but target not found`,
252
- });
253
- }
254
- }
255
- }
256
- }
257
- }
258
-
259
- private resolveRelativeImport(fromFile: string, importPath: string, projectFiles: Set<string>): boolean {
260
- const dir = path.dirname(fromFile);
261
- const resolved = path.join(dir, importPath).replace(/\\/g, '/');
262
-
263
- // Try exact match, then common extensions
264
- const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
265
- const indexFiles = extensions.map(ext => `${resolved}/index${ext}`);
266
-
267
- const candidates = [
268
- ...extensions.map(ext => resolved + ext),
269
- ...indexFiles,
270
- ];
271
-
272
- return candidates.some(c => projectFiles.has(c));
273
- }
274
-
275
- private extractPackageName(importPath: string): string {
276
- // Scoped packages: @scope/package/... → @scope/package
277
- if (importPath.startsWith('@')) {
278
- const parts = importPath.split('/');
279
- return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importPath;
280
- }
281
- // Regular packages: package/... → package
282
- return importPath.split('/')[0];
283
- }
284
-
285
- private shouldIgnore(importPath: string): boolean {
286
- return this.config.ignore_patterns.some(pattern => new RegExp(pattern).test(importPath));
287
- }
288
-
289
- private isNodeBuiltin(name: string): boolean {
290
- const builtins = new Set([
291
- 'assert', 'buffer', 'child_process', 'cluster', 'console', 'constants',
292
- 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2',
293
- 'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks',
294
- 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream',
295
- 'string_decoder', 'sys', 'timers', 'tls', 'trace_events', 'tty',
296
- 'url', 'util', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
297
- 'node:assert', 'node:buffer', 'node:child_process', 'node:cluster',
298
- 'node:console', 'node:constants', 'node:crypto', 'node:dgram',
299
- 'node:dns', 'node:domain', 'node:events', 'node:fs', 'node:http',
300
- 'node:http2', 'node:https', 'node:inspector', 'node:module', 'node:net',
301
- 'node:os', 'node:path', 'node:perf_hooks', 'node:process',
302
- 'node:punycode', 'node:querystring', 'node:readline', 'node:repl',
303
- 'node:stream', 'node:string_decoder', 'node:sys', 'node:timers',
304
- 'node:tls', 'node:trace_events', 'node:tty', 'node:url', 'node:util',
305
- 'node:v8', 'node:vm', 'node:wasi', 'node:worker_threads', 'node:zlib',
306
- 'fs-extra', // common enough to skip
307
- ]);
308
- return builtins.has(name) || name.startsWith('node:');
309
- }
310
-
311
- private isPythonStdlib(modulePath: string): boolean {
312
- const topLevel = modulePath.split('.')[0];
313
- const stdlibs = new Set([
314
- 'abc', 'aifc', 'argparse', 'array', 'ast', 'asyncio', 'atexit',
315
- 'base64', 'bdb', 'binascii', 'binhex', 'bisect', 'builtins',
316
- 'calendar', 'cgi', 'cgitb', 'chunk', 'cmath', 'cmd', 'code',
317
- 'codecs', 'codeop', 'collections', 'colorsys', 'compileall',
318
- 'concurrent', 'configparser', 'contextlib', 'contextvars', 'copy',
319
- 'copyreg', 'cProfile', 'csv', 'ctypes', 'curses', 'dataclasses',
320
- 'datetime', 'dbm', 'decimal', 'difflib', 'dis', 'distutils',
321
- 'doctest', 'email', 'encodings', 'enum', 'errno', 'faulthandler',
322
- 'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'fractions', 'ftplib',
323
- 'functools', 'gc', 'getopt', 'getpass', 'gettext', 'glob', 'grp',
324
- 'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http', 'idlelib',
325
- 'imaplib', 'imghdr', 'importlib', 'inspect', 'io', 'ipaddress',
326
- 'itertools', 'json', 'keyword', 'lib2to3', 'linecache', 'locale',
327
- 'logging', 'lzma', 'mailbox', 'mailcap', 'marshal', 'math',
328
- 'mimetypes', 'mmap', 'modulefinder', 'multiprocessing', 'netrc',
329
- 'nis', 'nntplib', 'numbers', 'operator', 'optparse', 'os',
330
- 'ossaudiodev', 'parser', 'pathlib', 'pdb', 'pickle', 'pickletools',
331
- 'pipes', 'pkgutil', 'platform', 'plistlib', 'poplib', 'posix',
332
- 'posixpath', 'pprint', 'profile', 'pstats', 'pty', 'pwd', 'py_compile',
333
- 'pyclbr', 'pydoc', 'queue', 'quopri', 'random', 're', 'readline',
334
- 'reprlib', 'resource', 'rlcompleter', 'runpy', 'sched', 'secrets',
335
- 'select', 'selectors', 'shelve', 'shlex', 'shutil', 'signal',
336
- 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver',
337
- 'spwd', 'sqlite3', 'sre_compile', 'sre_constants', 'sre_parse',
338
- 'ssl', 'stat', 'statistics', 'string', 'stringprep', 'struct',
339
- 'subprocess', 'sunau', 'symtable', 'sys', 'sysconfig', 'syslog',
340
- 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'termios', 'test',
341
- 'textwrap', 'threading', 'time', 'timeit', 'tkinter', 'token',
342
- 'tokenize', 'tomllib', 'trace', 'traceback', 'tracemalloc', 'tty',
343
- 'turtle', 'turtledemo', 'types', 'typing', 'unicodedata', 'unittest',
344
- 'urllib', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref',
345
- 'webbrowser', 'winreg', 'winsound', 'wsgiref', 'xdrlib', 'xml',
346
- 'xmlrpc', 'zipapp', 'zipfile', 'zipimport', 'zlib',
347
- '_thread', '__future__', '__main__',
348
- ]);
349
- return stdlibs.has(topLevel);
350
- }
351
-
352
- private async loadPackageJson(cwd: string): Promise<any> {
353
- try {
354
- const pkgPath = path.join(cwd, 'package.json');
355
- if (await fs.pathExists(pkgPath)) {
356
- return await fs.readJson(pkgPath);
357
- }
358
- } catch (e) { }
359
- return null;
360
- }
361
- }