@rigour-labs/core 4.0.4 → 4.1.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.
- package/dist/gates/ast-handlers/typescript.js +39 -12
- package/dist/gates/ast-handlers/universal.js +9 -3
- package/dist/gates/ast.js +15 -1
- package/dist/gates/ast.test.d.ts +1 -0
- package/dist/gates/ast.test.js +112 -0
- package/dist/gates/content.d.ts +5 -0
- package/dist/gates/content.js +66 -7
- package/dist/gates/content.test.d.ts +1 -0
- package/dist/gates/content.test.js +73 -0
- package/dist/gates/context-window-artifacts.d.ts +1 -0
- package/dist/gates/context-window-artifacts.js +10 -3
- package/dist/gates/context.d.ts +1 -0
- package/dist/gates/context.js +29 -8
- package/dist/gates/deep-analysis.js +2 -2
- package/dist/gates/deprecated-apis.d.ts +1 -0
- package/dist/gates/deprecated-apis.js +15 -2
- package/dist/gates/hallucinated-imports.d.ts +14 -0
- package/dist/gates/hallucinated-imports.js +267 -60
- package/dist/gates/hallucinated-imports.test.js +164 -1
- package/dist/gates/inconsistent-error-handling.d.ts +1 -0
- package/dist/gates/inconsistent-error-handling.js +12 -1
- package/dist/gates/phantom-apis.d.ts +2 -0
- package/dist/gates/phantom-apis.js +28 -3
- package/dist/gates/phantom-apis.test.js +14 -0
- package/dist/gates/promise-safety.d.ts +2 -0
- package/dist/gates/promise-safety.js +31 -9
- package/dist/gates/runner.js +8 -2
- package/dist/gates/runner.test.d.ts +1 -0
- package/dist/gates/runner.test.js +65 -0
- package/dist/gates/security-patterns.d.ts +1 -0
- package/dist/gates/security-patterns.js +22 -6
- package/dist/gates/security-patterns.test.js +18 -0
- package/dist/hooks/templates.d.ts +1 -1
- package/dist/hooks/templates.js +12 -12
- package/dist/inference/executable.d.ts +6 -0
- package/dist/inference/executable.js +29 -0
- package/dist/inference/executable.test.d.ts +1 -0
- package/dist/inference/executable.test.js +41 -0
- package/dist/inference/model-manager.d.ts +3 -1
- package/dist/inference/model-manager.js +76 -8
- package/dist/inference/model-manager.test.d.ts +1 -0
- package/dist/inference/model-manager.test.js +24 -0
- package/dist/inference/sidecar-provider.d.ts +1 -0
- package/dist/inference/sidecar-provider.js +124 -31
- package/dist/services/context-engine.js +1 -1
- package/dist/templates/universal-config.js +3 -3
- package/dist/types/index.js +3 -3
- package/dist/utils/scanner.js +6 -0
- package/package.json +7 -2
|
@@ -4,21 +4,74 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
7
8
|
import { RIGOUR_DIR } from '../storage/db.js';
|
|
8
9
|
import { MODELS } from './types.js';
|
|
9
10
|
const MODELS_DIR = path.join(RIGOUR_DIR, 'models');
|
|
11
|
+
const SHA256_RE = /^[a-f0-9]{64}$/i;
|
|
12
|
+
function getModelMetadataPath(tier) {
|
|
13
|
+
return path.join(MODELS_DIR, MODELS[tier].filename + '.meta.json');
|
|
14
|
+
}
|
|
15
|
+
function isValidMetadata(raw) {
|
|
16
|
+
return !!raw &&
|
|
17
|
+
typeof raw.sha256 === 'string' &&
|
|
18
|
+
SHA256_RE.test(raw.sha256) &&
|
|
19
|
+
typeof raw.sizeBytes === 'number' &&
|
|
20
|
+
typeof raw.verifiedAt === 'string' &&
|
|
21
|
+
typeof raw.sourceUrl === 'string';
|
|
22
|
+
}
|
|
23
|
+
export function extractSha256FromEtag(etag) {
|
|
24
|
+
if (!etag)
|
|
25
|
+
return null;
|
|
26
|
+
const normalized = etag.replace(/^W\//i, '').replace(/^"+|"+$/g, '').trim();
|
|
27
|
+
return SHA256_RE.test(normalized) ? normalized.toLowerCase() : null;
|
|
28
|
+
}
|
|
29
|
+
export async function hashFileSha256(filePath) {
|
|
30
|
+
const hash = createHash('sha256');
|
|
31
|
+
const stream = fs.createReadStream(filePath);
|
|
32
|
+
for await (const chunk of stream) {
|
|
33
|
+
hash.update(chunk);
|
|
34
|
+
}
|
|
35
|
+
return hash.digest('hex');
|
|
36
|
+
}
|
|
37
|
+
async function writeModelMetadata(tier, metadata) {
|
|
38
|
+
const metadataPath = getModelMetadataPath(tier);
|
|
39
|
+
await fs.writeJson(metadataPath, metadata, { spaces: 2 });
|
|
40
|
+
}
|
|
41
|
+
async function readModelMetadata(tier) {
|
|
42
|
+
const metadataPath = getModelMetadataPath(tier);
|
|
43
|
+
if (!(await fs.pathExists(metadataPath))) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const raw = await fs.readJson(metadataPath);
|
|
48
|
+
return isValidMetadata(raw) ? raw : null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
10
54
|
/**
|
|
11
55
|
* Check if a model is already downloaded and valid.
|
|
12
56
|
*/
|
|
13
|
-
export function isModelCached(tier) {
|
|
57
|
+
export async function isModelCached(tier) {
|
|
14
58
|
const model = MODELS[tier];
|
|
15
59
|
const modelPath = path.join(MODELS_DIR, model.filename);
|
|
16
|
-
if (!fs.
|
|
60
|
+
if (!(await fs.pathExists(modelPath)))
|
|
17
61
|
return false;
|
|
18
|
-
|
|
19
|
-
|
|
62
|
+
const metadata = await readModelMetadata(tier);
|
|
63
|
+
if (!metadata)
|
|
64
|
+
return false;
|
|
65
|
+
// Size check + "changed since verification" check.
|
|
66
|
+
const stat = await fs.stat(modelPath);
|
|
20
67
|
const tolerance = model.sizeBytes * 0.1;
|
|
21
|
-
|
|
68
|
+
if (stat.size <= model.sizeBytes - tolerance)
|
|
69
|
+
return false;
|
|
70
|
+
if (metadata.sizeBytes !== stat.size)
|
|
71
|
+
return false;
|
|
72
|
+
if (new Date(metadata.verifiedAt).getTime() < stat.mtimeMs)
|
|
73
|
+
return false;
|
|
74
|
+
return true;
|
|
22
75
|
}
|
|
23
76
|
/**
|
|
24
77
|
* Get the path to a cached model.
|
|
@@ -42,7 +95,7 @@ export async function downloadModel(tier, onProgress) {
|
|
|
42
95
|
const tempPath = destPath + '.download';
|
|
43
96
|
fs.ensureDirSync(MODELS_DIR);
|
|
44
97
|
// Already cached
|
|
45
|
-
if (isModelCached(tier)) {
|
|
98
|
+
if (await isModelCached(tier)) {
|
|
46
99
|
onProgress?.(`Model ${model.name} already cached`, 100);
|
|
47
100
|
return destPath;
|
|
48
101
|
}
|
|
@@ -52,18 +105,22 @@ export async function downloadModel(tier, onProgress) {
|
|
|
52
105
|
if (!response.ok) {
|
|
53
106
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
54
107
|
}
|
|
108
|
+
const expectedSha256 = extractSha256FromEtag(response.headers.get('etag'));
|
|
55
109
|
const contentLength = parseInt(response.headers.get('content-length') || '0', 10);
|
|
56
110
|
const reader = response.body?.getReader();
|
|
57
111
|
if (!reader)
|
|
58
112
|
throw new Error('No response body');
|
|
59
113
|
const writeStream = fs.createWriteStream(tempPath);
|
|
114
|
+
const hash = createHash('sha256');
|
|
60
115
|
let downloaded = 0;
|
|
61
116
|
let lastProgressPercent = 0;
|
|
62
117
|
while (true) {
|
|
63
118
|
const { done, value } = await reader.read();
|
|
64
119
|
if (done)
|
|
65
120
|
break;
|
|
66
|
-
|
|
121
|
+
const chunk = Buffer.from(value);
|
|
122
|
+
writeStream.write(chunk);
|
|
123
|
+
hash.update(chunk);
|
|
67
124
|
downloaded += value.length;
|
|
68
125
|
if (contentLength > 0) {
|
|
69
126
|
const percent = Math.round((downloaded / contentLength) * 100);
|
|
@@ -78,8 +135,19 @@ export async function downloadModel(tier, onProgress) {
|
|
|
78
135
|
writeStream.on('finish', resolve);
|
|
79
136
|
writeStream.on('error', reject);
|
|
80
137
|
});
|
|
138
|
+
const actualSha256 = hash.digest('hex');
|
|
139
|
+
if (expectedSha256 && actualSha256 !== expectedSha256) {
|
|
140
|
+
throw new Error(`Model checksum mismatch for ${model.name}: expected ${expectedSha256}, got ${actualSha256}`);
|
|
141
|
+
}
|
|
81
142
|
// Atomic rename
|
|
82
143
|
fs.renameSync(tempPath, destPath);
|
|
144
|
+
await writeModelMetadata(tier, {
|
|
145
|
+
sha256: actualSha256,
|
|
146
|
+
sizeBytes: downloaded,
|
|
147
|
+
verifiedAt: new Date().toISOString(),
|
|
148
|
+
sourceUrl: model.url,
|
|
149
|
+
sourceEtag: response.headers.get('etag') || undefined,
|
|
150
|
+
});
|
|
83
151
|
onProgress?.(`Model ${model.name} ready`, 100);
|
|
84
152
|
return destPath;
|
|
85
153
|
}
|
|
@@ -93,7 +161,7 @@ export async function downloadModel(tier, onProgress) {
|
|
|
93
161
|
* Ensure a model is available, downloading if needed.
|
|
94
162
|
*/
|
|
95
163
|
export async function ensureModel(tier, onProgress) {
|
|
96
|
-
if (isModelCached(tier)) {
|
|
164
|
+
if (await isModelCached(tier)) {
|
|
97
165
|
return getModelPath(tier);
|
|
98
166
|
}
|
|
99
167
|
return downloadModel(tier, onProgress);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { extractSha256FromEtag, hashFileSha256 } from './model-manager.js';
|
|
6
|
+
describe('model manager integrity helpers', () => {
|
|
7
|
+
it('extracts sha256 digest from a strong ETag', () => {
|
|
8
|
+
const digest = 'a'.repeat(64);
|
|
9
|
+
expect(extractSha256FromEtag(`"${digest}"`)).toBe(digest);
|
|
10
|
+
expect(extractSha256FromEtag(`W/"${digest}"`)).toBe(digest);
|
|
11
|
+
});
|
|
12
|
+
it('returns null for non-sha ETags', () => {
|
|
13
|
+
expect(extractSha256FromEtag('"not-a-digest"')).toBeNull();
|
|
14
|
+
expect(extractSha256FromEtag(null)).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
it('hashes file contents with sha256', async () => {
|
|
17
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-model-hash-'));
|
|
18
|
+
const filePath = path.join(dir, 'sample.gguf');
|
|
19
|
+
await fs.writeFile(filePath, 'rigour-model-check');
|
|
20
|
+
const digest = await hashFileSha256(filePath);
|
|
21
|
+
expect(digest).toBe('e123266ea4b37a81948a0a844dd58eddfc81737aa6fdf9dafc818fd23bae75f0');
|
|
22
|
+
await fs.remove(dir);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -11,6 +11,7 @@ export declare class SidecarProvider implements InferenceProvider {
|
|
|
11
11
|
analyze(prompt: string, options?: InferenceOptions): Promise<string>;
|
|
12
12
|
dispose(): void;
|
|
13
13
|
private getPlatformKey;
|
|
14
|
+
private getPlatformPackageName;
|
|
14
15
|
private resolveBinaryPath;
|
|
15
16
|
private installSidecarBinary;
|
|
16
17
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sidecar Binary Provider — runs inference via pre-compiled llama.cpp binary.
|
|
3
|
-
* Binary ships as @rigour/brain-{platform} optional npm dependency.
|
|
3
|
+
* Binary ships as @rigour-labs/brain-{platform} optional npm dependency.
|
|
4
4
|
* Falls back to PATH lookup for development/manual installs.
|
|
5
5
|
*/
|
|
6
6
|
import { execFile } from 'child_process';
|
|
@@ -10,14 +10,16 @@ import os from 'os';
|
|
|
10
10
|
import fs from 'fs-extra';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
12
|
import { ensureModel, isModelCached, getModelInfo } from './model-manager.js';
|
|
13
|
+
import { ensureExecutableBinary } from './executable.js';
|
|
13
14
|
const execFileAsync = promisify(execFile);
|
|
15
|
+
const SIDECAR_INSTALL_DIR = path.join(os.homedir(), '.rigour', 'sidecar');
|
|
14
16
|
/** Platform → npm package mapping */
|
|
15
17
|
const PLATFORM_PACKAGES = {
|
|
16
|
-
'darwin-arm64': '@rigour/brain-darwin-arm64',
|
|
17
|
-
'darwin-x64': '@rigour/brain-darwin-x64',
|
|
18
|
-
'linux-x64': '@rigour/brain-linux-x64',
|
|
19
|
-
'linux-arm64': '@rigour/brain-linux-arm64',
|
|
20
|
-
'win32-x64': '@rigour/brain-win-x64',
|
|
18
|
+
'darwin-arm64': '@rigour-labs/brain-darwin-arm64',
|
|
19
|
+
'darwin-x64': '@rigour-labs/brain-darwin-x64',
|
|
20
|
+
'linux-x64': '@rigour-labs/brain-linux-x64',
|
|
21
|
+
'linux-arm64': '@rigour-labs/brain-linux-arm64',
|
|
22
|
+
'win32-x64': '@rigour-labs/brain-win-x64',
|
|
21
23
|
};
|
|
22
24
|
export class SidecarProvider {
|
|
23
25
|
name = 'sidecar';
|
|
@@ -34,11 +36,10 @@ export class SidecarProvider {
|
|
|
34
36
|
return binary !== null;
|
|
35
37
|
}
|
|
36
38
|
async setup(onProgress) {
|
|
37
|
-
const
|
|
38
|
-
const packageName = PLATFORM_PACKAGES[platformKey];
|
|
39
|
+
const packageName = this.getPlatformPackageName();
|
|
39
40
|
// 1. Check/resolve binary
|
|
40
41
|
this.binaryPath = await this.resolveBinaryPath();
|
|
41
|
-
// Auto-bootstrap local sidecar
|
|
42
|
+
// Auto-bootstrap local sidecar when missing.
|
|
42
43
|
if (!this.binaryPath && packageName) {
|
|
43
44
|
const installed = await this.installSidecarBinary(packageName, onProgress);
|
|
44
45
|
if (installed) {
|
|
@@ -46,12 +47,32 @@ export class SidecarProvider {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
if (!this.binaryPath) {
|
|
49
|
-
onProgress?.('⚠ Inference engine not found. Install @rigour/brain-* or add llama-cli to PATH');
|
|
50
|
-
|
|
50
|
+
onProgress?.('⚠ Inference engine not found. Install @rigour-labs/brain-* or add llama-cli to PATH');
|
|
51
|
+
const installHint = packageName || `@rigour-labs/brain-${this.getPlatformKey()}`;
|
|
52
|
+
throw new Error(`Sidecar binary not found. Run: npm install ${installHint}`);
|
|
53
|
+
}
|
|
54
|
+
let executableCheck = ensureExecutableBinary(this.binaryPath);
|
|
55
|
+
// If the discovered binary is not executable, try a managed reinstall once.
|
|
56
|
+
if (!executableCheck.ok && packageName) {
|
|
57
|
+
onProgress?.('⚠ Inference engine is present but not executable. Reinstalling managed sidecar...');
|
|
58
|
+
const installed = await this.installSidecarBinary(packageName, onProgress);
|
|
59
|
+
if (installed) {
|
|
60
|
+
const refreshedPath = await this.resolveBinaryPath();
|
|
61
|
+
if (refreshedPath) {
|
|
62
|
+
this.binaryPath = refreshedPath;
|
|
63
|
+
executableCheck = ensureExecutableBinary(this.binaryPath);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!executableCheck.ok) {
|
|
68
|
+
throw new Error(`Sidecar binary is not executable: ${this.binaryPath}. Run: chmod +x "${this.binaryPath}"`);
|
|
69
|
+
}
|
|
70
|
+
if (executableCheck.fixed) {
|
|
71
|
+
onProgress?.('✓ Fixed execute permission for inference engine');
|
|
51
72
|
}
|
|
52
73
|
onProgress?.('✓ Inference engine ready');
|
|
53
74
|
// 2. Ensure model is downloaded
|
|
54
|
-
if (!isModelCached(this.tier)) {
|
|
75
|
+
if (!(await isModelCached(this.tier))) {
|
|
55
76
|
const modelInfo = getModelInfo(this.tier);
|
|
56
77
|
onProgress?.(`⬇ Downloading analysis model (${modelInfo.sizeHuman})...`);
|
|
57
78
|
}
|
|
@@ -80,11 +101,50 @@ export class SidecarProvider {
|
|
|
80
101
|
args.push('--json');
|
|
81
102
|
}
|
|
82
103
|
try {
|
|
83
|
-
const
|
|
104
|
+
const execOptions = {
|
|
84
105
|
timeout: options?.timeout || 60000,
|
|
85
106
|
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
86
107
|
env: { ...process.env, LLAMA_LOG_DISABLE: '1' },
|
|
87
|
-
}
|
|
108
|
+
};
|
|
109
|
+
const runInference = async () => {
|
|
110
|
+
return process.platform === 'win32' && this.binaryPath.endsWith('.cmd')
|
|
111
|
+
? await execFileAsync('cmd.exe', ['/d', '/s', '/c', [this.binaryPath, ...args].map(quoteCmdArg).join(' ')], execOptions)
|
|
112
|
+
: await execFileAsync(this.binaryPath, args, execOptions);
|
|
113
|
+
};
|
|
114
|
+
let stdout;
|
|
115
|
+
try {
|
|
116
|
+
({ stdout } = await runInference());
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// One retry path for stale/bad file mode in packaged installs.
|
|
120
|
+
if (error?.code === 'EACCES') {
|
|
121
|
+
const check = ensureExecutableBinary(this.binaryPath);
|
|
122
|
+
if (check.ok) {
|
|
123
|
+
({ stdout } = await runInference());
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const packageName = this.getPlatformPackageName();
|
|
127
|
+
if (packageName) {
|
|
128
|
+
const installed = await this.installSidecarBinary(packageName);
|
|
129
|
+
if (installed) {
|
|
130
|
+
const refreshedPath = await this.resolveBinaryPath();
|
|
131
|
+
if (refreshedPath) {
|
|
132
|
+
this.binaryPath = refreshedPath;
|
|
133
|
+
const refreshedCheck = ensureExecutableBinary(this.binaryPath);
|
|
134
|
+
if (refreshedCheck.ok) {
|
|
135
|
+
({ stdout } = await runInference());
|
|
136
|
+
return stdout.trim();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
88
148
|
// llama.cpp sometimes outputs to stderr for diagnostics — ignore
|
|
89
149
|
return stdout.trim();
|
|
90
150
|
}
|
|
@@ -92,6 +152,9 @@ export class SidecarProvider {
|
|
|
92
152
|
if (error.killed) {
|
|
93
153
|
throw new Error(`Inference timed out after ${(options?.timeout || 60000) / 1000}s`);
|
|
94
154
|
}
|
|
155
|
+
if (error?.code === 'EACCES') {
|
|
156
|
+
throw new Error(`Inference binary is not executable: ${this.binaryPath}. Run: chmod +x "${this.binaryPath}"`);
|
|
157
|
+
}
|
|
95
158
|
throw new Error(`Inference failed: ${error.message}`);
|
|
96
159
|
}
|
|
97
160
|
}
|
|
@@ -103,19 +166,37 @@ export class SidecarProvider {
|
|
|
103
166
|
getPlatformKey() {
|
|
104
167
|
return `${os.platform()}-${os.arch()}`;
|
|
105
168
|
}
|
|
169
|
+
getPlatformPackageName() {
|
|
170
|
+
const platformKey = this.getPlatformKey();
|
|
171
|
+
return PLATFORM_PACKAGES[platformKey];
|
|
172
|
+
}
|
|
106
173
|
async resolveBinaryPath() {
|
|
107
174
|
const platformKey = this.getPlatformKey();
|
|
108
|
-
// Strategy 1: Check @rigour/brain-{platform} optional dependency
|
|
175
|
+
// Strategy 1: Check @rigour-labs/brain-{platform} optional dependency
|
|
109
176
|
const packageName = PLATFORM_PACKAGES[platformKey];
|
|
110
177
|
if (packageName) {
|
|
178
|
+
// Prefer Rigour-managed sidecar install root first to avoid brittle global/homebrew layouts.
|
|
179
|
+
const managedPath = path.join(SIDECAR_INSTALL_DIR, 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain');
|
|
180
|
+
const managedCandidates = os.platform() === 'win32'
|
|
181
|
+
? [managedPath + '.exe', managedPath + '.cmd', managedPath]
|
|
182
|
+
: [managedPath];
|
|
183
|
+
for (const managedBinPath of managedCandidates) {
|
|
184
|
+
if (await fs.pathExists(managedBinPath)) {
|
|
185
|
+
return managedBinPath;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
111
188
|
try {
|
|
112
189
|
const require = createRequire(import.meta.url);
|
|
113
190
|
const pkgJsonPath = require.resolve(path.posix.join(packageName, 'package.json'));
|
|
114
191
|
const pkgDir = path.dirname(pkgJsonPath);
|
|
115
192
|
const resolvedBin = path.join(pkgDir, 'bin', 'rigour-brain');
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
193
|
+
const resolvedCandidates = os.platform() === 'win32'
|
|
194
|
+
? [resolvedBin + '.exe', resolvedBin + '.cmd', resolvedBin]
|
|
195
|
+
: [resolvedBin];
|
|
196
|
+
for (const resolvedBinPath of resolvedCandidates) {
|
|
197
|
+
if (await fs.pathExists(resolvedBinPath)) {
|
|
198
|
+
return resolvedBinPath;
|
|
199
|
+
}
|
|
119
200
|
}
|
|
120
201
|
}
|
|
121
202
|
catch {
|
|
@@ -134,9 +215,11 @@ export class SidecarProvider {
|
|
|
134
215
|
path.join(os.homedir(), '.npm-global', 'lib', 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain'),
|
|
135
216
|
];
|
|
136
217
|
for (const p of possiblePaths) {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
218
|
+
const candidates = os.platform() === 'win32' ? [p + '.exe', p + '.cmd', p] : [p];
|
|
219
|
+
for (const binPath of candidates) {
|
|
220
|
+
if (await fs.pathExists(binPath)) {
|
|
221
|
+
return binPath;
|
|
222
|
+
}
|
|
140
223
|
}
|
|
141
224
|
}
|
|
142
225
|
}
|
|
@@ -146,14 +229,19 @@ export class SidecarProvider {
|
|
|
146
229
|
}
|
|
147
230
|
// Strategy 2: Check ~/.rigour/bin/
|
|
148
231
|
const localBin = path.join(os.homedir(), '.rigour', 'bin', 'rigour-brain');
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
232
|
+
const localCandidates = os.platform() === 'win32'
|
|
233
|
+
? [localBin + '.exe', localBin + '.cmd', localBin]
|
|
234
|
+
: [localBin];
|
|
235
|
+
for (const localBinPath of localCandidates) {
|
|
236
|
+
if (await fs.pathExists(localBinPath)) {
|
|
237
|
+
return localBinPath;
|
|
238
|
+
}
|
|
152
239
|
}
|
|
153
240
|
// Strategy 3: Check PATH for llama-cli (llama.cpp CLI)
|
|
241
|
+
const locator = os.platform() === 'win32' ? 'where' : 'which';
|
|
154
242
|
try {
|
|
155
|
-
const { stdout } = await execFileAsync(
|
|
156
|
-
const llamaPath = stdout.trim();
|
|
243
|
+
const { stdout } = await execFileAsync(locator, ['llama-cli']);
|
|
244
|
+
const llamaPath = stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean) || '';
|
|
157
245
|
if (llamaPath && await fs.pathExists(llamaPath)) {
|
|
158
246
|
return llamaPath;
|
|
159
247
|
}
|
|
@@ -165,9 +253,10 @@ export class SidecarProvider {
|
|
|
165
253
|
const altNames = ['llama-cli', 'llama', 'main'];
|
|
166
254
|
for (const name of altNames) {
|
|
167
255
|
try {
|
|
168
|
-
const { stdout } = await execFileAsync(
|
|
169
|
-
|
|
170
|
-
|
|
256
|
+
const { stdout } = await execFileAsync(locator, [name]);
|
|
257
|
+
const resolved = stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean);
|
|
258
|
+
if (resolved && await fs.pathExists(resolved))
|
|
259
|
+
return resolved;
|
|
171
260
|
}
|
|
172
261
|
catch {
|
|
173
262
|
// Continue
|
|
@@ -178,8 +267,9 @@ export class SidecarProvider {
|
|
|
178
267
|
async installSidecarBinary(packageName, onProgress) {
|
|
179
268
|
onProgress?.(`⬇ Inference engine missing. Attempting automatic install: ${packageName}`);
|
|
180
269
|
try {
|
|
181
|
-
await
|
|
182
|
-
|
|
270
|
+
await fs.ensureDir(SIDECAR_INSTALL_DIR);
|
|
271
|
+
await execFileAsync(os.platform() === 'win32' ? 'npm.cmd' : 'npm', ['install', '--no-save', '--no-package-lock', '--prefix', SIDECAR_INSTALL_DIR, packageName], {
|
|
272
|
+
cwd: SIDECAR_INSTALL_DIR,
|
|
183
273
|
timeout: 120000,
|
|
184
274
|
maxBuffer: 10 * 1024 * 1024,
|
|
185
275
|
});
|
|
@@ -193,3 +283,6 @@ export class SidecarProvider {
|
|
|
193
283
|
return true;
|
|
194
284
|
}
|
|
195
285
|
}
|
|
286
|
+
function quoteCmdArg(value) {
|
|
287
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
288
|
+
}
|
|
@@ -113,9 +113,9 @@ export const UNIVERSAL_CONFIG = {
|
|
|
113
113
|
},
|
|
114
114
|
context_window_artifacts: {
|
|
115
115
|
enabled: true,
|
|
116
|
-
min_file_lines:
|
|
117
|
-
degradation_threshold: 0.
|
|
118
|
-
signals_required:
|
|
116
|
+
min_file_lines: 180,
|
|
117
|
+
degradation_threshold: 0.55,
|
|
118
|
+
signals_required: 4,
|
|
119
119
|
},
|
|
120
120
|
promise_safety: {
|
|
121
121
|
enabled: true,
|
package/dist/types/index.js
CHANGED
|
@@ -125,9 +125,9 @@ export const GatesSchema = z.object({
|
|
|
125
125
|
}).optional().default({}),
|
|
126
126
|
context_window_artifacts: z.object({
|
|
127
127
|
enabled: z.boolean().optional().default(true),
|
|
128
|
-
min_file_lines: z.number().optional().default(
|
|
129
|
-
degradation_threshold: z.number().min(0).max(1).optional().default(0.
|
|
130
|
-
signals_required: z.number().optional().default(
|
|
128
|
+
min_file_lines: z.number().optional().default(180),
|
|
129
|
+
degradation_threshold: z.number().min(0).max(1).optional().default(0.55),
|
|
130
|
+
signals_required: z.number().optional().default(4),
|
|
131
131
|
}).optional().default({}),
|
|
132
132
|
promise_safety: z.object({
|
|
133
133
|
enabled: z.boolean().optional().default(true),
|
package/dist/utils/scanner.js
CHANGED
|
@@ -6,6 +6,12 @@ export class FileScanner {
|
|
|
6
6
|
static DEFAULT_IGNORE = [
|
|
7
7
|
'**/node_modules/**',
|
|
8
8
|
'**/dist/**',
|
|
9
|
+
'**/studio-dist/**',
|
|
10
|
+
'**/.next/**',
|
|
11
|
+
'**/coverage/**',
|
|
12
|
+
'**/out/**',
|
|
13
|
+
'**/target/**',
|
|
14
|
+
'**/examples/**',
|
|
9
15
|
'**/package-lock.json',
|
|
10
16
|
'**/pnpm-lock.yaml',
|
|
11
17
|
'**/.git/**',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/core",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
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",
|
|
@@ -58,7 +58,12 @@
|
|
|
58
58
|
"@anthropic-ai/sdk": "^0.30.1",
|
|
59
59
|
"@xenova/transformers": "^2.17.2",
|
|
60
60
|
"better-sqlite3": "^11.0.0",
|
|
61
|
-
"openai": "^4.104.0"
|
|
61
|
+
"openai": "^4.104.0",
|
|
62
|
+
"@rigour-labs/brain-darwin-arm64": "4.1.0",
|
|
63
|
+
"@rigour-labs/brain-darwin-x64": "4.1.0",
|
|
64
|
+
"@rigour-labs/brain-win-x64": "4.1.0",
|
|
65
|
+
"@rigour-labs/brain-linux-x64": "4.1.0",
|
|
66
|
+
"@rigour-labs/brain-linux-arm64": "4.1.0"
|
|
62
67
|
},
|
|
63
68
|
"devDependencies": {
|
|
64
69
|
"@types/better-sqlite3": "^7.6.12",
|