@rigour-labs/core 6.5.1 → 6.5.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.
- package/dist/deep/fact-extractor.js +2 -1
- package/dist/deep/fact-extractor.test.js +2 -1
- package/dist/gates/ast.js +4 -2
- package/dist/gates/coverage.js +4 -2
- package/dist/pattern-index/indexer.js +4 -2
- package/dist/review/review.d.ts +2 -0
- package/dist/review/review.js +11 -4
- package/dist/review/review.test.js +25 -1
- package/dist/utils/glob-paths.d.ts +4 -0
- package/dist/utils/glob-paths.js +26 -0
- package/dist/utils/glob-paths.test.d.ts +1 -0
- package/dist/utils/glob-paths.test.js +40 -0
- package/dist/utils/scanner.js +4 -2
- package/dist/utils/scanner.test.js +2 -1
- package/package.json +6 -6
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { globby } from 'globby';
|
|
9
|
+
import { directoriesAsGlobs, withDirectoryForms } from '../utils/glob-paths.js';
|
|
9
10
|
const ANALYZABLE_EXTENSION = /\.(ts|js|tsx|jsx|py|go|rs|cs|java|rb|kt)$/i;
|
|
10
11
|
/**
|
|
11
12
|
* Lightweight regex-based fact extraction.
|
|
@@ -20,7 +21,7 @@ export async function extractFacts(cwd, ignore, scope) {
|
|
|
20
21
|
'**/.git/**', '**/vendor/**', '**/__pycache__/**',
|
|
21
22
|
'**/*.min.js', '**/*.bundle.js',
|
|
22
23
|
];
|
|
23
|
-
const matched = await globby(patterns, { cwd, ignore: ignorePatterns, followSymbolicLinks: false });
|
|
24
|
+
const matched = await globby(directoriesAsGlobs(cwd, patterns), { cwd, ignore: withDirectoryForms(ignorePatterns), expandDirectories: false, followSymbolicLinks: false });
|
|
24
25
|
const files = matched.filter(f => ANALYZABLE_EXTENSION.test(f));
|
|
25
26
|
const allFacts = [];
|
|
26
27
|
for (const file of files) {
|
|
@@ -3,7 +3,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
3
3
|
// But we need to test extraction logic directly, so we also test via the public API
|
|
4
4
|
const mockGlobby = vi.hoisted(() => vi.fn());
|
|
5
5
|
const mockReadFile = vi.hoisted(() => vi.fn());
|
|
6
|
-
vi.mock('globby', () => ({
|
|
6
|
+
vi.mock('globby', async (importOriginal) => ({
|
|
7
|
+
isDynamicPattern: (await importOriginal()).isDynamicPattern,
|
|
7
8
|
globby: mockGlobby,
|
|
8
9
|
}));
|
|
9
10
|
vi.mock('fs-extra', () => ({
|
package/dist/gates/ast.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Gate } from './base.js';
|
|
|
5
5
|
import { TypeScriptHandler } from './ast-handlers/typescript.js';
|
|
6
6
|
import { PythonHandler } from './ast-handlers/python.js';
|
|
7
7
|
import { UniversalASTHandler } from './ast-handlers/universal.js';
|
|
8
|
+
import { directoriesAsGlobs, withDirectoryForms } from '../utils/glob-paths.js';
|
|
8
9
|
export class ASTGate extends Gate {
|
|
9
10
|
config;
|
|
10
11
|
handlers = [];
|
|
@@ -35,9 +36,10 @@ export class ASTGate extends Gate {
|
|
|
35
36
|
const ignore = [...new Set([...(context.ignore || []), ...defaultIgnore])].map(p => p.replace(/\\/g, '/'));
|
|
36
37
|
const normalizedCwd = context.cwd.replace(/\\/g, '/');
|
|
37
38
|
// Find all supported files
|
|
38
|
-
const files = await globby(patterns, {
|
|
39
|
+
const files = await globby(directoriesAsGlobs(normalizedCwd, patterns), {
|
|
39
40
|
cwd: normalizedCwd,
|
|
40
|
-
ignore: ignore,
|
|
41
|
+
ignore: withDirectoryForms(ignore),
|
|
42
|
+
expandDirectories: false,
|
|
41
43
|
});
|
|
42
44
|
// Process files concurrently in batches for performance
|
|
43
45
|
const CONCURRENCY = 16;
|
package/dist/gates/coverage.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
import { Gate } from './base.js';
|
|
5
5
|
import { globby } from 'globby';
|
|
6
|
+
import { withDirectoryForms } from '../utils/glob-paths.js';
|
|
6
7
|
export class CoverageGate extends Gate {
|
|
7
8
|
config;
|
|
8
9
|
constructor(config) {
|
|
@@ -62,7 +63,8 @@ export class CoverageGate extends Gate {
|
|
|
62
63
|
followSymbolicLinks: false,
|
|
63
64
|
onlyFiles: true,
|
|
64
65
|
deep: 8,
|
|
65
|
-
|
|
66
|
+
expandDirectories: false,
|
|
67
|
+
ignore: withDirectoryForms([...new Set([
|
|
66
68
|
...(context.ignore || []),
|
|
67
69
|
'**/node_modules/**',
|
|
68
70
|
'**/.git/**',
|
|
@@ -75,7 +77,7 @@ export class CoverageGate extends Gate {
|
|
|
75
77
|
'**/vendor/**',
|
|
76
78
|
'**/.venv/**',
|
|
77
79
|
'**/venv/**',
|
|
78
|
-
])],
|
|
80
|
+
])]),
|
|
79
81
|
});
|
|
80
82
|
if (reports.length === 0)
|
|
81
83
|
return null;
|
|
@@ -17,6 +17,7 @@ import { generateEmbedding } from './embeddings.js';
|
|
|
17
17
|
import { hashContent } from './indexer-helpers.js';
|
|
18
18
|
import { extractGoPatterns, extractRustPatterns, extractJVMStylePatterns, extractPythonPatterns, extractGenericCPatterns, } from './indexer-lang.js';
|
|
19
19
|
import { nodeToPattern } from './indexer-ts.js';
|
|
20
|
+
import { directoriesAsGlobs, withDirectoryForms } from '../utils/glob-paths.js';
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Configuration
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
@@ -239,10 +240,11 @@ export class PatternIndexer {
|
|
|
239
240
|
if (!this.config.indexTests) {
|
|
240
241
|
exclude.push('**/*.test.*', '**/*.spec.*', '**/__tests__/**');
|
|
241
242
|
}
|
|
242
|
-
return globby(patterns, {
|
|
243
|
+
return globby(directoriesAsGlobs(this.rootDir, patterns), {
|
|
243
244
|
cwd: this.rootDir,
|
|
244
245
|
absolute: true,
|
|
245
|
-
ignore: exclude,
|
|
246
|
+
ignore: withDirectoryForms(exclude),
|
|
247
|
+
expandDirectories: false,
|
|
246
248
|
gitignore: true,
|
|
247
249
|
});
|
|
248
250
|
}
|
package/dist/review/review.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface ReviewResult {
|
|
|
29
29
|
changedLines: Record<string, Set<number>>;
|
|
30
30
|
report: Report | null;
|
|
31
31
|
deepError?: string;
|
|
32
|
+
/** Gates that crashed instead of running; a proven one makes the result ERROR. */
|
|
33
|
+
gateErrors: string[];
|
|
32
34
|
}
|
|
33
35
|
export interface ReviewFinding {
|
|
34
36
|
id: string;
|
package/dist/review/review.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Runs the configured gates on the files a change touches and keeps what
|
|
6
6
|
* belongs to the change (see changed-lines.ts). A deep run that was asked for
|
|
7
|
-
* but could not analyze anything makes the result ERROR, never a clean PASS
|
|
7
|
+
* but could not analyze anything makes the result ERROR, never a clean PASS,
|
|
8
|
+
* and so does a proven gate that crashed (a heuristic one is only listed).
|
|
8
9
|
*/
|
|
9
10
|
import { GateRunner } from '../gates/runner.js';
|
|
10
11
|
import { changedLinesByFile, parseDiff, removedByFile } from '../utils/diff.js';
|
|
@@ -13,7 +14,7 @@ import { deepAnalysisError } from '../utils/deep-status.js';
|
|
|
13
14
|
import { splitByChangedLines } from './changed-lines.js';
|
|
14
15
|
import { changedFunctionSpans } from './changed-function-spans.js';
|
|
15
16
|
import { withoutGenerated } from './generated-files.js';
|
|
16
|
-
import { findingKey, quietSplit } from './quiet.js';
|
|
17
|
+
import { findingKey, isProven, quietSplit } from './quiet.js';
|
|
17
18
|
import { diffFromGit } from './git-diff.js';
|
|
18
19
|
import { diffTestFailures } from './diff-test-findings.js';
|
|
19
20
|
export async function reviewChange(input) {
|
|
@@ -21,7 +22,7 @@ export async function reviewChange(input) {
|
|
|
21
22
|
const changedLines = withoutGenerated(input.cwd, parseDiff(diff));
|
|
22
23
|
const targets = input.files?.length ? input.files : Object.keys(changedLines);
|
|
23
24
|
if (targets.length === 0) {
|
|
24
|
-
return { status: 'PASS', findings: [], fileFindings: [], contextFindings: [], advisory: [], dismissed: 0, dismissedByGate: {}, unlocated: 0, excludedOutsideChangedLines: 0, changedLines, report: null };
|
|
25
|
+
return { status: 'PASS', findings: [], fileFindings: [], contextFindings: [], advisory: [], dismissed: 0, dismissedByGate: {}, unlocated: 0, excludedOutsideChangedLines: 0, changedLines, report: null, gateErrors: [] };
|
|
25
26
|
}
|
|
26
27
|
const deep = input.deep ? { ...input.deep, focusLines: changedLinesByFile(changedLines), removedLines: removedByFile(diff), diff } : undefined;
|
|
27
28
|
const report = await new GateRunner(input.config).run(input.cwd, await normalizeScopePatterns(input.cwd, targets), deep);
|
|
@@ -30,8 +31,10 @@ export async function reviewChange(input) {
|
|
|
30
31
|
const split = splitByChangedLines(report.failures, changedLines, deep ? changedFunctionSpans(input.cwd, changedLines) : {});
|
|
31
32
|
const deepError = deepAnalysisError(report);
|
|
32
33
|
const quiet = quietSplit(input.cwd, split.findings, input.config.review?.include_heuristics);
|
|
34
|
+
const gateErrors = crashedGates(report);
|
|
35
|
+
const provenCrashed = gateErrors.some(id => isProven({ id }));
|
|
33
36
|
return {
|
|
34
|
-
status: deepError ? 'ERROR' : quiet.speaking.length > 0 ? 'FAIL' : 'PASS',
|
|
37
|
+
status: deepError || provenCrashed ? 'ERROR' : quiet.speaking.length > 0 ? 'FAIL' : 'PASS',
|
|
35
38
|
findings: quiet.speaking,
|
|
36
39
|
advisory: quiet.advisory,
|
|
37
40
|
dismissed: quiet.dismissed,
|
|
@@ -42,9 +45,13 @@ export async function reviewChange(input) {
|
|
|
42
45
|
excludedOutsideChangedLines: split.outside,
|
|
43
46
|
changedLines,
|
|
44
47
|
report,
|
|
48
|
+
gateErrors,
|
|
45
49
|
...(deepError ? { deepError } : {}),
|
|
46
50
|
};
|
|
47
51
|
}
|
|
52
|
+
function crashedGates(report) {
|
|
53
|
+
return Object.entries(report.summary).filter(([, status]) => status === 'ERROR').map(([id]) => id);
|
|
54
|
+
}
|
|
48
55
|
/** The JSON shape of a finding, shared by the CLI's --json and the MCP tool. */
|
|
49
56
|
export function toReviewFinding(failure) {
|
|
50
57
|
return {
|
|
@@ -2,7 +2,8 @@ import { execFileSync } from 'child_process';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { GateRunner } from '../gates/runner.js';
|
|
6
7
|
import { ConfigSchema } from '../types/index.js';
|
|
7
8
|
import { parseDiff } from '../utils/diff.js';
|
|
8
9
|
import { splitByChangedLines } from './changed-lines.js';
|
|
@@ -102,6 +103,29 @@ describe('git-backed review', () => {
|
|
|
102
103
|
expect(semantic.map(f => [f.files?.[0], f.line])).toEqual([['src/notify.ts', 2]]);
|
|
103
104
|
expect(toReviewFinding(semantic[0])).toMatchObject({ id: 'semantic-bugs', file: 'src/notify.ts', line: 2, severity: 'high' });
|
|
104
105
|
});
|
|
106
|
+
it('runs every gate in a git worktree, where .git is a file', async () => {
|
|
107
|
+
write('src/a.ts', 'export const a = 1;\n');
|
|
108
|
+
git('add', '-A');
|
|
109
|
+
git('commit', '-qm', 'init');
|
|
110
|
+
const worktree = path.join(repo, 'wt');
|
|
111
|
+
git('worktree', 'add', '-q', worktree);
|
|
112
|
+
fs.writeFileSync(path.join(worktree, 'src/a.ts'), 'export const a = 1;\nexport const b = 2;\n');
|
|
113
|
+
const config = ConfigSchema.parse({ version: 1, ignore: ['.git/**', 'wt/**'] });
|
|
114
|
+
const result = await reviewChange({ cwd: worktree, config });
|
|
115
|
+
expect(result.gateErrors).toEqual([]);
|
|
116
|
+
expect(result.status).not.toBe('ERROR');
|
|
117
|
+
});
|
|
118
|
+
it('is ERROR when a proven gate crashed, and only lists a crashed heuristic gate', async () => {
|
|
119
|
+
write('src/a.ts', 'export const a = 1;\n');
|
|
120
|
+
const config = ConfigSchema.parse({ version: 1 });
|
|
121
|
+
const crash = (summary) => vi.spyOn(GateRunner.prototype, 'run').mockResolvedValueOnce({ status: 'FAIL', summary, failures: [], stats: { duration_ms: 0 } });
|
|
122
|
+
crash({ 'hallucinated-imports': 'ERROR' });
|
|
123
|
+
const proven = await reviewChange({ cwd: repo, config });
|
|
124
|
+
expect([proven.status, proven.gateErrors]).toEqual(['ERROR', ['hallucinated-imports']]);
|
|
125
|
+
crash({ 'style-drift': 'ERROR', 'semantic-bugs': 'PASS' });
|
|
126
|
+
const heuristic = await reviewChange({ cwd: repo, config });
|
|
127
|
+
expect([heuristic.status, heuristic.gateErrors]).toEqual(['PASS', ['style-drift']]);
|
|
128
|
+
});
|
|
105
129
|
it('passes when nothing changed', async () => {
|
|
106
130
|
write('src/a.ts', 'export const a = 1;\n');
|
|
107
131
|
git('add', '-A');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Ignore entries: a plain path also ignores everything under it. Matched only, never listed. */
|
|
2
|
+
export declare function withDirectoryForms(patterns: string[]): string[];
|
|
3
|
+
/** Search patterns: a plain path that is a directory becomes `dir/**`; files and globs stay. */
|
|
4
|
+
export declare function directoriesAsGlobs(cwd: string, patterns: string[]): string[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { isDynamicPattern } from 'globby';
|
|
4
|
+
/*
|
|
5
|
+
* globby's default directory expansion stats every pattern and ignore entry,
|
|
6
|
+
* and throws ENOTDIR when one runs through a file: `.git/**` in a git
|
|
7
|
+
* worktree, where `.git` is a file. Rigour turns it off
|
|
8
|
+
* (`expandDirectories: false`) and expands plain paths itself, as before:
|
|
9
|
+
* a directory `legacy` also means `legacy/**`.
|
|
10
|
+
*/
|
|
11
|
+
/** Ignore entries: a plain path also ignores everything under it. Matched only, never listed. */
|
|
12
|
+
export function withDirectoryForms(patterns) {
|
|
13
|
+
return patterns.flatMap(pattern => isDynamicPattern(pattern) ? [pattern] : [pattern, `${pattern.replace(/\/+$/, '')}/**`]);
|
|
14
|
+
}
|
|
15
|
+
/** Search patterns: a plain path that is a directory becomes `dir/**`; files and globs stay. */
|
|
16
|
+
export function directoriesAsGlobs(cwd, patterns) {
|
|
17
|
+
return patterns.map(pattern => !isDynamicPattern(pattern) && isDirectory(path.resolve(cwd, pattern)) ? `${pattern.replace(/\/+$/, '')}/**` : pattern);
|
|
18
|
+
}
|
|
19
|
+
function isDirectory(target) {
|
|
20
|
+
try {
|
|
21
|
+
return fs.statSync(target).isDirectory();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { FileScanner } from './scanner.js';
|
|
6
|
+
import { directoriesAsGlobs, withDirectoryForms } from './glob-paths.js';
|
|
7
|
+
let root;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), 'glob-paths-'));
|
|
10
|
+
fs.mkdirSync(path.join(root, 'src'));
|
|
11
|
+
fs.mkdirSync(path.join(root, 'legacy'));
|
|
12
|
+
fs.writeFileSync(path.join(root, 'src', 'a.ts'), 'export const a = 1;\n');
|
|
13
|
+
fs.writeFileSync(path.join(root, 'legacy', 'old.ts'), 'export const b = 2;\n');
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
16
|
+
describe('withDirectoryForms', () => {
|
|
17
|
+
it('adds the directory form to plain paths and leaves globs alone', () => {
|
|
18
|
+
expect(withDirectoryForms(['legacy', 'build/', '.git/**', '**/*.min.js'])).toEqual(['legacy', 'legacy/**', 'build/', 'build/**', '.git/**', '**/*.min.js']);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('directoriesAsGlobs', () => {
|
|
22
|
+
it('turns a directory into dir/** and leaves files, missing paths and globs alone', () => {
|
|
23
|
+
expect(directoriesAsGlobs(root, ['src', 'src/a.ts', 'missing', '**/*.ts'])).toEqual(['src/**', 'src/a.ts', 'missing', '**/*.ts']);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('FileScanner in a git worktree', () => {
|
|
27
|
+
it('scans when .git is a file and the config ignores .git/**', async () => {
|
|
28
|
+
fs.writeFileSync(path.join(root, '.git'), 'gitdir: /elsewhere/.git/worktrees/x\n');
|
|
29
|
+
const files = await FileScanner.findFiles({ cwd: root, patterns: ['**/*.ts'], ignore: ['.git/**'] });
|
|
30
|
+
expect(files.sort()).toEqual(['legacy/old.ts', 'src/a.ts']);
|
|
31
|
+
});
|
|
32
|
+
it('finds a file or a whole directory named by a plain path', async () => {
|
|
33
|
+
expect(await FileScanner.findFiles({ cwd: root, patterns: ['src/a.ts'] })).toEqual(['src/a.ts']);
|
|
34
|
+
expect(await FileScanner.findFiles({ cwd: root, patterns: ['legacy'] })).toEqual(['legacy/old.ts']);
|
|
35
|
+
});
|
|
36
|
+
it('still ignores a whole directory named by a plain path', async () => {
|
|
37
|
+
const files = await FileScanner.findFiles({ cwd: root, patterns: ['**/*.ts'], ignore: ['legacy'] });
|
|
38
|
+
expect(files).toEqual(['src/a.ts']);
|
|
39
|
+
});
|
|
40
|
+
});
|
package/dist/utils/scanner.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { globby } from 'globby';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { directoriesAsGlobs, withDirectoryForms } from './glob-paths.js';
|
|
4
5
|
/**
|
|
5
6
|
* Glob patterns use `/` as the separator and `\` as the escape character.
|
|
6
7
|
* Only on Windows can a backslash be a path separator, and even there one
|
|
@@ -33,9 +34,10 @@ export class FileScanner {
|
|
|
33
34
|
const userIgnore = options.ignore || [];
|
|
34
35
|
const ignore = [...new Set([...this.DEFAULT_IGNORE, ...userIgnore])].map(toGlobSeparators);
|
|
35
36
|
const normalizedCwd = options.cwd.replace(/\\/g, '/');
|
|
36
|
-
return globby(patterns, {
|
|
37
|
+
return globby(directoriesAsGlobs(normalizedCwd, patterns), {
|
|
37
38
|
cwd: normalizedCwd,
|
|
38
|
-
ignore: ignore,
|
|
39
|
+
ignore: withDirectoryForms(ignore),
|
|
40
|
+
expandDirectories: false,
|
|
39
41
|
});
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { FileScanner } from './scanner.js';
|
|
3
3
|
import { globby } from 'globby';
|
|
4
|
-
vi.mock('globby', () => ({
|
|
4
|
+
vi.mock('globby', async (importOriginal) => ({
|
|
5
|
+
isDynamicPattern: (await importOriginal()).isDynamicPattern,
|
|
5
6
|
globby: vi.fn(),
|
|
6
7
|
}));
|
|
7
8
|
describe('FileScanner', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/core",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.2",
|
|
4
4
|
"description": "AI-native quality gate engine with local Bayesian learning. AST analysis, drift detection, Fix Packet generation, and agent self-healing across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
"pg": "^8.16.3",
|
|
71
71
|
"sqlite3": "^5.1.7",
|
|
72
72
|
"openai": "^4.104.0",
|
|
73
|
-
"@rigour-labs/brain-darwin-arm64": "6.5.
|
|
74
|
-
"@rigour-labs/brain-linux-arm64": "6.5.
|
|
75
|
-
"@rigour-labs/brain-darwin-x64": "6.5.
|
|
76
|
-
"@rigour-labs/brain-
|
|
77
|
-
"@rigour-labs/brain-
|
|
73
|
+
"@rigour-labs/brain-darwin-arm64": "6.5.2",
|
|
74
|
+
"@rigour-labs/brain-linux-arm64": "6.5.2",
|
|
75
|
+
"@rigour-labs/brain-darwin-x64": "6.5.2",
|
|
76
|
+
"@rigour-labs/brain-win-x64": "6.5.2",
|
|
77
|
+
"@rigour-labs/brain-linux-x64": "6.5.2"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@types/fs-extra": "^11.0.4",
|