@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.
Files changed (100) hide show
  1. package/dist/deep/code-context.d.ts +28 -0
  2. package/dist/deep/code-context.js +93 -0
  3. package/dist/deep/code-context.test.d.ts +1 -0
  4. package/dist/deep/code-context.test.js +48 -0
  5. package/dist/deep/code-pass.d.ts +15 -0
  6. package/dist/deep/code-pass.js +44 -0
  7. package/dist/deep/code-review-prompt.d.ts +17 -0
  8. package/dist/deep/code-review-prompt.js +30 -0
  9. package/dist/deep/code-review-prompt.test.d.ts +1 -0
  10. package/dist/deep/code-review-prompt.test.js +20 -0
  11. package/dist/deep/code-verifier.d.ts +19 -0
  12. package/dist/deep/code-verifier.js +48 -0
  13. package/dist/deep/code-verifier.test.d.ts +1 -0
  14. package/dist/deep/code-verifier.test.js +46 -0
  15. package/dist/deep/fact-extractor.d.ts +7 -1
  16. package/dist/deep/fact-extractor.js +16 -8
  17. package/dist/deep/fact-extractor.test.js +34 -1
  18. package/dist/deep/facts-pass.d.ts +24 -0
  19. package/dist/deep/facts-pass.js +69 -0
  20. package/dist/deep/index.d.ts +6 -1
  21. package/dist/deep/index.js +5 -1
  22. package/dist/deep/parse-findings.d.ts +12 -0
  23. package/dist/deep/parse-findings.js +73 -0
  24. package/dist/deep/parse-findings.test.d.ts +1 -0
  25. package/dist/deep/parse-findings.test.js +25 -0
  26. package/dist/deep/prompts.d.ts +1 -1
  27. package/dist/deep/prompts.js +7 -8
  28. package/dist/deep/prompts.test.js +15 -0
  29. package/dist/gates/context.js +7 -0
  30. package/dist/gates/deep-analysis.d.ts +31 -8
  31. package/dist/gates/deep-analysis.js +165 -306
  32. package/dist/gates/deep-analysis.test.d.ts +1 -0
  33. package/dist/gates/deep-analysis.test.js +86 -0
  34. package/dist/gates/deep-runner.d.ts +15 -0
  35. package/dist/gates/deep-runner.js +65 -0
  36. package/dist/gates/dependency.js +4 -0
  37. package/dist/gates/hallucinated-imports/index.js +13 -9
  38. package/dist/gates/hallucinated-imports.test.js +10 -0
  39. package/dist/gates/js-style-context.d.ts +3 -0
  40. package/dist/gates/js-style-context.js +65 -0
  41. package/dist/gates/js-style-context.test.d.ts +1 -0
  42. package/dist/gates/js-style-context.test.js +35 -0
  43. package/dist/gates/logic-drift-git-base.d.ts +7 -0
  44. package/dist/gates/logic-drift-git-base.js +41 -0
  45. package/dist/gates/logic-drift.d.ts +3 -3
  46. package/dist/gates/logic-drift.js +39 -7
  47. package/dist/gates/logic-drift.test.d.ts +1 -0
  48. package/dist/gates/logic-drift.test.js +45 -0
  49. package/dist/gates/runner.js +5 -40
  50. package/dist/gates/runner.test.js +37 -25
  51. package/dist/gates/scoped-gates.test.d.ts +1 -0
  52. package/dist/gates/scoped-gates.test.js +52 -0
  53. package/dist/gates/security-command-execution.d.ts +5 -0
  54. package/dist/gates/security-command-execution.js +123 -0
  55. package/dist/gates/security-patterns-data.js +1 -17
  56. package/dist/gates/security-patterns.d.ts +0 -5
  57. package/dist/gates/security-patterns.js +11 -19
  58. package/dist/gates/security-patterns.test.js +37 -2
  59. package/dist/gates/style-drift.d.ts +4 -4
  60. package/dist/gates/style-drift.js +15 -16
  61. package/dist/gates/style-drift.test.d.ts +1 -0
  62. package/dist/gates/style-drift.test.js +26 -0
  63. package/dist/index.d.ts +6 -1
  64. package/dist/index.js +6 -1
  65. package/dist/inference/http-download.d.ts +22 -0
  66. package/dist/inference/http-download.js +132 -0
  67. package/dist/inference/http-download.test.d.ts +1 -0
  68. package/dist/inference/http-download.test.js +109 -0
  69. package/dist/inference/index.d.ts +5 -1
  70. package/dist/inference/index.js +2 -1
  71. package/dist/inference/llama-engine.d.ts +27 -0
  72. package/dist/inference/llama-engine.js +152 -0
  73. package/dist/inference/llama-engine.test.d.ts +1 -0
  74. package/dist/inference/llama-engine.test.js +51 -0
  75. package/dist/inference/llama-process.d.ts +72 -0
  76. package/dist/inference/llama-process.js +126 -0
  77. package/dist/inference/llama-process.test.d.ts +1 -0
  78. package/dist/inference/llama-process.test.js +61 -0
  79. package/dist/inference/model-download.test.d.ts +1 -0
  80. package/dist/inference/model-download.test.js +73 -0
  81. package/dist/inference/model-manager.d.ts +14 -1
  82. package/dist/inference/model-manager.js +65 -80
  83. package/dist/inference/sidecar-provider.d.ts +19 -10
  84. package/dist/inference/sidecar-provider.js +115 -343
  85. package/dist/templates/universal-config.js +0 -2
  86. package/dist/types/index.d.ts +58 -14
  87. package/dist/types/index.js +9 -2
  88. package/dist/utils/deep-status.d.ts +7 -0
  89. package/dist/utils/deep-status.js +11 -0
  90. package/dist/utils/diff.d.ts +3 -0
  91. package/dist/utils/diff.js +52 -0
  92. package/dist/utils/diff.test.d.ts +1 -0
  93. package/dist/utils/diff.test.js +38 -0
  94. package/dist/utils/scanner.js +13 -2
  95. package/dist/utils/scanner.test.js +22 -8
  96. package/dist/utils/scope.d.ts +15 -0
  97. package/dist/utils/scope.js +51 -0
  98. package/dist/utils/scope.test.d.ts +1 -0
  99. package/dist/utils/scope.test.js +38 -0
  100. package/package.json +6 -6
@@ -1,20 +1,25 @@
1
1
  /**
2
- * Sidecar Binary Provider — runs inference via pre-compiled llama.cpp binary.
3
- * Auto-downloads llama-cli from GitHub releases on first use.
4
- * Falls back to npm packages or PATH lookup for development/manual installs.
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, isModelCached, getModelInfo } from './model-manager.js';
16
+ import { ensureModel, getCachedModel } from './model-manager.js';
13
17
  import { ensureExecutableBinary } from './executable.js';
14
- const execFileAsync = promisify(execFile);
15
- const SIDECAR_INSTALL_DIR = path.join(os.homedir(), '.rigour', 'sidecar');
16
- const BINARY_DIR = path.join(os.homedir(), '.rigour', 'bin');
17
- /** Platform → npm package mapping (legacy, still checked) */
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
- modelPath = null;
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
- return binary !== null;
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
- // 1. Check/resolve binary
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
- // 2. Ensure model is downloaded
95
- if (!(await isModelCached(this.tier))) {
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.modelPath) {
81
+ if (!this.binaryPath || !this.model) {
108
82
  throw new Error('Provider not set up. Call setup() first.');
109
83
  }
110
- const args = [
111
- '--model', this.modelPath,
112
- '--prompt', prompt,
113
- '--n-predict', String(options?.maxTokens || 512),
114
- '--threads', String(this.threads),
115
- '--temp', String(options?.temperature || 0.1),
116
- '--no-display-prompt', // Don't echo the prompt
117
- '--log-disable', // Suppress llama.cpp logging
118
- ];
119
- // JSON grammar constraint if available
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
- const execOptions = {
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.killed) {
173
- throw new Error(`Inference timed out after ${(options?.timeout || 60000) / 1000}s`);
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.modelPath = null;
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 platformKey = this.getPlatformKey();
195
- // Strategy 0: Check ~/.rigour/bin/llama-cli (auto-downloaded from GitHub releases)
196
- const binaryName = os.platform() === 'win32' ? 'llama-cli.exe' : 'llama-cli';
197
- const autoDownloadedPath = path.join(BINARY_DIR, binaryName);
198
- if (await fs.pathExists(autoDownloadedPath)) {
199
- return autoDownloadedPath;
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
- * Download llama-cli directly from llama.cpp GitHub releases.
295
- * Extracts the binary to ~/.rigour/bin/llama-cli
296
- */
297
- async downloadLlamaCli(onProgress) {
298
- const platformKey = this.getPlatformKey();
299
- const assetName = LLAMA_RELEASE_ASSETS[platformKey];
300
- if (!assetName)
301
- return false;
302
- const url = `${LLAMA_RELEASE_BASE}/${assetName}`;
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 quoteCmdArg(value) {
378
- return `"${value.replace(/"/g, '\\"')}"`;
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
- /** Recursively find a file by name in a directory */
381
- async function findFileRecursive(dir, filename) {
382
- const entries = await fs.readdir(dir, { withFileTypes: true });
383
- for (const entry of entries) {
384
- const fullPath = path.join(dir, entry.name);
385
- if (entry.isDirectory()) {
386
- const found = await findFileRecursive(fullPath, filename);
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
- return null;
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
  }
@@ -251,9 +251,7 @@ export const UNIVERSAL_CONFIG = {
251
251
  pro: false,
252
252
  provider: 'local',
253
253
  threads: 4,
254
- max_tokens: 512,
255
254
  temperature: 0.1,
256
- timeout_ms: 60000,
257
255
  checks: {
258
256
  solid: true,
259
257
  dry: true,