@rigour-labs/core 6.2.4 → 6.3.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/README.md +2 -0
- 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/discovery-signals.d.ts +27 -0
- package/dist/discovery-signals.js +96 -0
- package/dist/discovery.d.ts +1 -2
- package/dist/discovery.js +36 -49
- package/dist/discovery.test.js +84 -71
- package/dist/gates/context.js +7 -0
- package/dist/gates/deep-analysis.d.ts +33 -8
- package/dist/gates/deep-analysis.js +169 -306
- package/dist/gates/deep-analysis.test.d.ts +1 -0
- package/dist/gates/deep-analysis.test.js +86 -0
- package/dist/gates/deep-intent.d.ts +3 -0
- package/dist/gates/deep-intent.js +57 -0
- package/dist/gates/deep-intent.test.d.ts +1 -0
- package/dist/gates/deep-intent.test.js +51 -0
- package/dist/gates/deep-runner.d.ts +15 -0
- package/dist/gates/deep-runner.js +66 -0
- package/dist/gates/dependency.js +4 -0
- package/dist/gates/deprecated-apis-ast.d.ts +8 -0
- package/dist/gates/deprecated-apis-ast.js +25 -0
- package/dist/gates/deprecated-apis-rules-node.js +2 -8
- package/dist/gates/deprecated-apis.js +5 -1
- package/dist/gates/deprecated-apis.test.js +30 -0
- package/dist/gates/frontend-secret-exposure.js +5 -1
- package/dist/gates/frontend-secret-exposure.test.js +20 -0
- package/dist/gates/hallucinated-imports/framework-modules.d.ts +14 -0
- package/dist/gates/hallucinated-imports/framework-modules.js +75 -0
- package/dist/gates/hallucinated-imports/index.js +14 -6
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +16 -12
- package/dist/gates/hallucinated-imports/js-resolver.js +65 -153
- package/dist/gates/hallucinated-imports/ts-path-target.js +16 -0
- package/dist/gates/hallucinated-imports/tsconfig-paths.d.ts +16 -0
- package/dist/gates/hallucinated-imports/tsconfig-paths.js +120 -0
- package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +1 -0
- package/dist/gates/hallucinated-imports-sveltekit.test.js +108 -0
- package/dist/gates/logic-drift.js +7 -1
- package/dist/gates/runner.js +10 -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/semantic-bugs.d.ts +23 -0
- package/dist/gates/semantic-bugs.js +48 -0
- package/dist/gates/semantic-bugs.test.d.ts +1 -0
- package/dist/gates/semantic-bugs.test.js +70 -0
- package/dist/gates/side-effect-analysis/index.js +11 -1
- package/dist/gates/side-effect-analysis.test.d.ts +1 -0
- package/dist/gates/side-effect-analysis.test.js +162 -0
- package/dist/gates/side-effect-helpers/pattern-detection.js +35 -7
- package/dist/gates/side-effect-helpers/resource-tracking.js +17 -10
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +6 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +20 -0
- package/dist/gates/style-drift.js +3 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +10 -2
- 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 +125 -342
- package/dist/inference/types.d.ts +2 -0
- package/dist/semantic/ast.d.ts +29 -0
- package/dist/semantic/ast.js +122 -0
- package/dist/semantic/benchmark.d.ts +16 -0
- package/dist/semantic/benchmark.js +31 -0
- package/dist/semantic/benchmark.test.d.ts +1 -0
- package/dist/semantic/benchmark.test.js +18 -0
- package/dist/semantic/engine.d.ts +8 -0
- package/dist/semantic/engine.js +45 -0
- package/dist/semantic/intent/benchmark.d.ts +31 -0
- package/dist/semantic/intent/benchmark.js +74 -0
- package/dist/semantic/intent/intent-check.d.ts +43 -0
- package/dist/semantic/intent/intent-check.js +72 -0
- package/dist/semantic/intent/intent.test.d.ts +1 -0
- package/dist/semantic/intent/intent.test.js +46 -0
- package/dist/semantic/intent/parallel-reads.d.ts +23 -0
- package/dist/semantic/intent/parallel-reads.js +59 -0
- package/dist/semantic/learn/benchmark.d.ts +20 -0
- package/dist/semantic/learn/benchmark.js +71 -0
- package/dist/semantic/learn/compile.d.ts +4 -0
- package/dist/semantic/learn/compile.js +56 -0
- package/dist/semantic/learn/conditions.d.ts +22 -0
- package/dist/semantic/learn/conditions.js +93 -0
- package/dist/semantic/learn/extract.d.ts +30 -0
- package/dist/semantic/learn/extract.js +122 -0
- package/dist/semantic/learn/git-trees.d.ts +8 -0
- package/dist/semantic/learn/git-trees.js +32 -0
- package/dist/semantic/learn/identity.d.ts +18 -0
- package/dist/semantic/learn/identity.js +97 -0
- package/dist/semantic/learn/learn.d.ts +25 -0
- package/dist/semantic/learn/learn.js +109 -0
- package/dist/semantic/learn/learn.test.d.ts +1 -0
- package/dist/semantic/learn/learn.test.js +67 -0
- package/dist/semantic/learn/store.d.ts +4 -0
- package/dist/semantic/learn/store.js +39 -0
- package/dist/semantic/learn/types.d.ts +468 -0
- package/dist/semantic/learn/types.js +55 -0
- package/dist/semantic/origins.d.ts +42 -0
- package/dist/semantic/origins.js +149 -0
- package/dist/semantic/origins.test.d.ts +1 -0
- package/dist/semantic/origins.test.js +77 -0
- package/dist/semantic/program.d.ts +13 -0
- package/dist/semantic/program.js +62 -0
- package/dist/semantic/rules/credential-redirect.d.ts +2 -0
- package/dist/semantic/rules/credential-redirect.js +209 -0
- package/dist/semantic/rules/degraded-response-cached.d.ts +2 -0
- package/dist/semantic/rules/degraded-response-cached.js +271 -0
- package/dist/semantic/rules/in-memory-aggregation.d.ts +2 -0
- package/dist/semantic/rules/in-memory-aggregation.js +179 -0
- package/dist/semantic/summaries.d.ts +18 -0
- package/dist/semantic/summaries.js +33 -0
- package/dist/semantic/types.d.ts +32 -0
- package/dist/semantic/types.js +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/team-diagnostics.d.ts +22 -0
- package/dist/storage/team-diagnostics.js +58 -0
- package/dist/storage/team-diagnostics.test.d.ts +1 -0
- package/dist/storage/team-diagnostics.test.js +51 -0
- package/dist/storage/team-schema.d.ts +6 -0
- package/dist/storage/team-schema.js +82 -0
- package/dist/storage/team-store.js +25 -72
- package/dist/templates/universal-config.js +4 -2
- package/dist/types/index.d.ts +114 -14
- package/dist/types/index.js +16 -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,26 @@
|
|
|
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
|
|
7
|
-
import { promisify } from 'util';
|
|
12
|
+
import crypto from 'crypto';
|
|
8
13
|
import path from 'path';
|
|
9
14
|
import os from 'os';
|
|
10
15
|
import fs from 'fs-extra';
|
|
11
16
|
import { createRequire } from 'module';
|
|
12
|
-
import { ensureModel,
|
|
17
|
+
import { ensureModel, getCachedModel } from './model-manager.js';
|
|
13
18
|
import { ensureExecutableBinary } from './executable.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
import { installLlamaEngine, llamaBinaryName, managedEngineDir, managedEnginePath, probeBinary } from './llama-engine.js';
|
|
20
|
+
import { buildLlamaArgs, cleanLlamaOutput, FINDINGS_JSON_SCHEMA, ProcessTimeoutError, runProcess } from './llama-process.js';
|
|
21
|
+
import { RIGOUR_DIR } from '../storage/db.js';
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
23
|
+
/** Platform → legacy npm package that may carry a rigour-brain binary. */
|
|
18
24
|
const PLATFORM_PACKAGES = {
|
|
19
25
|
'darwin-arm64': '@rigour-labs/brain-darwin-arm64',
|
|
20
26
|
'darwin-x64': '@rigour-labs/brain-darwin-x64',
|
|
@@ -22,374 +28,151 @@ const PLATFORM_PACKAGES = {
|
|
|
22
28
|
'linux-arm64': '@rigour-labs/brain-linux-arm64',
|
|
23
29
|
'win32-x64': '@rigour-labs/brain-win-x64',
|
|
24
30
|
};
|
|
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
31
|
export class SidecarProvider {
|
|
36
32
|
name = 'sidecar';
|
|
37
33
|
binaryPath = null;
|
|
38
|
-
|
|
34
|
+
model = null;
|
|
35
|
+
schemaPath = null;
|
|
39
36
|
tier;
|
|
40
37
|
threads;
|
|
41
38
|
constructor(tier = 'lite', threads = 4) {
|
|
42
39
|
this.tier = tier;
|
|
43
40
|
this.threads = threads;
|
|
44
41
|
}
|
|
42
|
+
/** True when a llama-cli that actually runs is installed. */
|
|
45
43
|
async isAvailable() {
|
|
44
|
+
return (await this.findEngine()) !== null;
|
|
45
|
+
}
|
|
46
|
+
/** The llama-cli this provider would use and its version, or null if none runs. */
|
|
47
|
+
async findEngine() {
|
|
46
48
|
const binary = await this.resolveBinaryPath();
|
|
47
|
-
|
|
49
|
+
if (!binary)
|
|
50
|
+
return null;
|
|
51
|
+
return { path: binary, version: (await probeBinary(binary)).version };
|
|
52
|
+
}
|
|
53
|
+
/** Install the engine and model for this tier without running inference. */
|
|
54
|
+
async prepare(onProgress) {
|
|
55
|
+
await this.setup(onProgress);
|
|
56
|
+
this.dispose();
|
|
48
57
|
}
|
|
49
58
|
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
|
-
}
|
|
59
|
+
this.binaryPath = await this.resolveBinaryPath() ?? await installLlamaEngine(onProgress);
|
|
93
60
|
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) {
|
|
61
|
+
await ensureModel(this.tier, (msg, percent) => {
|
|
62
|
+
if (percent !== undefined && percent < 100)
|
|
101
63
|
onProgress?.(` ${msg}`);
|
|
102
|
-
}
|
|
103
64
|
});
|
|
65
|
+
this.model = await getCachedModel(this.tier);
|
|
66
|
+
if (!this.model) {
|
|
67
|
+
throw new Error(`Model for tier "${this.tier}" is not cached after download.`);
|
|
68
|
+
}
|
|
69
|
+
if (this.model.fallback) {
|
|
70
|
+
onProgress?.(` Using stock model ${this.model.info.name} (fine-tuned model not available).`);
|
|
71
|
+
}
|
|
72
|
+
this.schemaPath = await writeFindingsSchema();
|
|
104
73
|
onProgress?.('✓ Model ready');
|
|
105
74
|
}
|
|
75
|
+
/** The model file setup() loaded, or null before setup. */
|
|
76
|
+
getActiveModel() {
|
|
77
|
+
if (!this.model)
|
|
78
|
+
return null;
|
|
79
|
+
return { name: this.model.info.name, filename: this.model.info.filename, fallback: this.model.fallback };
|
|
80
|
+
}
|
|
106
81
|
async analyze(prompt, options) {
|
|
107
|
-
if (!this.binaryPath || !this.
|
|
82
|
+
if (!this.binaryPath || !this.model) {
|
|
108
83
|
throw new Error('Provider not set up. Call setup() first.');
|
|
109
84
|
}
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (options?.jsonMode) {
|
|
121
|
-
args.push('--json');
|
|
122
|
-
}
|
|
85
|
+
const timeoutMs = options?.timeout || DEFAULT_TIMEOUT_MS;
|
|
86
|
+
const args = buildLlamaArgs({
|
|
87
|
+
modelPath: this.model.path,
|
|
88
|
+
prompt,
|
|
89
|
+
maxTokens: options?.maxTokens || 1024,
|
|
90
|
+
threads: this.threads,
|
|
91
|
+
temperature: options?.temperature ?? 0.1,
|
|
92
|
+
schemaPath: options?.jsonSchema ? await schemaFileFor(options.jsonSchema) : options?.jsonMode ? this.schemaPath ?? undefined : undefined,
|
|
93
|
+
});
|
|
94
|
+
let result;
|
|
123
95
|
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();
|
|
96
|
+
result = await runProcess(this.binaryPath, args, { timeoutMs });
|
|
170
97
|
}
|
|
171
98
|
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}`);
|
|
99
|
+
if (error instanceof ProcessTimeoutError)
|
|
100
|
+
throw new Error(`Inference ${error.message}`);
|
|
101
|
+
throw new Error(`Inference failed: ${error?.message ?? String(error)}`);
|
|
179
102
|
}
|
|
103
|
+
if (result.code !== 0) {
|
|
104
|
+
const detail = result.stderr.trim().split(/\r?\n/).slice(-3).join(' | ');
|
|
105
|
+
throw new Error(`Inference failed (exit ${result.code}): ${detail}`);
|
|
106
|
+
}
|
|
107
|
+
return cleanLlamaOutput(result.stdout);
|
|
180
108
|
}
|
|
181
109
|
dispose() {
|
|
182
|
-
// No persistent process to clean up
|
|
183
110
|
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];
|
|
111
|
+
this.model = null;
|
|
192
112
|
}
|
|
113
|
+
/** First candidate binary that exists, is executable and passes the probe. */
|
|
193
114
|
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
|
-
}
|
|
115
|
+
for (const candidate of await this.candidateBinaries()) {
|
|
116
|
+
if (!(await fs.pathExists(candidate)))
|
|
117
|
+
continue;
|
|
118
|
+
if (!ensureExecutableBinary(candidate).ok)
|
|
119
|
+
continue;
|
|
120
|
+
if ((await probeBinary(candidate)).ok)
|
|
121
|
+
return candidate;
|
|
290
122
|
}
|
|
291
123
|
return null;
|
|
292
124
|
}
|
|
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
|
-
}
|
|
125
|
+
async candidateBinaries() {
|
|
126
|
+
const candidates = [
|
|
127
|
+
managedEnginePath(),
|
|
128
|
+
path.join(RIGOUR_DIR, 'bin', llamaBinaryName()),
|
|
129
|
+
...brainPackageBinaries(),
|
|
130
|
+
];
|
|
131
|
+
const onPath = await findOnPath('llama-cli');
|
|
132
|
+
if (onPath)
|
|
133
|
+
candidates.push(onPath);
|
|
134
|
+
return candidates.filter((c, i) => candidates.indexOf(c) === i);
|
|
356
135
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
onProgress?.(`⚠ npm install failed: ${reason}`);
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
onProgress?.(`✓ Installed ${packageName}`);
|
|
374
|
-
return true;
|
|
136
|
+
}
|
|
137
|
+
function brainPackageBinaries() {
|
|
138
|
+
const packageName = PLATFORM_PACKAGES[`${os.platform()}-${os.arch()}`];
|
|
139
|
+
if (!packageName)
|
|
140
|
+
return [];
|
|
141
|
+
const suffixes = os.platform() === 'win32' ? ['.exe', '.cmd', ''] : [''];
|
|
142
|
+
try {
|
|
143
|
+
const require = createRequire(import.meta.url);
|
|
144
|
+
const pkgDir = path.dirname(require.resolve(path.posix.join(packageName, 'package.json')));
|
|
145
|
+
return suffixes.map(s => path.join(pkgDir, 'bin', `rigour-brain${s}`));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return [];
|
|
375
149
|
}
|
|
376
150
|
}
|
|
377
|
-
function
|
|
378
|
-
|
|
151
|
+
async function findOnPath(name) {
|
|
152
|
+
const locator = os.platform() === 'win32' ? 'where' : 'which';
|
|
153
|
+
try {
|
|
154
|
+
const { code, stdout } = await runProcess(locator, [name], { timeoutMs: 5_000 });
|
|
155
|
+
if (code !== 0)
|
|
156
|
+
return null;
|
|
157
|
+
return stdout.split(/\r?\n/).map(s => s.trim()).find(Boolean) ?? null;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
379
162
|
}
|
|
380
|
-
/**
|
|
381
|
-
async function
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (found)
|
|
388
|
-
return found;
|
|
389
|
-
}
|
|
390
|
-
else if (entry.name === filename) {
|
|
391
|
-
return fullPath;
|
|
392
|
-
}
|
|
163
|
+
/** A schema file per distinct schema, written once and reused across calls. */
|
|
164
|
+
async function schemaFileFor(schema) {
|
|
165
|
+
const text = JSON.stringify(schema);
|
|
166
|
+
const schemaPath = path.join(managedEngineDir(), '..', 'schemas', `${crypto.createHash('sha256').update(text).digest('hex').slice(0, 16)}.json`);
|
|
167
|
+
if (!(await fs.pathExists(schemaPath))) {
|
|
168
|
+
await fs.ensureDir(path.dirname(schemaPath));
|
|
169
|
+
await fs.writeFile(schemaPath, text);
|
|
393
170
|
}
|
|
394
|
-
return
|
|
171
|
+
return schemaPath;
|
|
172
|
+
}
|
|
173
|
+
async function writeFindingsSchema() {
|
|
174
|
+
const schemaPath = path.join(managedEngineDir(), '..', 'findings.schema.json');
|
|
175
|
+
await fs.ensureDir(path.dirname(schemaPath));
|
|
176
|
+
await fs.writeJson(schemaPath, FINDINGS_JSON_SCHEMA);
|
|
177
|
+
return schemaPath;
|
|
395
178
|
}
|
|
@@ -29,6 +29,8 @@ export interface InferenceOptions {
|
|
|
29
29
|
temperature?: number;
|
|
30
30
|
timeout?: number;
|
|
31
31
|
jsonMode?: boolean;
|
|
32
|
+
/** Constrain local output to this JSON schema instead of the findings schema. */
|
|
33
|
+
jsonSchema?: Record<string, unknown>;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* A single finding from deep LLM analysis.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
export type FunctionLike = ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | ts.MethodDeclaration;
|
|
3
|
+
export declare function isFunctionLike(node: ts.Node): node is FunctionLike;
|
|
4
|
+
/** Drop parentheses, type assertions, non-null and `await` around an expression. */
|
|
5
|
+
export declare function unwrap(expr: ts.Expression): ts.Expression;
|
|
6
|
+
/**
|
|
7
|
+
* Symbol for a node, following import aliases to the declaration. For the
|
|
8
|
+
* name in a shorthand property (`{ headers }`) this is the variable it reads,
|
|
9
|
+
* not the property it creates.
|
|
10
|
+
*/
|
|
11
|
+
export declare function symbolOf(checker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined;
|
|
12
|
+
/** The function a call invokes, when it resolves to one declared in source. */
|
|
13
|
+
export declare function calledFunction(checker: ts.TypeChecker, call: ts.CallExpression): FunctionLike | undefined;
|
|
14
|
+
/** `fetch` in `fetch(...)`, `deps.fetch(...)`, `this.fetch(...)`. */
|
|
15
|
+
export declare function calleeName(call: ts.CallExpression): string | undefined;
|
|
16
|
+
export declare function enclosingFunction(node: ts.Node): FunctionLike | undefined;
|
|
17
|
+
/** Visit every descendant of `root` that belongs to `root` itself, not to a nested function. */
|
|
18
|
+
export declare function forEachOwnNode(root: ts.Node, visit: (node: ts.Node) => void): void;
|
|
19
|
+
export declare function forEachNode(root: ts.Node, visit: (node: ts.Node) => void): void;
|
|
20
|
+
/** Text of a property name (`a`, `'a'`, `"x-token"`), or undefined for computed names. */
|
|
21
|
+
export declare function propertyNameText(name: ts.PropertyName | undefined): string | undefined;
|
|
22
|
+
export declare function lineOf(node: ts.Node): number;
|
|
23
|
+
/** "http.ts:51" style location for evidence, relative to cwd. */
|
|
24
|
+
export declare function locationOf(cwd: string, node: ts.Node): string;
|
|
25
|
+
export declare function relativeFile(cwd: string, sf: ts.SourceFile): string;
|
|
26
|
+
/** Short, single-line source text for messages. */
|
|
27
|
+
export declare function snippet(node: ts.Node, max?: number): string;
|
|
28
|
+
/** True when any identifier inside `expr` resolves to `target`. */
|
|
29
|
+
export declare function mentionsSymbol(checker: ts.TypeChecker, expr: ts.Node, target: ts.Symbol): boolean;
|