@rigour-labs/core 6.2.3 → 6.2.5
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/code-context.d.ts +28 -0
- package/dist/deep/code-context.js +93 -0
- package/dist/deep/code-context.test.d.ts +1 -0
- package/dist/deep/code-context.test.js +48 -0
- package/dist/deep/code-pass.d.ts +15 -0
- package/dist/deep/code-pass.js +44 -0
- package/dist/deep/code-review-prompt.d.ts +17 -0
- package/dist/deep/code-review-prompt.js +30 -0
- package/dist/deep/code-review-prompt.test.d.ts +1 -0
- package/dist/deep/code-review-prompt.test.js +20 -0
- package/dist/deep/code-verifier.d.ts +19 -0
- package/dist/deep/code-verifier.js +48 -0
- package/dist/deep/code-verifier.test.d.ts +1 -0
- package/dist/deep/code-verifier.test.js +46 -0
- package/dist/deep/fact-extractor.d.ts +7 -1
- package/dist/deep/fact-extractor.js +16 -8
- package/dist/deep/fact-extractor.test.js +34 -1
- package/dist/deep/facts-pass.d.ts +24 -0
- package/dist/deep/facts-pass.js +69 -0
- package/dist/deep/index.d.ts +6 -1
- package/dist/deep/index.js +5 -1
- package/dist/deep/parse-findings.d.ts +12 -0
- package/dist/deep/parse-findings.js +73 -0
- package/dist/deep/parse-findings.test.d.ts +1 -0
- package/dist/deep/parse-findings.test.js +25 -0
- package/dist/deep/prompts.d.ts +1 -1
- package/dist/deep/prompts.js +7 -8
- package/dist/deep/prompts.test.js +15 -0
- package/dist/gates/context.js +7 -0
- package/dist/gates/deep-analysis.d.ts +31 -8
- package/dist/gates/deep-analysis.js +165 -306
- package/dist/gates/deep-analysis.test.d.ts +1 -0
- package/dist/gates/deep-analysis.test.js +86 -0
- package/dist/gates/deep-runner.d.ts +15 -0
- package/dist/gates/deep-runner.js +65 -0
- package/dist/gates/dependency.js +4 -0
- package/dist/gates/hallucinated-imports/index.js +13 -9
- package/dist/gates/hallucinated-imports.test.js +10 -0
- package/dist/gates/js-style-context.d.ts +3 -0
- package/dist/gates/js-style-context.js +65 -0
- package/dist/gates/js-style-context.test.d.ts +1 -0
- package/dist/gates/js-style-context.test.js +35 -0
- package/dist/gates/logic-drift-git-base.d.ts +7 -0
- package/dist/gates/logic-drift-git-base.js +41 -0
- package/dist/gates/logic-drift.d.ts +3 -3
- package/dist/gates/logic-drift.js +39 -7
- package/dist/gates/logic-drift.test.d.ts +1 -0
- package/dist/gates/logic-drift.test.js +45 -0
- package/dist/gates/runner.js +5 -40
- package/dist/gates/runner.test.js +37 -25
- package/dist/gates/scoped-gates.test.d.ts +1 -0
- package/dist/gates/scoped-gates.test.js +52 -0
- package/dist/gates/security-command-execution.d.ts +5 -0
- package/dist/gates/security-command-execution.js +123 -0
- package/dist/gates/security-patterns-data.js +1 -17
- package/dist/gates/security-patterns.d.ts +0 -5
- package/dist/gates/security-patterns.js +11 -19
- package/dist/gates/security-patterns.test.js +37 -2
- package/dist/gates/style-drift.d.ts +4 -4
- package/dist/gates/style-drift.js +15 -16
- package/dist/gates/style-drift.test.d.ts +1 -0
- package/dist/gates/style-drift.test.js +26 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/inference/http-download.d.ts +22 -0
- package/dist/inference/http-download.js +132 -0
- package/dist/inference/http-download.test.d.ts +1 -0
- package/dist/inference/http-download.test.js +109 -0
- package/dist/inference/index.d.ts +5 -1
- package/dist/inference/index.js +2 -1
- package/dist/inference/llama-engine.d.ts +27 -0
- package/dist/inference/llama-engine.js +152 -0
- package/dist/inference/llama-engine.test.d.ts +1 -0
- package/dist/inference/llama-engine.test.js +51 -0
- package/dist/inference/llama-process.d.ts +72 -0
- package/dist/inference/llama-process.js +126 -0
- package/dist/inference/llama-process.test.d.ts +1 -0
- package/dist/inference/llama-process.test.js +61 -0
- package/dist/inference/model-download.test.d.ts +1 -0
- package/dist/inference/model-download.test.js +73 -0
- package/dist/inference/model-manager.d.ts +14 -1
- package/dist/inference/model-manager.js +65 -80
- package/dist/inference/sidecar-provider.d.ts +19 -10
- package/dist/inference/sidecar-provider.js +115 -343
- package/dist/templates/universal-config.js +0 -2
- package/dist/types/index.d.ts +58 -14
- package/dist/types/index.js +9 -2
- package/dist/utils/deep-status.d.ts +7 -0
- package/dist/utils/deep-status.js +11 -0
- package/dist/utils/diff.d.ts +3 -0
- package/dist/utils/diff.js +52 -0
- package/dist/utils/diff.test.d.ts +1 -0
- package/dist/utils/diff.test.js +38 -0
- package/dist/utils/scanner.js +13 -2
- package/dist/utils/scanner.test.js +22 -8
- package/dist/utils/scope.d.ts +15 -0
- package/dist/utils/scope.js +51 -0
- package/dist/utils/scope.test.d.ts +1 -0
- package/dist/utils/scope.test.js +38 -0
- package/package.json +6 -6
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sidecar
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Sidecar provider: local inference with llama.cpp's llama-cli.
|
|
3
|
+
*
|
|
4
|
+
* Engine resolution, first binary that passes `--version` wins:
|
|
5
|
+
* 1. managed install of the pinned release (~/.rigour/bin/llama-<tag>/)
|
|
6
|
+
* 2. legacy locations: ~/.rigour/bin/llama-cli, @rigour-labs/brain-<platform>
|
|
7
|
+
* 3. llama-cli on PATH
|
|
8
|
+
* If none runs, the pinned release is downloaded and installed.
|
|
9
|
+
*
|
|
10
|
+
* Code never leaves the machine: prompts go to a local child process.
|
|
5
11
|
*/
|
|
6
|
-
import { execFile } from 'child_process';
|
|
7
|
-
import { promisify } from 'util';
|
|
8
12
|
import path from 'path';
|
|
9
13
|
import os from 'os';
|
|
10
14
|
import fs from 'fs-extra';
|
|
11
15
|
import { createRequire } from 'module';
|
|
12
|
-
import { ensureModel,
|
|
16
|
+
import { ensureModel, getCachedModel } from './model-manager.js';
|
|
13
17
|
import { ensureExecutableBinary } from './executable.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
import { installLlamaEngine, llamaBinaryName, managedEngineDir, managedEnginePath, probeBinary } from './llama-engine.js';
|
|
19
|
+
import { buildLlamaArgs, cleanLlamaOutput, FINDINGS_JSON_SCHEMA, ProcessTimeoutError, runProcess } from './llama-process.js';
|
|
20
|
+
import { RIGOUR_DIR } from '../storage/db.js';
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
22
|
+
/** Platform → legacy npm package that may carry a rigour-brain binary. */
|
|
18
23
|
const PLATFORM_PACKAGES = {
|
|
19
24
|
'darwin-arm64': '@rigour-labs/brain-darwin-arm64',
|
|
20
25
|
'darwin-x64': '@rigour-labs/brain-darwin-x64',
|
|
@@ -22,374 +27,141 @@ const PLATFORM_PACKAGES = {
|
|
|
22
27
|
'linux-arm64': '@rigour-labs/brain-linux-arm64',
|
|
23
28
|
'win32-x64': '@rigour-labs/brain-win-x64',
|
|
24
29
|
};
|
|
25
|
-
const LLAMA_RELEASE_TAG = 'b5604';
|
|
26
|
-
const LLAMA_RELEASE_BASE = `https://github.com/ggml-org/llama.cpp/releases/download/${LLAMA_RELEASE_TAG}`;
|
|
27
|
-
/** Platform → llama.cpp release asset name */
|
|
28
|
-
const LLAMA_RELEASE_ASSETS = {
|
|
29
|
-
'darwin-arm64': `llama-${LLAMA_RELEASE_TAG}-bin-macos-arm64.zip`,
|
|
30
|
-
'darwin-x64': `llama-${LLAMA_RELEASE_TAG}-bin-macos-x64.zip`,
|
|
31
|
-
'linux-x64': `llama-${LLAMA_RELEASE_TAG}-bin-ubuntu-x64.zip`,
|
|
32
|
-
'win32-x64': `llama-${LLAMA_RELEASE_TAG}-bin-win-cpu-x64.zip`,
|
|
33
|
-
// linux-arm64 not published by llama.cpp — users must build from source or use cloud provider
|
|
34
|
-
};
|
|
35
30
|
export class SidecarProvider {
|
|
36
31
|
name = 'sidecar';
|
|
37
32
|
binaryPath = null;
|
|
38
|
-
|
|
33
|
+
model = null;
|
|
34
|
+
schemaPath = null;
|
|
39
35
|
tier;
|
|
40
36
|
threads;
|
|
41
37
|
constructor(tier = 'lite', threads = 4) {
|
|
42
38
|
this.tier = tier;
|
|
43
39
|
this.threads = threads;
|
|
44
40
|
}
|
|
41
|
+
/** True when a llama-cli that actually runs is installed. */
|
|
45
42
|
async isAvailable() {
|
|
43
|
+
return (await this.findEngine()) !== null;
|
|
44
|
+
}
|
|
45
|
+
/** The llama-cli this provider would use and its version, or null if none runs. */
|
|
46
|
+
async findEngine() {
|
|
46
47
|
const binary = await this.resolveBinaryPath();
|
|
47
|
-
|
|
48
|
+
if (!binary)
|
|
49
|
+
return null;
|
|
50
|
+
return { path: binary, version: (await probeBinary(binary)).version };
|
|
51
|
+
}
|
|
52
|
+
/** Install the engine and model for this tier without running inference. */
|
|
53
|
+
async prepare(onProgress) {
|
|
54
|
+
await this.setup(onProgress);
|
|
55
|
+
this.dispose();
|
|
48
56
|
}
|
|
49
57
|
async setup(onProgress) {
|
|
50
|
-
|
|
51
|
-
this.binaryPath = await this.resolveBinaryPath();
|
|
52
|
-
// Auto-bootstrap: download llama-cli directly from GitHub releases
|
|
53
|
-
if (!this.binaryPath) {
|
|
54
|
-
const downloaded = await this.downloadLlamaCli(onProgress);
|
|
55
|
-
if (downloaded) {
|
|
56
|
-
this.binaryPath = await this.resolveBinaryPath();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// Legacy fallback: try npm package install
|
|
60
|
-
if (!this.binaryPath) {
|
|
61
|
-
const packageName = this.getPlatformPackageName();
|
|
62
|
-
if (packageName) {
|
|
63
|
-
const installed = await this.installSidecarBinary(packageName, onProgress);
|
|
64
|
-
if (installed) {
|
|
65
|
-
this.binaryPath = await this.resolveBinaryPath();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (!this.binaryPath) {
|
|
70
|
-
onProgress?.('⚠ Inference engine not found. Rigour will auto-download on next attempt.');
|
|
71
|
-
throw new Error(`Sidecar binary not found. Check network connection and retry.`);
|
|
72
|
-
}
|
|
73
|
-
let executableCheck = ensureExecutableBinary(this.binaryPath);
|
|
74
|
-
// If the discovered binary is not executable, try a managed reinstall once.
|
|
75
|
-
const retryPackage = this.getPlatformPackageName();
|
|
76
|
-
if (!executableCheck.ok && retryPackage) {
|
|
77
|
-
onProgress?.('⚠ Inference engine is present but not executable. Reinstalling managed sidecar...');
|
|
78
|
-
const installed = await this.installSidecarBinary(retryPackage, onProgress);
|
|
79
|
-
if (installed) {
|
|
80
|
-
const refreshedPath = await this.resolveBinaryPath();
|
|
81
|
-
if (refreshedPath) {
|
|
82
|
-
this.binaryPath = refreshedPath;
|
|
83
|
-
executableCheck = ensureExecutableBinary(this.binaryPath);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (!executableCheck.ok) {
|
|
88
|
-
throw new Error(`Sidecar binary is not executable: ${this.binaryPath}. Run: chmod +x "${this.binaryPath}"`);
|
|
89
|
-
}
|
|
90
|
-
if (executableCheck.fixed) {
|
|
91
|
-
onProgress?.('✓ Fixed execute permission for inference engine');
|
|
92
|
-
}
|
|
58
|
+
this.binaryPath = await this.resolveBinaryPath() ?? await installLlamaEngine(onProgress);
|
|
93
59
|
onProgress?.('✓ Inference engine ready');
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const modelInfo = getModelInfo(this.tier);
|
|
97
|
-
onProgress?.(`⬇ Downloading analysis model (${modelInfo.sizeHuman})...`);
|
|
98
|
-
}
|
|
99
|
-
this.modelPath = await ensureModel(this.tier, (msg, percent) => {
|
|
100
|
-
if (percent !== undefined && percent < 100) {
|
|
60
|
+
await ensureModel(this.tier, (msg, percent) => {
|
|
61
|
+
if (percent !== undefined && percent < 100)
|
|
101
62
|
onProgress?.(` ${msg}`);
|
|
102
|
-
}
|
|
103
63
|
});
|
|
64
|
+
this.model = await getCachedModel(this.tier);
|
|
65
|
+
if (!this.model) {
|
|
66
|
+
throw new Error(`Model for tier "${this.tier}" is not cached after download.`);
|
|
67
|
+
}
|
|
68
|
+
if (this.model.fallback) {
|
|
69
|
+
onProgress?.(` Using stock model ${this.model.info.name} (fine-tuned model not available).`);
|
|
70
|
+
}
|
|
71
|
+
this.schemaPath = await writeFindingsSchema();
|
|
104
72
|
onProgress?.('✓ Model ready');
|
|
105
73
|
}
|
|
74
|
+
/** The model file setup() loaded, or null before setup. */
|
|
75
|
+
getActiveModel() {
|
|
76
|
+
if (!this.model)
|
|
77
|
+
return null;
|
|
78
|
+
return { name: this.model.info.name, filename: this.model.info.filename, fallback: this.model.fallback };
|
|
79
|
+
}
|
|
106
80
|
async analyze(prompt, options) {
|
|
107
|
-
if (!this.binaryPath || !this.
|
|
81
|
+
if (!this.binaryPath || !this.model) {
|
|
108
82
|
throw new Error('Provider not set up. Call setup() first.');
|
|
109
83
|
}
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (options?.jsonMode) {
|
|
121
|
-
args.push('--json');
|
|
122
|
-
}
|
|
84
|
+
const timeoutMs = options?.timeout || DEFAULT_TIMEOUT_MS;
|
|
85
|
+
const args = buildLlamaArgs({
|
|
86
|
+
modelPath: this.model.path,
|
|
87
|
+
prompt,
|
|
88
|
+
maxTokens: options?.maxTokens || 1024,
|
|
89
|
+
threads: this.threads,
|
|
90
|
+
temperature: options?.temperature ?? 0.1,
|
|
91
|
+
schemaPath: options?.jsonMode ? this.schemaPath ?? undefined : undefined,
|
|
92
|
+
});
|
|
93
|
+
let result;
|
|
123
94
|
try {
|
|
124
|
-
|
|
125
|
-
timeout: options?.timeout || 60000,
|
|
126
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
127
|
-
env: { ...process.env, LLAMA_LOG_DISABLE: '1' },
|
|
128
|
-
};
|
|
129
|
-
const runInference = async () => {
|
|
130
|
-
return process.platform === 'win32' && this.binaryPath.endsWith('.cmd')
|
|
131
|
-
? await execFileAsync('cmd.exe', ['/d', '/s', '/c', [this.binaryPath, ...args].map(quoteCmdArg).join(' ')], execOptions)
|
|
132
|
-
: await execFileAsync(this.binaryPath, args, execOptions);
|
|
133
|
-
};
|
|
134
|
-
let stdout;
|
|
135
|
-
try {
|
|
136
|
-
({ stdout } = await runInference());
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
// One retry path for stale/bad file mode in packaged installs.
|
|
140
|
-
if (error?.code === 'EACCES') {
|
|
141
|
-
const check = ensureExecutableBinary(this.binaryPath);
|
|
142
|
-
if (check.ok) {
|
|
143
|
-
({ stdout } = await runInference());
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
const packageName = this.getPlatformPackageName();
|
|
147
|
-
if (packageName) {
|
|
148
|
-
const installed = await this.installSidecarBinary(packageName);
|
|
149
|
-
if (installed) {
|
|
150
|
-
const refreshedPath = await this.resolveBinaryPath();
|
|
151
|
-
if (refreshedPath) {
|
|
152
|
-
this.binaryPath = refreshedPath;
|
|
153
|
-
const refreshedCheck = ensureExecutableBinary(this.binaryPath);
|
|
154
|
-
if (refreshedCheck.ok) {
|
|
155
|
-
({ stdout } = await runInference());
|
|
156
|
-
return stdout.trim();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
// llama.cpp sometimes outputs to stderr for diagnostics — ignore
|
|
169
|
-
return stdout.trim();
|
|
95
|
+
result = await runProcess(this.binaryPath, args, { timeoutMs });
|
|
170
96
|
}
|
|
171
97
|
catch (error) {
|
|
172
|
-
if (error
|
|
173
|
-
throw new Error(`Inference
|
|
174
|
-
}
|
|
175
|
-
if (error?.code === 'EACCES') {
|
|
176
|
-
throw new Error(`Inference binary is not executable: ${this.binaryPath}. Run: chmod +x "${this.binaryPath}"`);
|
|
177
|
-
}
|
|
178
|
-
throw new Error(`Inference failed: ${error.message}`);
|
|
98
|
+
if (error instanceof ProcessTimeoutError)
|
|
99
|
+
throw new Error(`Inference ${error.message}`);
|
|
100
|
+
throw new Error(`Inference failed: ${error?.message ?? String(error)}`);
|
|
179
101
|
}
|
|
102
|
+
if (result.code !== 0) {
|
|
103
|
+
const detail = result.stderr.trim().split(/\r?\n/).slice(-3).join(' | ');
|
|
104
|
+
throw new Error(`Inference failed (exit ${result.code}): ${detail}`);
|
|
105
|
+
}
|
|
106
|
+
return cleanLlamaOutput(result.stdout);
|
|
180
107
|
}
|
|
181
108
|
dispose() {
|
|
182
|
-
// No persistent process to clean up
|
|
183
109
|
this.binaryPath = null;
|
|
184
|
-
this.
|
|
185
|
-
}
|
|
186
|
-
getPlatformKey() {
|
|
187
|
-
return `${os.platform()}-${os.arch()}`;
|
|
188
|
-
}
|
|
189
|
-
getPlatformPackageName() {
|
|
190
|
-
const platformKey = this.getPlatformKey();
|
|
191
|
-
return PLATFORM_PACKAGES[platformKey];
|
|
110
|
+
this.model = null;
|
|
192
111
|
}
|
|
112
|
+
/** First candidate binary that exists, is executable and passes the probe. */
|
|
193
113
|
async resolveBinaryPath() {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Strategy 1: Check @rigour-labs/brain-{platform} optional dependency
|
|
202
|
-
const packageName = PLATFORM_PACKAGES[platformKey];
|
|
203
|
-
if (packageName) {
|
|
204
|
-
// Prefer Rigour-managed sidecar install root first to avoid brittle global/homebrew layouts.
|
|
205
|
-
const managedPath = path.join(SIDECAR_INSTALL_DIR, 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain');
|
|
206
|
-
const managedCandidates = os.platform() === 'win32'
|
|
207
|
-
? [managedPath + '.exe', managedPath + '.cmd', managedPath]
|
|
208
|
-
: [managedPath];
|
|
209
|
-
for (const managedBinPath of managedCandidates) {
|
|
210
|
-
if (await fs.pathExists(managedBinPath)) {
|
|
211
|
-
return managedBinPath;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
try {
|
|
215
|
-
const require = createRequire(import.meta.url);
|
|
216
|
-
const pkgJsonPath = require.resolve(path.posix.join(packageName, 'package.json'));
|
|
217
|
-
const pkgDir = path.dirname(pkgJsonPath);
|
|
218
|
-
const resolvedBin = path.join(pkgDir, 'bin', 'rigour-brain');
|
|
219
|
-
const resolvedCandidates = os.platform() === 'win32'
|
|
220
|
-
? [resolvedBin + '.exe', resolvedBin + '.cmd', resolvedBin]
|
|
221
|
-
: [resolvedBin];
|
|
222
|
-
for (const resolvedBinPath of resolvedCandidates) {
|
|
223
|
-
if (await fs.pathExists(resolvedBinPath)) {
|
|
224
|
-
return resolvedBinPath;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
// Package not resolvable from current runtime
|
|
230
|
-
}
|
|
231
|
-
try {
|
|
232
|
-
// Try to resolve from node_modules
|
|
233
|
-
const possiblePaths = [
|
|
234
|
-
// From current working directory
|
|
235
|
-
path.join(process.cwd(), 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain'),
|
|
236
|
-
// From rigour-core node_modules
|
|
237
|
-
path.join(__dirname, '..', '..', '..', 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain'),
|
|
238
|
-
// From monorepo root when rigour-core is nested under packages/
|
|
239
|
-
path.join(__dirname, '..', '..', '..', '..', '..', 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain'),
|
|
240
|
-
// From global node_modules
|
|
241
|
-
path.join(os.homedir(), '.npm-global', 'lib', 'node_modules', ...packageName.split('/'), 'bin', 'rigour-brain'),
|
|
242
|
-
];
|
|
243
|
-
for (const p of possiblePaths) {
|
|
244
|
-
const candidates = os.platform() === 'win32' ? [p + '.exe', p + '.cmd', p] : [p];
|
|
245
|
-
for (const binPath of candidates) {
|
|
246
|
-
if (await fs.pathExists(binPath)) {
|
|
247
|
-
return binPath;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
catch {
|
|
253
|
-
// Package not installed
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// Strategy 2: Check ~/.rigour/bin/
|
|
257
|
-
const localBin = path.join(os.homedir(), '.rigour', 'bin', 'rigour-brain');
|
|
258
|
-
const localCandidates = os.platform() === 'win32'
|
|
259
|
-
? [localBin + '.exe', localBin + '.cmd', localBin]
|
|
260
|
-
: [localBin];
|
|
261
|
-
for (const localBinPath of localCandidates) {
|
|
262
|
-
if (await fs.pathExists(localBinPath)) {
|
|
263
|
-
return localBinPath;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
// Strategy 3: Check PATH for llama-cli (llama.cpp CLI)
|
|
267
|
-
const locator = os.platform() === 'win32' ? 'where' : 'which';
|
|
268
|
-
try {
|
|
269
|
-
const { stdout } = await execFileAsync(locator, ['llama-cli']);
|
|
270
|
-
const llamaPath = stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean) || '';
|
|
271
|
-
if (llamaPath && await fs.pathExists(llamaPath)) {
|
|
272
|
-
return llamaPath;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
// Not in PATH
|
|
277
|
-
}
|
|
278
|
-
// Strategy 4: Check for llama.cpp server-style binary names
|
|
279
|
-
const altNames = ['llama-cli', 'llama', 'main'];
|
|
280
|
-
for (const name of altNames) {
|
|
281
|
-
try {
|
|
282
|
-
const { stdout } = await execFileAsync(locator, [name]);
|
|
283
|
-
const resolved = stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean);
|
|
284
|
-
if (resolved && await fs.pathExists(resolved))
|
|
285
|
-
return resolved;
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
// Continue
|
|
289
|
-
}
|
|
114
|
+
for (const candidate of await this.candidateBinaries()) {
|
|
115
|
+
if (!(await fs.pathExists(candidate)))
|
|
116
|
+
continue;
|
|
117
|
+
if (!ensureExecutableBinary(candidate).ok)
|
|
118
|
+
continue;
|
|
119
|
+
if ((await probeBinary(candidate)).ok)
|
|
120
|
+
return candidate;
|
|
290
121
|
}
|
|
291
122
|
return null;
|
|
292
123
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const zipPath = path.join(BINARY_DIR, assetName);
|
|
304
|
-
const binaryName = os.platform() === 'win32' ? 'llama-cli.exe' : 'llama-cli';
|
|
305
|
-
const destPath = path.join(BINARY_DIR, binaryName);
|
|
306
|
-
// Already downloaded
|
|
307
|
-
if (await fs.pathExists(destPath))
|
|
308
|
-
return true;
|
|
309
|
-
onProgress?.(`⬇ Downloading inference engine (llama.cpp ${LLAMA_RELEASE_TAG})...`);
|
|
310
|
-
try {
|
|
311
|
-
await fs.ensureDir(BINARY_DIR);
|
|
312
|
-
// Download zip
|
|
313
|
-
const response = await fetch(url, { redirect: 'follow' });
|
|
314
|
-
if (!response.ok) {
|
|
315
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
316
|
-
}
|
|
317
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
318
|
-
await fs.writeFile(zipPath, buffer);
|
|
319
|
-
onProgress?.(' Extracting...');
|
|
320
|
-
// Extract llama-cli from zip
|
|
321
|
-
if (os.platform() === 'win32') {
|
|
322
|
-
await execFileAsync('powershell', [
|
|
323
|
-
'-Command',
|
|
324
|
-
`Expand-Archive -Path "${zipPath}" -DestinationPath "${BINARY_DIR}/llama-extract" -Force`,
|
|
325
|
-
], { timeout: 60000 });
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
await execFileAsync('unzip', ['-o', zipPath, '-d', path.join(BINARY_DIR, 'llama-extract')], {
|
|
329
|
-
timeout: 60000,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
// Find llama-cli in extracted files
|
|
333
|
-
const extractDir = path.join(BINARY_DIR, 'llama-extract');
|
|
334
|
-
const llamaBin = await findFileRecursive(extractDir, binaryName);
|
|
335
|
-
if (!llamaBin) {
|
|
336
|
-
throw new Error(`${binaryName} not found in release archive`);
|
|
337
|
-
}
|
|
338
|
-
await fs.copy(llamaBin, destPath);
|
|
339
|
-
if (os.platform() !== 'win32') {
|
|
340
|
-
await fs.chmod(destPath, 0o755);
|
|
341
|
-
}
|
|
342
|
-
// Cleanup
|
|
343
|
-
await fs.remove(zipPath);
|
|
344
|
-
await fs.remove(extractDir);
|
|
345
|
-
onProgress?.(`✓ Inference engine ready (${LLAMA_RELEASE_TAG})`);
|
|
346
|
-
return true;
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
const reason = typeof error?.message === 'string' ? error.message : 'unknown error';
|
|
350
|
-
onProgress?.(`⚠ Download failed: ${reason}`);
|
|
351
|
-
// Cleanup partial downloads
|
|
352
|
-
await fs.remove(zipPath).catch(() => { });
|
|
353
|
-
await fs.remove(path.join(BINARY_DIR, 'llama-extract')).catch(() => { });
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
/** Legacy: install via npm brain package */
|
|
358
|
-
async installSidecarBinary(packageName, onProgress) {
|
|
359
|
-
onProgress?.(`⬇ Trying npm fallback: ${packageName}`);
|
|
360
|
-
try {
|
|
361
|
-
await fs.ensureDir(SIDECAR_INSTALL_DIR);
|
|
362
|
-
await execFileAsync(os.platform() === 'win32' ? 'npm.cmd' : 'npm', ['install', '--no-save', '--no-package-lock', '--prefix', SIDECAR_INSTALL_DIR, packageName], {
|
|
363
|
-
cwd: SIDECAR_INSTALL_DIR,
|
|
364
|
-
timeout: 120000,
|
|
365
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
const reason = typeof error?.message === 'string' ? error.message : 'unknown install error';
|
|
370
|
-
onProgress?.(`⚠ npm install failed: ${reason}`);
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
onProgress?.(`✓ Installed ${packageName}`);
|
|
374
|
-
return true;
|
|
124
|
+
async candidateBinaries() {
|
|
125
|
+
const candidates = [
|
|
126
|
+
managedEnginePath(),
|
|
127
|
+
path.join(RIGOUR_DIR, 'bin', llamaBinaryName()),
|
|
128
|
+
...brainPackageBinaries(),
|
|
129
|
+
];
|
|
130
|
+
const onPath = await findOnPath('llama-cli');
|
|
131
|
+
if (onPath)
|
|
132
|
+
candidates.push(onPath);
|
|
133
|
+
return candidates.filter((c, i) => candidates.indexOf(c) === i);
|
|
375
134
|
}
|
|
376
135
|
}
|
|
377
|
-
function
|
|
378
|
-
|
|
136
|
+
function brainPackageBinaries() {
|
|
137
|
+
const packageName = PLATFORM_PACKAGES[`${os.platform()}-${os.arch()}`];
|
|
138
|
+
if (!packageName)
|
|
139
|
+
return [];
|
|
140
|
+
const suffixes = os.platform() === 'win32' ? ['.exe', '.cmd', ''] : [''];
|
|
141
|
+
try {
|
|
142
|
+
const require = createRequire(import.meta.url);
|
|
143
|
+
const pkgDir = path.dirname(require.resolve(path.posix.join(packageName, 'package.json')));
|
|
144
|
+
return suffixes.map(s => path.join(pkgDir, 'bin', `rigour-brain${s}`));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
379
149
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (found)
|
|
388
|
-
return found;
|
|
389
|
-
}
|
|
390
|
-
else if (entry.name === filename) {
|
|
391
|
-
return fullPath;
|
|
392
|
-
}
|
|
150
|
+
async function findOnPath(name) {
|
|
151
|
+
const locator = os.platform() === 'win32' ? 'where' : 'which';
|
|
152
|
+
try {
|
|
153
|
+
const { code, stdout } = await runProcess(locator, [name], { timeoutMs: 5_000 });
|
|
154
|
+
if (code !== 0)
|
|
155
|
+
return null;
|
|
156
|
+
return stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean) ?? null;
|
|
393
157
|
}
|
|
394
|
-
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function writeFindingsSchema() {
|
|
163
|
+
const schemaPath = path.join(managedEngineDir(), '..', 'findings.schema.json');
|
|
164
|
+
await fs.ensureDir(path.dirname(schemaPath));
|
|
165
|
+
await fs.writeJson(schemaPath, FINDINGS_JSON_SCHEMA);
|
|
166
|
+
return schemaPath;
|
|
395
167
|
}
|