@rigour-labs/core 3.0.1 → 3.0.2
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.
|
@@ -46,10 +46,26 @@ export declare class HallucinatedImportsGate extends Gate {
|
|
|
46
46
|
private isNodeBuiltin;
|
|
47
47
|
private isPythonStdlib;
|
|
48
48
|
/**
|
|
49
|
-
* Check Go imports — verify relative
|
|
50
|
-
*
|
|
49
|
+
* Check Go imports — verify project-relative package paths exist.
|
|
50
|
+
*
|
|
51
|
+
* Strategy:
|
|
52
|
+
* 1. Skip Go standard library (comprehensive list of 150+ packages)
|
|
53
|
+
* 2. Skip external modules (any path containing a dot → domain name)
|
|
54
|
+
* 3. Parse go.mod for the project module path
|
|
55
|
+
* 4. Only flag imports that match the project module prefix but don't resolve
|
|
56
|
+
*
|
|
57
|
+
* @since v3.0.1 — fixed false positives on Go stdlib (encoding/json, net/http, etc.)
|
|
51
58
|
*/
|
|
52
59
|
private checkGoImports;
|
|
60
|
+
/**
|
|
61
|
+
* Comprehensive Go standard library package list.
|
|
62
|
+
* Includes all packages from Go 1.22+ (latest stable).
|
|
63
|
+
* Go stdlib is identified by having NO dots in the import path.
|
|
64
|
+
* We maintain an explicit list for packages with slashes (e.g. encoding/json).
|
|
65
|
+
*
|
|
66
|
+
* @since v3.0.1
|
|
67
|
+
*/
|
|
68
|
+
private isGoStdlib;
|
|
53
69
|
/**
|
|
54
70
|
* Check Ruby imports — verify require_relative paths exist
|
|
55
71
|
*/
|
|
@@ -295,12 +295,31 @@ export class HallucinatedImportsGate extends Gate {
|
|
|
295
295
|
return stdlibs.has(topLevel);
|
|
296
296
|
}
|
|
297
297
|
/**
|
|
298
|
-
* Check Go imports — verify relative
|
|
299
|
-
*
|
|
298
|
+
* Check Go imports — verify project-relative package paths exist.
|
|
299
|
+
*
|
|
300
|
+
* Strategy:
|
|
301
|
+
* 1. Skip Go standard library (comprehensive list of 150+ packages)
|
|
302
|
+
* 2. Skip external modules (any path containing a dot → domain name)
|
|
303
|
+
* 3. Parse go.mod for the project module path
|
|
304
|
+
* 4. Only flag imports that match the project module prefix but don't resolve
|
|
305
|
+
*
|
|
306
|
+
* @since v3.0.1 — fixed false positives on Go stdlib (encoding/json, net/http, etc.)
|
|
300
307
|
*/
|
|
301
308
|
checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
302
309
|
const lines = content.split('\n');
|
|
303
310
|
let inImportBlock = false;
|
|
311
|
+
// Try to read go.mod for the module path
|
|
312
|
+
const goModPath = path.join(cwd, 'go.mod');
|
|
313
|
+
let modulePath = null;
|
|
314
|
+
try {
|
|
315
|
+
if (fs.pathExistsSync(goModPath)) {
|
|
316
|
+
const goMod = fs.readFileSync(goModPath, 'utf-8');
|
|
317
|
+
const moduleMatch = goMod.match(/^module\s+(\S+)/m);
|
|
318
|
+
if (moduleMatch)
|
|
319
|
+
modulePath = moduleMatch[1];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch { /* no go.mod — skip project-relative checks entirely */ }
|
|
304
323
|
for (let i = 0; i < lines.length; i++) {
|
|
305
324
|
const line = lines[i].trim();
|
|
306
325
|
// Detect import block: import ( ... )
|
|
@@ -312,30 +331,136 @@ export class HallucinatedImportsGate extends Gate {
|
|
|
312
331
|
inImportBlock = false;
|
|
313
332
|
continue;
|
|
314
333
|
}
|
|
315
|
-
// Single import: import "path"
|
|
316
|
-
const singleMatch = line.match(/^import\s+"([^"]+)"/);
|
|
317
|
-
const blockMatch = inImportBlock ? line.match(/^\s*"([^"]+)"/) : null;
|
|
334
|
+
// Single import: import "path" or import alias "path"
|
|
335
|
+
const singleMatch = line.match(/^import\s+(?:\w+\s+)?"([^"]+)"/);
|
|
336
|
+
const blockMatch = inImportBlock ? line.match(/^\s*(?:\w+\s+)?"([^"]+)"/) : null;
|
|
318
337
|
const importPath = singleMatch?.[1] || blockMatch?.[1];
|
|
319
338
|
if (!importPath)
|
|
320
339
|
continue;
|
|
321
|
-
// Skip Go
|
|
322
|
-
if (
|
|
340
|
+
// 1. Skip Go standard library — comprehensive list
|
|
341
|
+
if (this.isGoStdlib(importPath))
|
|
323
342
|
continue;
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
const hasMatchingFile = [...projectFiles].some(f => f.includes(dirPath));
|
|
343
|
+
// 2. If we have a module path, check project-relative imports FIRST
|
|
344
|
+
// (project imports like github.com/myorg/project/pkg also have dots)
|
|
345
|
+
if (modulePath && importPath.startsWith(modulePath + '/')) {
|
|
346
|
+
const relPath = importPath.slice(modulePath.length + 1);
|
|
347
|
+
const hasMatchingFile = [...projectFiles].some(f => f.endsWith('.go') && f.startsWith(relPath));
|
|
330
348
|
if (!hasMatchingFile) {
|
|
331
349
|
hallucinated.push({
|
|
332
350
|
file, line: i + 1, importPath, type: 'go',
|
|
333
|
-
reason: `Go import '${importPath}' — package
|
|
351
|
+
reason: `Go import '${importPath}' — package directory '${relPath}' not found in project`,
|
|
334
352
|
});
|
|
335
353
|
}
|
|
354
|
+
continue;
|
|
336
355
|
}
|
|
356
|
+
// 3. Skip external modules — any import containing a dot is a domain
|
|
357
|
+
// e.g. github.com/*, google.golang.org/*, go.uber.org/*
|
|
358
|
+
if (importPath.includes('.'))
|
|
359
|
+
continue;
|
|
360
|
+
// 4. No dots, no go.mod match, not stdlib → likely an internal package
|
|
361
|
+
// without go.mod context we can't verify, so skip to avoid false positives
|
|
337
362
|
}
|
|
338
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Comprehensive Go standard library package list.
|
|
366
|
+
* Includes all packages from Go 1.22+ (latest stable).
|
|
367
|
+
* Go stdlib is identified by having NO dots in the import path.
|
|
368
|
+
* We maintain an explicit list for packages with slashes (e.g. encoding/json).
|
|
369
|
+
*
|
|
370
|
+
* @since v3.0.1
|
|
371
|
+
*/
|
|
372
|
+
isGoStdlib(importPath) {
|
|
373
|
+
// Fast check: single-segment packages are always stdlib if no dots
|
|
374
|
+
if (!importPath.includes('/') && !importPath.includes('.'))
|
|
375
|
+
return true;
|
|
376
|
+
// Check the full path against known stdlib packages with sub-paths
|
|
377
|
+
const topLevel = importPath.split('/')[0];
|
|
378
|
+
// All Go stdlib top-level packages (including those with sub-packages)
|
|
379
|
+
const stdlibTopLevel = new Set([
|
|
380
|
+
// Single-word packages
|
|
381
|
+
'archive', 'bufio', 'builtin', 'bytes', 'cmp', 'compress',
|
|
382
|
+
'container', 'context', 'crypto', 'database', 'debug',
|
|
383
|
+
'embed', 'encoding', 'errors', 'expvar', 'flag', 'fmt',
|
|
384
|
+
'go', 'hash', 'html', 'image', 'index', 'io', 'iter',
|
|
385
|
+
'log', 'maps', 'math', 'mime', 'net', 'os', 'path',
|
|
386
|
+
'plugin', 'reflect', 'regexp', 'runtime', 'slices', 'sort',
|
|
387
|
+
'strconv', 'strings', 'structs', 'sync', 'syscall',
|
|
388
|
+
'testing', 'text', 'time', 'unicode', 'unique', 'unsafe',
|
|
389
|
+
// Internal packages (used by stdlib, sometimes by tools)
|
|
390
|
+
'internal', 'vendor',
|
|
391
|
+
]);
|
|
392
|
+
if (stdlibTopLevel.has(topLevel))
|
|
393
|
+
return true;
|
|
394
|
+
// Explicit full-path list for maximum safety — covers all Go 1.22 stdlib paths
|
|
395
|
+
// This catches any edge case the top-level check might miss
|
|
396
|
+
const knownStdlibPaths = new Set([
|
|
397
|
+
// archive/*
|
|
398
|
+
'archive/tar', 'archive/zip',
|
|
399
|
+
// compress/*
|
|
400
|
+
'compress/bzip2', 'compress/flate', 'compress/gzip', 'compress/lzw', 'compress/zlib',
|
|
401
|
+
// container/*
|
|
402
|
+
'container/heap', 'container/list', 'container/ring',
|
|
403
|
+
// crypto/*
|
|
404
|
+
'crypto/aes', 'crypto/cipher', 'crypto/des', 'crypto/dsa',
|
|
405
|
+
'crypto/ecdh', 'crypto/ecdsa', 'crypto/ed25519', 'crypto/elliptic',
|
|
406
|
+
'crypto/hmac', 'crypto/md5', 'crypto/rand', 'crypto/rc4',
|
|
407
|
+
'crypto/rsa', 'crypto/sha1', 'crypto/sha256', 'crypto/sha512',
|
|
408
|
+
'crypto/subtle', 'crypto/tls', 'crypto/x509', 'crypto/x509/pkix',
|
|
409
|
+
// database/*
|
|
410
|
+
'database/sql', 'database/sql/driver',
|
|
411
|
+
// debug/*
|
|
412
|
+
'debug/buildinfo', 'debug/dwarf', 'debug/elf', 'debug/gosym',
|
|
413
|
+
'debug/macho', 'debug/pe', 'debug/plan9obj',
|
|
414
|
+
// encoding/*
|
|
415
|
+
'encoding/ascii85', 'encoding/asn1', 'encoding/base32', 'encoding/base64',
|
|
416
|
+
'encoding/binary', 'encoding/csv', 'encoding/gob', 'encoding/hex',
|
|
417
|
+
'encoding/json', 'encoding/pem', 'encoding/xml',
|
|
418
|
+
// go/*
|
|
419
|
+
'go/ast', 'go/build', 'go/build/constraint', 'go/constant',
|
|
420
|
+
'go/doc', 'go/doc/comment', 'go/format', 'go/importer',
|
|
421
|
+
'go/parser', 'go/printer', 'go/scanner', 'go/token', 'go/types', 'go/version',
|
|
422
|
+
// hash/*
|
|
423
|
+
'hash/adler32', 'hash/crc32', 'hash/crc64', 'hash/fnv', 'hash/maphash',
|
|
424
|
+
// html/*
|
|
425
|
+
'html/template',
|
|
426
|
+
// image/*
|
|
427
|
+
'image/color', 'image/color/palette', 'image/draw',
|
|
428
|
+
'image/gif', 'image/jpeg', 'image/png',
|
|
429
|
+
// index/*
|
|
430
|
+
'index/suffixarray',
|
|
431
|
+
// io/*
|
|
432
|
+
'io/fs', 'io/ioutil',
|
|
433
|
+
// log/*
|
|
434
|
+
'log/slog', 'log/syslog',
|
|
435
|
+
// math/*
|
|
436
|
+
'math/big', 'math/bits', 'math/cmplx', 'math/rand', 'math/rand/v2',
|
|
437
|
+
// mime/*
|
|
438
|
+
'mime/multipart', 'mime/quotedprintable',
|
|
439
|
+
// net/*
|
|
440
|
+
'net/http', 'net/http/cgi', 'net/http/cookiejar', 'net/http/fcgi',
|
|
441
|
+
'net/http/httptest', 'net/http/httptrace', 'net/http/httputil',
|
|
442
|
+
'net/http/pprof', 'net/mail', 'net/netip', 'net/rpc',
|
|
443
|
+
'net/rpc/jsonrpc', 'net/smtp', 'net/textproto', 'net/url',
|
|
444
|
+
// os/*
|
|
445
|
+
'os/exec', 'os/signal', 'os/user',
|
|
446
|
+
// path/*
|
|
447
|
+
'path/filepath',
|
|
448
|
+
// regexp/*
|
|
449
|
+
'regexp/syntax',
|
|
450
|
+
// runtime/*
|
|
451
|
+
'runtime/cgo', 'runtime/coverage', 'runtime/debug', 'runtime/metrics',
|
|
452
|
+
'runtime/pprof', 'runtime/race', 'runtime/trace',
|
|
453
|
+
// sync/*
|
|
454
|
+
'sync/atomic',
|
|
455
|
+
// testing/*
|
|
456
|
+
'testing/fstest', 'testing/iotest', 'testing/quick', 'testing/slogtest',
|
|
457
|
+
// text/*
|
|
458
|
+
'text/scanner', 'text/tabwriter', 'text/template', 'text/template/parse',
|
|
459
|
+
// unicode/*
|
|
460
|
+
'unicode/utf16', 'unicode/utf8',
|
|
461
|
+
]);
|
|
462
|
+
return knownStdlibPaths.has(importPath);
|
|
463
|
+
}
|
|
339
464
|
/**
|
|
340
465
|
* Check Ruby imports — verify require_relative paths exist
|
|
341
466
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hallucinated Imports Gate — Go Standard Library False Positive Regression Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the fix for https://github.com/rigour-labs/rigour/issues/XXX
|
|
5
|
+
* Previously, Go stdlib packages with slashes (encoding/json, net/http, etc.)
|
|
6
|
+
* were flagged as hallucinated imports because the gate only recognized
|
|
7
|
+
* single-word stdlib packages (fmt, os, io).
|
|
8
|
+
*
|
|
9
|
+
* @since v3.0.1
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
|
+
import { HallucinatedImportsGate } from './hallucinated-imports.js';
|
|
13
|
+
// fs-extra is mocked via module-level mock fns above
|
|
14
|
+
// Mock fs-extra — vi.hoisted ensures these are available when vi.mock runs (hoisted)
|
|
15
|
+
const { mockPathExists, mockPathExistsSync, mockReadFile, mockReadFileSync, mockReadJson } = vi.hoisted(() => ({
|
|
16
|
+
mockPathExists: vi.fn(),
|
|
17
|
+
mockPathExistsSync: vi.fn(),
|
|
18
|
+
mockReadFile: vi.fn(),
|
|
19
|
+
mockReadFileSync: vi.fn(),
|
|
20
|
+
mockReadJson: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
vi.mock('fs-extra', () => {
|
|
23
|
+
const mock = {
|
|
24
|
+
pathExists: mockPathExists,
|
|
25
|
+
pathExistsSync: mockPathExistsSync,
|
|
26
|
+
readFile: mockReadFile,
|
|
27
|
+
readFileSync: mockReadFileSync,
|
|
28
|
+
readJson: mockReadJson,
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
...mock,
|
|
32
|
+
default: mock,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
// Mock FileScanner
|
|
36
|
+
vi.mock('../utils/scanner.js', () => ({
|
|
37
|
+
FileScanner: {
|
|
38
|
+
findFiles: vi.fn().mockResolvedValue([]),
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
import { FileScanner } from '../utils/scanner.js';
|
|
42
|
+
describe('HallucinatedImportsGate — Go stdlib false positives', () => {
|
|
43
|
+
let gate;
|
|
44
|
+
const testCwd = '/tmp/test-go-project';
|
|
45
|
+
const context = {
|
|
46
|
+
cwd: testCwd,
|
|
47
|
+
ignore: [],
|
|
48
|
+
};
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
vi.clearAllMocks();
|
|
51
|
+
gate = new HallucinatedImportsGate({ enabled: true });
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* This is the exact scenario from PicoClaw — Go stdlib packages with slashes
|
|
55
|
+
* were being flagged as hallucinated. These are ALL real Go stdlib packages.
|
|
56
|
+
*/
|
|
57
|
+
it('should NOT flag Go standard library packages as hallucinated (PicoClaw regression)', async () => {
|
|
58
|
+
const goFileContent = `package main
|
|
59
|
+
|
|
60
|
+
import (
|
|
61
|
+
"encoding/json"
|
|
62
|
+
"path/filepath"
|
|
63
|
+
"net/http"
|
|
64
|
+
"crypto/rand"
|
|
65
|
+
"crypto/sha256"
|
|
66
|
+
"encoding/base64"
|
|
67
|
+
"os/exec"
|
|
68
|
+
"os/signal"
|
|
69
|
+
"net/url"
|
|
70
|
+
"fmt"
|
|
71
|
+
"io"
|
|
72
|
+
"os"
|
|
73
|
+
"strings"
|
|
74
|
+
"context"
|
|
75
|
+
"sync"
|
|
76
|
+
"time"
|
|
77
|
+
"log"
|
|
78
|
+
"errors"
|
|
79
|
+
"io/ioutil"
|
|
80
|
+
"io/fs"
|
|
81
|
+
"math/rand"
|
|
82
|
+
"regexp"
|
|
83
|
+
"strconv"
|
|
84
|
+
"bytes"
|
|
85
|
+
"bufio"
|
|
86
|
+
"sort"
|
|
87
|
+
"testing"
|
|
88
|
+
"net/http/httptest"
|
|
89
|
+
"database/sql"
|
|
90
|
+
"html/template"
|
|
91
|
+
"text/template"
|
|
92
|
+
"archive/zip"
|
|
93
|
+
"compress/gzip"
|
|
94
|
+
"runtime/debug"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
func main() {}
|
|
98
|
+
`;
|
|
99
|
+
const goFile = 'main.go';
|
|
100
|
+
FileScanner.findFiles.mockResolvedValue([goFile]);
|
|
101
|
+
mockReadFile.mockResolvedValue(goFileContent);
|
|
102
|
+
mockPathExists.mockResolvedValue(false);
|
|
103
|
+
mockPathExistsSync.mockReturnValue(false); // no go.mod
|
|
104
|
+
const failures = await gate.run(context);
|
|
105
|
+
// ZERO failures — every import above is a real Go stdlib package
|
|
106
|
+
expect(failures).toHaveLength(0);
|
|
107
|
+
});
|
|
108
|
+
it('should NOT flag external module imports (github.com, etc.)', async () => {
|
|
109
|
+
const goFileContent = `package main
|
|
110
|
+
|
|
111
|
+
import (
|
|
112
|
+
"github.com/gin-gonic/gin"
|
|
113
|
+
"github.com/stretchr/testify/assert"
|
|
114
|
+
"google.golang.org/grpc"
|
|
115
|
+
"go.uber.org/zap"
|
|
116
|
+
"golang.org/x/crypto/bcrypt"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
func main() {}
|
|
120
|
+
`;
|
|
121
|
+
FileScanner.findFiles.mockResolvedValue(['main.go']);
|
|
122
|
+
mockReadFile.mockResolvedValue(goFileContent);
|
|
123
|
+
mockPathExists.mockResolvedValue(false);
|
|
124
|
+
mockPathExistsSync.mockReturnValue(false);
|
|
125
|
+
const failures = await gate.run(context);
|
|
126
|
+
expect(failures).toHaveLength(0);
|
|
127
|
+
});
|
|
128
|
+
it('should flag project-relative imports that do not resolve (with go.mod)', async () => {
|
|
129
|
+
const goMod = `module github.com/myorg/myproject
|
|
130
|
+
|
|
131
|
+
go 1.22
|
|
132
|
+
`;
|
|
133
|
+
const goFileContent = `package main
|
|
134
|
+
|
|
135
|
+
import (
|
|
136
|
+
"fmt"
|
|
137
|
+
"github.com/myorg/myproject/pkg/realmodule"
|
|
138
|
+
"github.com/myorg/myproject/pkg/doesnotexist"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
func main() {}
|
|
142
|
+
`;
|
|
143
|
+
FileScanner.findFiles.mockResolvedValue(['cmd/main.go', 'pkg/realmodule/handler.go']);
|
|
144
|
+
mockReadFile.mockImplementation(async (filePath) => {
|
|
145
|
+
if (filePath.includes('handler.go'))
|
|
146
|
+
return 'package realmodule\n\nimport "fmt"\n\nfunc Handler() {}\n';
|
|
147
|
+
return goFileContent;
|
|
148
|
+
});
|
|
149
|
+
mockPathExists.mockResolvedValue(false);
|
|
150
|
+
mockPathExistsSync.mockReturnValue(true); // go.mod exists
|
|
151
|
+
mockReadFileSync.mockReturnValue(goMod);
|
|
152
|
+
const failures = await gate.run(context);
|
|
153
|
+
// Should flag doesnotexist but NOT realmodule
|
|
154
|
+
expect(failures).toHaveLength(1);
|
|
155
|
+
expect(failures[0].details).toContain('doesnotexist');
|
|
156
|
+
expect(failures[0].details).not.toContain('realmodule');
|
|
157
|
+
});
|
|
158
|
+
it('should NOT flag anything when no go.mod exists and imports have no dots', async () => {
|
|
159
|
+
// Without go.mod, we can't determine the project module path,
|
|
160
|
+
// so we skip project-relative checks to avoid false positives
|
|
161
|
+
const goFileContent = `package main
|
|
162
|
+
|
|
163
|
+
import (
|
|
164
|
+
"fmt"
|
|
165
|
+
"net/http"
|
|
166
|
+
"internal/custom"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
func main() {}
|
|
170
|
+
`;
|
|
171
|
+
FileScanner.findFiles.mockResolvedValue(['main.go']);
|
|
172
|
+
mockReadFile.mockResolvedValue(goFileContent);
|
|
173
|
+
mockPathExists.mockResolvedValue(false);
|
|
174
|
+
mockPathExistsSync.mockReturnValue(false);
|
|
175
|
+
const failures = await gate.run(context);
|
|
176
|
+
expect(failures).toHaveLength(0);
|
|
177
|
+
});
|
|
178
|
+
it('should handle single-line imports', async () => {
|
|
179
|
+
const goFileContent = `package main
|
|
180
|
+
|
|
181
|
+
import "fmt"
|
|
182
|
+
import "encoding/json"
|
|
183
|
+
import "net/http"
|
|
184
|
+
|
|
185
|
+
func main() {}
|
|
186
|
+
`;
|
|
187
|
+
FileScanner.findFiles.mockResolvedValue(['main.go']);
|
|
188
|
+
mockReadFile.mockResolvedValue(goFileContent);
|
|
189
|
+
mockPathExists.mockResolvedValue(false);
|
|
190
|
+
mockPathExistsSync.mockReturnValue(false);
|
|
191
|
+
const failures = await gate.run(context);
|
|
192
|
+
expect(failures).toHaveLength(0);
|
|
193
|
+
});
|
|
194
|
+
it('should handle aliased imports', async () => {
|
|
195
|
+
const goFileContent = `package main
|
|
196
|
+
|
|
197
|
+
import (
|
|
198
|
+
"fmt"
|
|
199
|
+
mrand "math/rand"
|
|
200
|
+
_ "net/http/pprof"
|
|
201
|
+
. "os"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
func main() {}
|
|
205
|
+
`;
|
|
206
|
+
FileScanner.findFiles.mockResolvedValue(['main.go']);
|
|
207
|
+
mockReadFile.mockResolvedValue(goFileContent);
|
|
208
|
+
mockPathExists.mockResolvedValue(false);
|
|
209
|
+
mockPathExistsSync.mockReturnValue(false);
|
|
210
|
+
const failures = await gate.run(context);
|
|
211
|
+
expect(failures).toHaveLength(0);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
describe('HallucinatedImportsGate — Python stdlib coverage', () => {
|
|
215
|
+
let gate;
|
|
216
|
+
const testCwd = '/tmp/test-py-project';
|
|
217
|
+
const context = {
|
|
218
|
+
cwd: testCwd,
|
|
219
|
+
ignore: [],
|
|
220
|
+
};
|
|
221
|
+
beforeEach(() => {
|
|
222
|
+
vi.clearAllMocks();
|
|
223
|
+
gate = new HallucinatedImportsGate({ enabled: true });
|
|
224
|
+
});
|
|
225
|
+
it('should NOT flag Python standard library imports', async () => {
|
|
226
|
+
const pyContent = `
|
|
227
|
+
import os
|
|
228
|
+
import sys
|
|
229
|
+
import json
|
|
230
|
+
import hashlib
|
|
231
|
+
import pathlib
|
|
232
|
+
import subprocess
|
|
233
|
+
import argparse
|
|
234
|
+
import typing
|
|
235
|
+
import dataclasses
|
|
236
|
+
import functools
|
|
237
|
+
import itertools
|
|
238
|
+
import collections
|
|
239
|
+
import datetime
|
|
240
|
+
import re
|
|
241
|
+
import math
|
|
242
|
+
import random
|
|
243
|
+
import threading
|
|
244
|
+
import asyncio
|
|
245
|
+
from os.path import join, exists
|
|
246
|
+
from collections import defaultdict
|
|
247
|
+
from typing import List, Optional
|
|
248
|
+
from urllib.parse import urlparse
|
|
249
|
+
`;
|
|
250
|
+
FileScanner.findFiles.mockResolvedValue(['main.py']);
|
|
251
|
+
mockReadFile.mockResolvedValue(pyContent);
|
|
252
|
+
mockPathExists.mockResolvedValue(false);
|
|
253
|
+
const failures = await gate.run(context);
|
|
254
|
+
expect(failures).toHaveLength(0);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
describe('HallucinatedImportsGate — JS/TS Node builtins', () => {
|
|
258
|
+
let gate;
|
|
259
|
+
const testCwd = '/tmp/test-node-project';
|
|
260
|
+
const context = {
|
|
261
|
+
cwd: testCwd,
|
|
262
|
+
ignore: [],
|
|
263
|
+
};
|
|
264
|
+
beforeEach(() => {
|
|
265
|
+
vi.clearAllMocks();
|
|
266
|
+
gate = new HallucinatedImportsGate({ enabled: true });
|
|
267
|
+
});
|
|
268
|
+
it('should NOT flag Node.js built-in modules', async () => {
|
|
269
|
+
const jsContent = `
|
|
270
|
+
import fs from 'fs';
|
|
271
|
+
import path from 'path';
|
|
272
|
+
import crypto from 'crypto';
|
|
273
|
+
import http from 'http';
|
|
274
|
+
import https from 'https';
|
|
275
|
+
import url from 'url';
|
|
276
|
+
import os from 'os';
|
|
277
|
+
import stream from 'stream';
|
|
278
|
+
import util from 'util';
|
|
279
|
+
import { readFile } from 'node:fs';
|
|
280
|
+
import { join } from 'node:path';
|
|
281
|
+
`;
|
|
282
|
+
FileScanner.findFiles.mockResolvedValue(['index.ts']);
|
|
283
|
+
mockReadFile.mockResolvedValue(jsContent);
|
|
284
|
+
mockPathExists.mockResolvedValue(false);
|
|
285
|
+
const failures = await gate.run(context);
|
|
286
|
+
expect(failures).toHaveLength(0);
|
|
287
|
+
});
|
|
288
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Deterministic quality gate engine for AI-generated code. AST analysis, drift detection, and Fix Packet generation across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://rigour.run",
|