@rigour-labs/core 6.11.1-rc.3 → 6.11.2-rc.1
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.
|
@@ -24,6 +24,8 @@ export interface HallucinatedImport {
|
|
|
24
24
|
importPath: string;
|
|
25
25
|
type: 'relative' | 'package' | 'python' | 'go' | 'ruby' | 'csharp' | 'rust' | 'java' | 'kotlin';
|
|
26
26
|
reason: string;
|
|
27
|
+
/** The resolver could not read what the file may import (no manifest found): a note, never a block. */
|
|
28
|
+
uncertain?: boolean;
|
|
27
29
|
}
|
|
28
30
|
export interface HallucinatedImportsConfig {
|
|
29
31
|
enabled?: boolean;
|
|
@@ -123,14 +123,24 @@ export class HallucinatedImportsGate extends Gate {
|
|
|
123
123
|
}
|
|
124
124
|
catch (e) { }
|
|
125
125
|
}
|
|
126
|
+
// One finding per file; an import the resolver could not check against a manifest is a separate note.
|
|
126
127
|
const byFile = new Map();
|
|
127
128
|
for (const h of hallucinated) {
|
|
128
|
-
const
|
|
129
|
+
const key = `${h.uncertain ? 'note' : 'block'}\u0000${h.file}`;
|
|
130
|
+
const existing = byFile.get(key) || [];
|
|
129
131
|
existing.push(h);
|
|
130
|
-
byFile.set(
|
|
132
|
+
byFile.set(key, existing);
|
|
131
133
|
}
|
|
132
|
-
for (const
|
|
134
|
+
for (const imports of byFile.values()) {
|
|
135
|
+
const file = imports[0].file;
|
|
133
136
|
const details = imports.map(i => ` L${i.line}: import '${i.importPath}' — ${i.reason}`).join('\n');
|
|
137
|
+
if (imports[0].uncertain) {
|
|
138
|
+
failures.push({
|
|
139
|
+
...this.createFailure(`Imports that could not be checked in ${file} (no manifest found for it):\n${details}`, [file], 'Rigour found no manifest (Cargo.toml) for this file, so it cannot tell whether these are declared. Check them, or add the manifest.', 'Hallucinated Imports', imports[0].line, undefined, 'low'),
|
|
140
|
+
advisory: true,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
134
144
|
failures.push(this.createFailure(`Hallucinated imports in ${file}:\n${details}`, [file], `These imports reference modules that don't exist. Remove or replace with real modules. AI models often "hallucinate" package names or file paths.`, 'Hallucinated Imports', imports[0].line, undefined, 'critical'));
|
|
135
145
|
}
|
|
136
146
|
return failures;
|
|
@@ -16,7 +16,6 @@ export declare function loadNuGetPackages(cwd: string): Set<string>;
|
|
|
16
16
|
*/
|
|
17
17
|
export declare function loadNuGetPackagesForFile(file: string, cwd: string): Set<string>;
|
|
18
18
|
export declare function checkRustImports(content: string, file: string, cwd: string, projectFiles: Set<string>, hallucinated: HallucinatedImport[]): void;
|
|
19
|
-
export declare function loadCargoDeps(cwd: string): Set<string>;
|
|
20
19
|
export declare function checkJavaKotlinImports(content: string, file: string, ext: string, cwd: string, projectFiles: Set<string>, hallucinated: HallucinatedImport[]): void;
|
|
21
20
|
export declare function loadJavaDeps(cwd: string): Set<string>;
|
|
22
21
|
/**
|
|
@@ -10,6 +10,8 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
10
10
|
let inImportBlock = false;
|
|
11
11
|
// Find go.mod by walking up from the Go file's directory (monorepo support).
|
|
12
12
|
let modulePath = null;
|
|
13
|
+
let moduleDir = ''; // where go.mod sits, relative to the repository root
|
|
14
|
+
const requiredModules = []; // `require` entries: another module, even under the module's own prefix
|
|
13
15
|
const replaceModules = new Set(); // Modules with local replace directives
|
|
14
16
|
const fileAbsDir = path.dirname(path.resolve(cwd, file));
|
|
15
17
|
const rootDir = path.resolve(cwd);
|
|
@@ -22,6 +24,16 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
22
24
|
const moduleMatch = goMod.match(/^module\s+(\S+)/m);
|
|
23
25
|
if (moduleMatch) {
|
|
24
26
|
modulePath = moduleMatch[1];
|
|
27
|
+
moduleDir = path.relative(rootDir, searchDir).replace(/\\/g, '/');
|
|
28
|
+
}
|
|
29
|
+
for (const m of goMod.matchAll(/^require\s+([^\s(]\S*)\s+\S+/gm))
|
|
30
|
+
requiredModules.push(m[1]);
|
|
31
|
+
for (const block of goMod.matchAll(/^require\s*\(([\s\S]*?)^\)/gm)) {
|
|
32
|
+
for (const entry of block[1].split('\n')) {
|
|
33
|
+
const m = entry.trim().match(/^([^\s/][^\s]*)\s+\S+/);
|
|
34
|
+
if (m)
|
|
35
|
+
requiredModules.push(m[1]);
|
|
36
|
+
}
|
|
25
37
|
}
|
|
26
38
|
// Parse replace directives: replace github.com/foo/bar => ../local-bar
|
|
27
39
|
// These modules are valid even without being in go.sum
|
|
@@ -94,10 +106,16 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
94
106
|
// 1. Skip Go standard library — comprehensive list
|
|
95
107
|
if (isGoStdlib(importPath))
|
|
96
108
|
continue;
|
|
97
|
-
//
|
|
109
|
+
// 1b. A module go.mod requires is another module, even under this module's own path
|
|
110
|
+
// (tailscale.com requiring tailscale.com/client/tailscale/v2): resolved by the module system.
|
|
111
|
+
if (requiredModules.some(m => importPath === m || importPath.startsWith(m + '/')))
|
|
112
|
+
continue;
|
|
113
|
+
// 2. If we have a module path, check project-relative imports FIRST: the package's own folder must hold Go files.
|
|
98
114
|
if (modulePath && importPath.startsWith(modulePath + '/')) {
|
|
99
115
|
const relPath = importPath.slice(modulePath.length + 1);
|
|
100
|
-
const
|
|
116
|
+
const packageDir = moduleDir ? `${moduleDir}/${relPath}` : relPath;
|
|
117
|
+
// On disk, not in the scanned file list: that list leaves out folders like `build/` that a Go package may be.
|
|
118
|
+
const hasMatchingFile = goPackageExists(path.join(rootDir, ...packageDir.split('/')));
|
|
101
119
|
if (!hasMatchingFile) {
|
|
102
120
|
hallucinated.push({
|
|
103
121
|
file, line: i + 1, importPath, type: 'go',
|
|
@@ -137,6 +155,15 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
137
155
|
// without go.mod context we can't verify, so skip to avoid false positives
|
|
138
156
|
}
|
|
139
157
|
}
|
|
158
|
+
/** A Go package: a folder holding at least one .go file. */
|
|
159
|
+
function goPackageExists(dir) {
|
|
160
|
+
try {
|
|
161
|
+
return fs.readdirSync(dir).some(entry => String(entry).endsWith('.go'));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
140
167
|
export function checkRubyImports(content, file, cwd, projectFiles, hallucinated) {
|
|
141
168
|
const lines = content.split('\n');
|
|
142
169
|
// Parse Gemfile for known gem dependencies
|
|
@@ -384,125 +411,182 @@ export function loadNuGetPackagesForFile(file, cwd) {
|
|
|
384
411
|
}
|
|
385
412
|
export function checkRustImports(content, file, cwd, projectFiles, hallucinated) {
|
|
386
413
|
const lines = content.split('\n');
|
|
387
|
-
|
|
414
|
+
// The file's own crate manifest and its workspace: what this crate may name. With no Cargo.toml found, nothing
|
|
415
|
+
// here can be proven missing, so an unresolved name is a note (`uncertain`), never a block.
|
|
416
|
+
const manifest = rustManifestFor(cwd, file);
|
|
417
|
+
// Items in scope by name: `use log::x` after `mod log;` names the module, not a crate (Rust 2018 paths).
|
|
418
|
+
const local = rustLocalNames(content);
|
|
419
|
+
const where = manifest.found ? 'its Cargo.toml' : 'any Cargo.toml found';
|
|
388
420
|
for (let i = 0; i < lines.length; i++) {
|
|
389
421
|
const line = lines[i].trim();
|
|
390
422
|
if (line.startsWith('//') || line.startsWith('/*'))
|
|
391
423
|
continue;
|
|
392
|
-
// extern crate foo;
|
|
424
|
+
// extern crate foo; (pre-2018: the crate must be declared, and then `use foo::` names it)
|
|
393
425
|
const externMatch = line.match(/^extern\s+crate\s+(\w+)/);
|
|
394
426
|
if (externMatch) {
|
|
395
427
|
const crateName = externMatch[1];
|
|
396
|
-
if (isRustStdCrate(crateName))
|
|
397
|
-
continue;
|
|
398
|
-
if (cargoDeps.has(crateName))
|
|
428
|
+
if (isRustStdCrate(crateName) || manifest.deps.has(crateName))
|
|
399
429
|
continue;
|
|
400
430
|
hallucinated.push({
|
|
401
431
|
file, line: i + 1, importPath: crateName, type: 'rust',
|
|
402
|
-
reason: `extern crate '${crateName}' — not in
|
|
432
|
+
reason: `extern crate '${crateName}' — not in ${where} or Rust std`,
|
|
433
|
+
...(manifest.found ? {} : { uncertain: true }),
|
|
403
434
|
});
|
|
404
435
|
continue;
|
|
405
436
|
}
|
|
406
|
-
// use foo::bar::
|
|
407
|
-
const useMatch = line.match(/^(?:pub
|
|
437
|
+
// use foo::bar; use ::foo::bar; pub(crate) use foo::{bar, baz}; Only the first segment can be a crate.
|
|
438
|
+
const useMatch = line.match(/^(?:pub(?:\([^)]*\))?\s+)?use\s+(?:::)?(\w+)::/);
|
|
408
439
|
if (useMatch) {
|
|
409
440
|
const crateName = useMatch[1];
|
|
410
|
-
|
|
441
|
+
// Path keywords, std, declared crates, and names the file itself brings into scope.
|
|
442
|
+
if (['crate', 'self', 'super', 'Self'].includes(crateName))
|
|
411
443
|
continue;
|
|
412
|
-
if (
|
|
413
|
-
continue;
|
|
414
|
-
// 'crate' and 'self' and 'super' are Rust path keywords
|
|
415
|
-
if (['crate', 'self', 'super'].includes(crateName))
|
|
444
|
+
if (isRustStdCrate(crateName) || manifest.deps.has(crateName) || local.has(crateName))
|
|
416
445
|
continue;
|
|
417
446
|
hallucinated.push({
|
|
418
447
|
file, line: i + 1, importPath: crateName, type: 'rust',
|
|
419
|
-
reason: `use ${crateName}:: — crate not in
|
|
448
|
+
reason: `use ${crateName}:: — crate not in ${where} or Rust std`,
|
|
449
|
+
...(manifest.found ? {} : { uncertain: true }),
|
|
420
450
|
});
|
|
421
451
|
}
|
|
422
452
|
}
|
|
423
453
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
454
|
+
/** Names a Rust file declares or brings into scope itself: modules, types, functions, `extern crate`, `as` aliases and the last segment of a plain `use`. */
|
|
455
|
+
function rustLocalNames(content) {
|
|
456
|
+
const names = new Set();
|
|
457
|
+
const declared = /^\s*(?:pub(?:\([^)]*\))?\s+)?(?:(?:unsafe|async|const|extern(?:\s+"[^"]*")?)\s+)*(?:mod|struct|enum|union|trait|type|fn|const|static|macro_rules!)\s+(\w+)/gm;
|
|
458
|
+
for (const m of content.matchAll(declared))
|
|
459
|
+
names.add(m[1]);
|
|
460
|
+
for (const m of content.matchAll(/^\s*extern\s+crate\s+(\w+)(?:\s+as\s+(\w+))?/gm)) {
|
|
461
|
+
names.add(m[1]);
|
|
462
|
+
if (m[2])
|
|
463
|
+
names.add(m[2]);
|
|
464
|
+
}
|
|
465
|
+
for (const m of content.matchAll(/\bas\s+(\w+)\s*[;,}]/g))
|
|
466
|
+
names.add(m[1]);
|
|
467
|
+
// `use serde::de;` brings `de` into this file's scope (only this file's: it never becomes a crate name).
|
|
468
|
+
for (const m of content.matchAll(/^\s*(?:pub(?:\([^)]*\))?\s+)?use\s+[\w:]+::(\w+)\s*;/gm))
|
|
469
|
+
names.add(m[1]);
|
|
470
|
+
return names;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* What a Rust file's crate may name: every dependency section of the nearest Cargo.toml from the file up to the
|
|
474
|
+
* repository root (its crate) and that crate's own name (integration tests and examples use it); then, from the
|
|
475
|
+
* nearest `[workspace]` at or above it, the shared dependencies and every member's name. `found` is false when no
|
|
476
|
+
* Cargo.toml was found at all.
|
|
477
|
+
*/
|
|
478
|
+
function rustManifestFor(cwd, file) {
|
|
479
|
+
const root = path.resolve(cwd);
|
|
480
|
+
const crate = crateManifest(root, path.dirname(path.resolve(root, file)));
|
|
481
|
+
const deps = new Set(crate ? cargoNames(crate) : []);
|
|
482
|
+
const workspace = workspaceManifest(crate ? path.dirname(crate) : root);
|
|
483
|
+
if (workspace) {
|
|
484
|
+
for (const name of cargoNames(workspace.file))
|
|
485
|
+
deps.add(name);
|
|
486
|
+
for (const member of workspaceMembers(path.dirname(workspace.file), workspace.text))
|
|
487
|
+
for (const name of cargoNames(member, true))
|
|
488
|
+
deps.add(name);
|
|
489
|
+
}
|
|
490
|
+
return { found: !!crate || !!workspace, deps };
|
|
491
|
+
}
|
|
492
|
+
/** The nearest Cargo.toml from `dir` up to the repository root: the file's own crate. */
|
|
493
|
+
function crateManifest(root, dir) {
|
|
494
|
+
for (; dir.startsWith(root); dir = path.dirname(dir)) {
|
|
495
|
+
const candidate = path.join(dir, 'Cargo.toml');
|
|
496
|
+
if (fs.pathExistsSync(candidate))
|
|
497
|
+
return candidate;
|
|
498
|
+
if (dir === root || path.dirname(dir) === dir)
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
/** The nearest Cargo.toml with `[workspace]` at or above `dir`. */
|
|
504
|
+
function workspaceManifest(dir) {
|
|
505
|
+
for (;; dir = path.dirname(dir)) {
|
|
506
|
+
const file = path.join(dir, 'Cargo.toml');
|
|
507
|
+
const text = readText(file);
|
|
508
|
+
if (text?.includes('[workspace]'))
|
|
509
|
+
return { file, text };
|
|
510
|
+
if (path.dirname(dir) === dir)
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function readText(file) {
|
|
515
|
+
try {
|
|
516
|
+
return fs.pathExistsSync(file) ? fs.readFileSync(file, 'utf-8') : undefined;
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/** A workspace's member manifests: `members = ["crates/a", "crates/*"]`, a trailing `/*` listing that folder. */
|
|
523
|
+
function workspaceMembers(dir, text) {
|
|
524
|
+
const list = /members\s*=\s*\[([^\]]*)\]/.exec(text)?.[1] ?? '';
|
|
525
|
+
const out = [];
|
|
526
|
+
for (const m of list.matchAll(/"([^"]+)"/g)) {
|
|
527
|
+
const member = m[1];
|
|
528
|
+
if (member.endsWith('/*')) {
|
|
529
|
+
const parent = path.join(dir, member.slice(0, -2));
|
|
530
|
+
try {
|
|
531
|
+
for (const entry of fs.readdirSync(parent))
|
|
532
|
+
out.push(path.join(parent, String(entry), 'Cargo.toml'));
|
|
482
533
|
}
|
|
534
|
+
catch { /* none */ }
|
|
483
535
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
536
|
+
else
|
|
537
|
+
out.push(path.join(dir, member, 'Cargo.toml'));
|
|
538
|
+
}
|
|
539
|
+
return out;
|
|
540
|
+
}
|
|
541
|
+
/** Names a Cargo.toml makes nameable in code (`-` read as `_`); `ownOnly`: just its [package] and [lib] names. */
|
|
542
|
+
function cargoNames(file, ownOnly = false) {
|
|
543
|
+
const text = readText(file);
|
|
544
|
+
if (!text)
|
|
545
|
+
return new Set();
|
|
546
|
+
const names = ownOnly ? new Set() : cargoDepsOf(text);
|
|
547
|
+
for (const section of ['package', 'lib']) {
|
|
548
|
+
const body = new RegExp(`^\\[${section}\\]\\s*$([\\s\\S]*?)(?=^\\[|(?![\\s\\S]))`, 'm').exec(text)?.[1] ?? '';
|
|
549
|
+
const name = /^\s*name\s*=\s*"([^"]+)"/m.exec(body)?.[1];
|
|
550
|
+
if (name)
|
|
551
|
+
names.add(name.replace(/-/g, '_'));
|
|
552
|
+
}
|
|
553
|
+
return names;
|
|
554
|
+
}
|
|
555
|
+
/** Every name a Cargo.toml's dependency sections declare: [dependencies], dev, build, target-specific, [workspace.dependencies], `[dependencies.foo]` and `dep:` features; `-` read as `_`. */
|
|
556
|
+
function cargoDepsOf(content) {
|
|
557
|
+
const deps = new Set([...sectionDeps(content), ...tableDeps(content), ...featureDeps(content)]);
|
|
558
|
+
return new Set([...deps].map(name => name.replace(/-/g, '_'))); // Rust uses _ in code for - in Cargo
|
|
559
|
+
}
|
|
560
|
+
/** Keys of every dependency section: [dependencies], [dev-dependencies], [build-dependencies], target-specific and the workspace's. */
|
|
561
|
+
function sectionDeps(content) {
|
|
562
|
+
const names = [];
|
|
563
|
+
let inDeps = false;
|
|
564
|
+
for (const line of content.split('\n')) {
|
|
565
|
+
const trimmed = line.trim();
|
|
566
|
+
if (/^\[(?:(?:dev|build)-)?dependencies(?:\.]|\])/.test(trimmed) ||
|
|
567
|
+
/^\[target\.[^\]]*\.(?:(?:dev|build)-)?dependencies\]/.test(trimmed) ||
|
|
568
|
+
/^\[workspace\.dependencies\]/.test(trimmed)) {
|
|
569
|
+
inDeps = true;
|
|
570
|
+
continue;
|
|
499
571
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
572
|
+
if (/^\[/.test(trimmed)) {
|
|
573
|
+
inDeps = false;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const m = inDeps ? trimmed.match(/^([\w][\w-]*)\s*=/) : null;
|
|
577
|
+
if (m)
|
|
578
|
+
names.push(m[1]);
|
|
504
579
|
}
|
|
505
|
-
return
|
|
580
|
+
return names;
|
|
581
|
+
}
|
|
582
|
+
/** `[dependencies.foo]` tables. */
|
|
583
|
+
function tableDeps(content) {
|
|
584
|
+
return [...content.matchAll(/^\[(?:(?:dev|build)-)?dependencies\.([\w][\w-]*)\]/gm)].map(m => m[1]);
|
|
585
|
+
}
|
|
586
|
+
/** Optional dependencies a feature enables (`"dep:foo"` in [features]). */
|
|
587
|
+
function featureDeps(content) {
|
|
588
|
+
const features = /^\[features\]\s*$([\s\S]*?)(?=^\[|(?![\s\S]))/m.exec(content)?.[1] ?? '';
|
|
589
|
+
return [...features.matchAll(/"dep:(\w[\w-]*)"/g)].map(m => m[1]);
|
|
506
590
|
}
|
|
507
591
|
export function checkJavaKotlinImports(content, file, ext, cwd, projectFiles, hallucinated) {
|
|
508
592
|
const lines = content.split('\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/core",
|
|
3
|
-
"version": "6.11.
|
|
3
|
+
"version": "6.11.2-rc.1",
|
|
4
4
|
"description": "Rigour's review engine: deterministic gates on changed lines, rules and lessons learned from your team's fixes, and per-check precision from what you fix versus dismiss, across TypeScript, JavaScript, Python, Go, Ruby and C#.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.13"
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"@anthropic-ai/sdk": "^0.132.1",
|
|
76
76
|
"pg": "^8.16.3",
|
|
77
77
|
"openai": "^5.23.2",
|
|
78
|
-
"@rigour-labs/brain-darwin-arm64": "6.11.
|
|
79
|
-
"@rigour-labs/brain-
|
|
80
|
-
"@rigour-labs/brain-linux-
|
|
81
|
-
"@rigour-labs/brain-
|
|
82
|
-
"@rigour-labs/brain-win-x64": "6.11.
|
|
78
|
+
"@rigour-labs/brain-darwin-arm64": "6.11.2-rc.1",
|
|
79
|
+
"@rigour-labs/brain-darwin-x64": "6.11.2-rc.1",
|
|
80
|
+
"@rigour-labs/brain-linux-arm64": "6.11.2-rc.1",
|
|
81
|
+
"@rigour-labs/brain-linux-x64": "6.11.2-rc.1",
|
|
82
|
+
"@rigour-labs/brain-win-x64": "6.11.2-rc.1"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/fs-extra": "^11.0.4",
|