moflo 4.10.1 → 4.10.3

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.
@@ -1,325 +0,0 @@
1
- /**
2
- * RVFA Appliance Builder -- Constructs self-contained .rvf appliance files.
3
- *
4
- * Creates a single binary containing kernel, runtime, Ruflo CLI, models/keys,
5
- * AgentDB data, and the verification suite. See ADR-058.
6
- */
7
- import { createHash, scryptSync, randomBytes, createCipheriv, createDecipheriv, } from 'node:crypto';
8
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
9
- import { dirname, join, resolve } from 'node:path';
10
- import { execSync } from 'node:child_process';
11
- import { RvfaWriter, } from './rvfa-format.js';
12
- import { locateMofloRootPath } from '../services/moflo-require.js';
13
- import { VERSION } from '../version.js';
14
- // ── Encryption Constants ─────────────────────────────────────
15
- const SCRYPT_KEY_LEN = 32;
16
- const SCRYPT_SALT_LEN = 32;
17
- const SCRYPT_OPTS = { N: 16384, r: 8, p: 1 };
18
- const AES_IV_LEN = 16;
19
- const AES_TAG_LEN = 16;
20
- const AES_ALG = 'aes-256-gcm';
21
- // ── Catalog ──────────────────────────────────────────────────
22
- const RUFLO_COMMANDS = 'init agent swarm memory mcp task session config status start workflow hooks hive-mind daemon neural security performance providers plugins deployment embeddings claims migrate process doctor completions'.split(' ');
23
- const AGENT_TYPES = 'coder reviewer tester planner researcher security-auditor backend-dev frontend-dev database-dev cicd-engineer api-docs system-architect code-analyzer analyst base-template-generator test-long-runner'.split(' ');
24
- const HOOK_TYPES = 'pre-edit post-edit pre-command post-command pre-task post-task session-start session-end session-restore notify route explain pretrain build-agents transfer teammate-idle task-completed'.split(' ');
25
- const WORKER_TYPES = 'ultralearn optimize consolidate predict audit map preload deepdive document refactor benchmark testgaps'.split(' ');
26
- const OFFLINE_MODELS = [
27
- { name: 'phi-3-mini-q4', format: 'gguf', sizeHint: '2.3GB', params: '3.8B' },
28
- { name: 'qwen2.5-coder-3b-q4', format: 'gguf', sizeHint: '1.7GB', params: '3B' },
29
- ];
30
- // ── API Key Encryption / Decryption ──────────────────────────
31
- /** Encrypt API keys from a .env file. Output: salt(32)+iv(16)+tag(16)+ciphertext */
32
- export function encryptApiKeys(envPath, passphrase) {
33
- const keys = parseEnvFile(readFileSync(envPath, 'utf-8'));
34
- const plaintext = Buffer.from(JSON.stringify(keys), 'utf-8');
35
- const salt = randomBytes(SCRYPT_SALT_LEN);
36
- const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
37
- const iv = randomBytes(AES_IV_LEN);
38
- const cipher = createCipheriv(AES_ALG, key, iv);
39
- const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
40
- return Buffer.concat([salt, iv, cipher.getAuthTag(), encrypted]);
41
- }
42
- /** Decrypt API keys previously encrypted with encryptApiKeys. */
43
- export function decryptApiKeys(buf, passphrase) {
44
- const minLen = SCRYPT_SALT_LEN + AES_IV_LEN + AES_TAG_LEN + 1;
45
- if (buf.length < minLen) {
46
- throw new Error(`Encrypted buffer too short: need >= ${minLen}B, got ${buf.length}B`);
47
- }
48
- let off = 0;
49
- const salt = buf.subarray(off, off += SCRYPT_SALT_LEN);
50
- const iv = buf.subarray(off, off += AES_IV_LEN);
51
- const tag = buf.subarray(off, off += AES_TAG_LEN);
52
- const ciphertext = buf.subarray(off);
53
- const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
54
- const decipher = createDecipheriv(AES_ALG, key, iv);
55
- decipher.setAuthTag(tag);
56
- return JSON.parse(Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf-8'));
57
- }
58
- export class RvfaBuilder {
59
- opts;
60
- constructor(options) {
61
- this.opts = {
62
- arch: options.arch || 'x86_64',
63
- profile: options.profile,
64
- output: resolve(options.output),
65
- rufloVersion: options.rufloVersion || VERSION,
66
- models: options.models ?? defaultModelsForProfile(options.profile),
67
- apiKeys: options.apiKeys ?? '',
68
- verbose: options.verbose ?? false,
69
- };
70
- }
71
- async build() {
72
- const t0 = performance.now();
73
- this.log(`Building RVFA appliance (profile=${this.opts.profile}, arch=${this.opts.arch})`);
74
- const outDir = dirname(this.opts.output);
75
- if (!existsSync(outDir))
76
- mkdirSync(outDir, { recursive: true });
77
- const stages = [
78
- { id: 'kernel', raw: this.buildKernelSection(), label: 'Kernel (Alpine rootfs)' },
79
- { id: 'runtime', raw: this.buildRuntimeSection(), label: 'Runtime (Node.js + Claude Code)' },
80
- { id: 'ruflo', raw: this.buildRufloSection(), label: 'Ruflo CLI' },
81
- { id: 'models', raw: this.buildModelsSection(), label: `Models (${this.opts.profile})` },
82
- { id: 'data', raw: this.buildDataSection(), label: 'Data (AgentDB)' },
83
- { id: 'verify', raw: this.buildVerifySection(), label: 'Verify (test suite)' },
84
- ];
85
- const writer = new RvfaWriter(this.buildHeaderPartial());
86
- const summary = [];
87
- for (const s of stages) {
88
- const st = performance.now();
89
- this.log(` Stage: ${s.label}...`);
90
- writer.addSection(s.id, s.raw, { type: s.id });
91
- this.log(` ${fmtBytes(s.raw.length)} raw (${elapsed(st)})`);
92
- summary.push({ id: s.id, size: 0, originalSize: s.raw.length });
93
- }
94
- const binary = writer.build();
95
- // Patch compressed sizes from the built header
96
- try {
97
- const hLen = binary.readUInt32LE(8);
98
- const hdr = JSON.parse(binary.subarray(12, 12 + hLen).toString('utf-8'));
99
- for (const sec of hdr.sections) {
100
- const e = summary.find((x) => x.id === sec.id);
101
- if (e)
102
- e.size = sec.size;
103
- }
104
- }
105
- catch { /* non-fatal */ }
106
- writeFileSync(this.opts.output, binary);
107
- const duration = performance.now() - t0;
108
- this.log('');
109
- this.log('RVFA appliance built successfully.');
110
- this.log(` Output: ${this.opts.output} Size: ${fmtBytes(binary.length)} Duration: ${elapsed(t0)}`);
111
- for (const s of summary) {
112
- const r = s.originalSize > 0 && s.size > 0
113
- ? ` (${((1 - s.size / s.originalSize) * 100).toFixed(1)}% reduction)` : '';
114
- this.log(` ${s.id}: ${fmtBytes(s.originalSize)} -> ${fmtBytes(s.size)}${r}`);
115
- }
116
- return { outputPath: this.opts.output, size: binary.length, sections: summary, duration, profile: this.opts.profile };
117
- }
118
- // ── Section Builders ─────────────────────────────────────
119
- buildKernelSection() {
120
- return jsonBuf({
121
- type: 'kernel', distribution: 'alpine', version: '3.23', arch: this.opts.arch,
122
- packages: ['busybox', 'dumb-init', 'musl'],
123
- init: '/sbin/init -> ruflo-init (PID 1)',
124
- features: ['minimal rootfs', 'read-only root filesystem', 'tmpfs for /tmp and /run', 'seccomp profile applied'],
125
- sizeTarget: '~5MB compressed',
126
- note: 'Manifest-only: actual rootfs fetched during full build pipeline',
127
- });
128
- }
129
- buildRuntimeSection() {
130
- let nodeVersion = 'v22.0.0';
131
- try {
132
- nodeVersion = execSync('node --version', { encoding: 'utf-8', windowsHide: true }).trim();
133
- }
134
- catch { /* keep default */ }
135
- return jsonBuf({
136
- type: 'runtime',
137
- node: { version: nodeVersion, target: 'v22', variant: `linux-${this.opts.arch}-musl`, stripped: true, excludes: ['npm', 'corepack', 'debug-symbols'] },
138
- claudeCode: { name: 'claude-code-cli', entrypoint: '/usr/local/bin/claude' },
139
- paths: { node: '/usr/local/bin/node', claude: '/usr/local/bin/claude' },
140
- });
141
- }
142
- buildRufloSection() {
143
- let packageMeta = null;
144
- try {
145
- const nd = process.platform === 'win32' ? 'NUL' : '/dev/null';
146
- const raw = execSync(`npm pack ruflo@latest --dry-run --json 2>${nd}`, { encoding: 'utf-8', timeout: 15_000, windowsHide: true });
147
- const parsed = JSON.parse(raw);
148
- if (Array.isArray(parsed) && parsed.length > 0)
149
- packageMeta = parsed[0];
150
- }
151
- catch { /* manifest-only fallback */ }
152
- return jsonBuf({
153
- type: 'ruflo', version: this.opts.rufloVersion,
154
- package: packageMeta ?? { name: 'ruflo', version: this.opts.rufloVersion },
155
- commands: RUFLO_COMMANDS, commandCount: RUFLO_COMMANDS.length,
156
- agents: AGENT_TYPES, agentCount: AGENT_TYPES.length,
157
- hooks: { count: HOOK_TYPES.length, types: HOOK_TYPES },
158
- workers: { count: WORKER_TYPES.length, types: WORKER_TYPES },
159
- mcpTools: 215,
160
- });
161
- }
162
- buildModelsSection() {
163
- const p = this.opts.profile;
164
- const resolveModels = (names) => names.map((n) => OFFLINE_MODELS.find((m) => m.name === n) ?? { name: n, format: 'gguf', sizeHint: 'unknown', params: 'unknown' });
165
- if (p === 'cloud') {
166
- const content = { type: 'models', profile: 'cloud', provider: 'api-vault', models: [] };
167
- if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) {
168
- const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase());
169
- content.vault = { format: AES_ALG, kdf: 'scrypt', kdfParams: SCRYPT_OPTS, encrypted: enc.toString('base64') };
170
- this.log(' API keys encrypted into vault');
171
- }
172
- else {
173
- content.vault = null;
174
- content.note = 'No API keys provided; set --api-keys to include vault';
175
- }
176
- return jsonBuf(content);
177
- }
178
- if (p === 'hybrid') {
179
- const content = {
180
- type: 'models', profile: 'hybrid', provider: 'hybrid', engine: 'ruvllm+api-vault',
181
- localModels: resolveModels(this.opts.models),
182
- routing: { tier1: { handler: 'agent-booster', latency: '<1ms' }, tier2: { handler: 'local-model', latency: '~200ms' }, tier3: { handler: 'cloud-api', latency: '2-5s' }, complexityThreshold: 0.3 },
183
- };
184
- if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) {
185
- const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase());
186
- content.vault = { format: AES_ALG, kdf: 'scrypt', encrypted: enc.toString('base64') };
187
- this.log(' API keys encrypted into vault');
188
- }
189
- return jsonBuf(content);
190
- }
191
- // offline
192
- const names = this.opts.models.length > 0 ? this.opts.models : ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4'];
193
- return jsonBuf({
194
- type: 'models', profile: 'offline', provider: 'ruvllm', engine: 'ruvllm',
195
- models: resolveModels(names),
196
- routing: { tier1: { handler: 'agent-booster-wasm', latency: '<1ms' }, tier2: { handler: 'phi-3-mini-q4', latency: '~200ms' }, tier3: { handler: 'qwen2.5-coder-3b-q4', latency: '~2s' }, fallbackToCloud: false },
197
- kvCache: { backend: 'rvf', persistence: true },
198
- note: 'Manifest-only: GGUF weights fetched during full build pipeline',
199
- });
200
- }
201
- buildDataSection() {
202
- // Empty RVF database header (matches rvf-backend.ts RVF\0 magic)
203
- const rvfMagic = Buffer.from([0x52, 0x56, 0x46, 0x00]);
204
- const hdrJson = Buffer.from(JSON.stringify({
205
- magic: 'RVF\0', version: 1, dimensions: 1536, metric: 'cosine',
206
- quantization: 'fp32', entryCount: 0, createdAt: Date.now(), updatedAt: Date.now(),
207
- }), 'utf-8');
208
- const hdrLen = Buffer.alloc(4);
209
- hdrLen.writeUInt32LE(hdrJson.length, 0);
210
- const rvfDb = Buffer.concat([rvfMagic, hdrLen, hdrJson]);
211
- const manifest = jsonBuf({
212
- type: 'data',
213
- components: {
214
- agentDb: { format: 'rvf', magicBytes: 'RVF\\0', databaseSize: rvfDb.length },
215
- hnswIndex: { type: 'hnsw-index', dimensions: 1536, metric: 'cosine', m: 16, efConstruction: 200, maxElements: 100_000, vectorCount: 0 },
216
- sonaPatterns: { type: 'sona-patterns', version: 1, architecture: 'self-optimizing-neural', adaptationTime: '<0.05ms', patterns: [], expertCount: 8, moeConfig: { topK: 2, capacityFactor: 1.25, loadBalancingLoss: 0.01 } },
217
- pluginRegistry: { source: 'ipfs', snapshotted: true, pluginCount: 20 },
218
- },
219
- });
220
- return Buffer.concat([rvfDb, Buffer.from('\n---DATA-MANIFEST---\n'), manifest]);
221
- }
222
- buildVerifySection() {
223
- // Walk up to moflo root for the bundled verify script — see issue #575
224
- // (prior `new URL().pathname` was Windows-broken and the depth was wrong).
225
- const scriptPath = locateMofloRootPath(join('scripts', 'verify-appliance.sh'));
226
- let script;
227
- if (scriptPath && existsSync(scriptPath)) {
228
- script = readFileSync(scriptPath);
229
- this.log(` Bundled verify-appliance.sh (${fmtBytes(script.length)})`);
230
- }
231
- else {
232
- script = Buffer.from([
233
- '#!/bin/sh', 'set -e', 'RUFLO_CMD="${RUFLO_CMD:-ruflo}"',
234
- 'echo "Running basic verification..."',
235
- '$RUFLO_CMD --version && echo " OK: CLI" || echo " FAIL: CLI"',
236
- '$RUFLO_CMD doctor && echo " OK: Doctor" || echo " FAIL: Doctor"',
237
- 'echo "Verification complete."',
238
- ].join('\n'), 'utf-8');
239
- this.log(' Using stub verify script (verify-appliance.sh not found)');
240
- }
241
- const manifest = jsonBuf({
242
- type: 'verify-manifest', categories: 35, criticalChecks: 95,
243
- script: 'verify-appliance.sh',
244
- scriptSha256: createHash('sha256').update(script).digest('hex'),
245
- quickMode: { categories: [1, 2, 3, 4, 5, 25] },
246
- });
247
- return Buffer.concat([script, Buffer.from('\n---VERIFY-MANIFEST---\n'), manifest]);
248
- }
249
- // ── Header ───────────────────────────────────────────────
250
- buildHeaderPartial() {
251
- const providerMap = { cloud: 'api-vault', hybrid: 'hybrid', offline: 'ruvllm' };
252
- const caps = ['cli-26-commands', 'agents-60-plus', 'hooks-17', 'workers-12', 'mcp-215-tools', 'agentdb-rvf', 'hnsw-search', 'sona-patterns', 'security-scanning', 'performance-profiling', 'hive-mind-consensus', 'plugin-registry'];
253
- if (this.opts.profile !== 'cloud')
254
- caps.push('local-inference-ruvllm');
255
- if (this.opts.profile !== 'offline')
256
- caps.push('cloud-api-vault');
257
- const boot = {
258
- entrypoint: '/opt/ruflo/bin/cli.js',
259
- args: ['--profile', this.opts.profile],
260
- env: { NODE_ENV: 'production', CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid', CLAUDE_FLOW_LOG_LEVEL: 'info' },
261
- isolation: this.opts.profile === 'cloud' ? 'container' : 'native',
262
- };
263
- const models = {
264
- provider: providerMap[this.opts.profile],
265
- engine: this.opts.profile === 'cloud' ? undefined : 'ruvllm-0.1.0',
266
- models: this.opts.models.length > 0 ? this.opts.models : undefined,
267
- vaultEncryption: this.opts.profile !== 'offline' ? 'aes-256-gcm' : undefined,
268
- };
269
- return {
270
- magic: 'RVFA', version: 1, name: 'ruflo-appliance', appVersion: this.opts.rufloVersion,
271
- arch: this.opts.arch, platform: 'linux', profile: this.opts.profile,
272
- created: new Date().toISOString(), boot, models, capabilities: caps,
273
- };
274
- }
275
- log(msg) {
276
- if (this.opts.verbose)
277
- console.log(`[RvfaBuilder] ${msg}`);
278
- }
279
- }
280
- // ── Helpers ──────────────────────────────────────────────────
281
- function jsonBuf(obj) {
282
- return Buffer.from(JSON.stringify(obj, null, 2), 'utf-8');
283
- }
284
- function parseEnvFile(content) {
285
- const result = {};
286
- for (const line of content.split(/\r?\n/)) {
287
- const t = line.trim();
288
- if (!t || t.startsWith('#'))
289
- continue;
290
- const eq = t.indexOf('=');
291
- if (eq === -1)
292
- continue;
293
- const k = t.substring(0, eq).trim();
294
- let v = t.substring(eq + 1).trim();
295
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))
296
- v = v.slice(1, -1);
297
- if (k)
298
- result[k] = v;
299
- }
300
- return result;
301
- }
302
- function defaultModelsForProfile(profile) {
303
- if (profile === 'offline')
304
- return ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4'];
305
- if (profile === 'hybrid')
306
- return ['phi-3-mini-q4'];
307
- return [];
308
- }
309
- function generateBuildPassphrase() {
310
- return randomBytes(32).toString('hex');
311
- }
312
- function fmtBytes(b) {
313
- if (b < 1024)
314
- return `${b}B`;
315
- if (b < 1048576)
316
- return `${(b / 1024).toFixed(1)}KB`;
317
- if (b < 1073741824)
318
- return `${(b / 1048576).toFixed(1)}MB`;
319
- return `${(b / 1073741824).toFixed(2)}GB`;
320
- }
321
- function elapsed(t) {
322
- const ms = performance.now() - t;
323
- return ms < 1000 ? `${ms.toFixed(0)}ms` : `${(ms / 1000).toFixed(2)}s`;
324
- }
325
- //# sourceMappingURL=rvfa-builder.js.map