@ijfw/memory-server 1.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/bin/ijfw +27 -0
- package/bin/ijfw-dashboard +180 -0
- package/bin/ijfw-dispatch-plan +41 -0
- package/bin/ijfw-memorize +273 -0
- package/bin/ijfw-memory +51 -0
- package/fixtures/demo-target.js +28 -0
- package/package.json +53 -0
- package/src/api-client.js +190 -0
- package/src/audit-roster.js +315 -0
- package/src/caps.js +37 -0
- package/src/cold-scan-runner.mjs +37 -0
- package/src/compute/edges.js +155 -0
- package/src/compute/extract.js +560 -0
- package/src/compute/fts5.js +420 -0
- package/src/compute/graph-auto-index.js +191 -0
- package/src/compute/graph-lock.js +114 -0
- package/src/compute/index.js +18 -0
- package/src/compute/migration-runner.js +116 -0
- package/src/compute/migrations/001-initial.js +23 -0
- package/src/compute/migrations/002-porter-stemming-source.js +139 -0
- package/src/compute/migrations/003-tier-semantic.js +69 -0
- package/src/compute/migrations/004-kg-tables.js +83 -0
- package/src/compute/migrations/005-stale-candidate.js +72 -0
- package/src/compute/python-resolver.js +106 -0
- package/src/compute/runner-vm.js +185 -0
- package/src/compute/runner.js +416 -0
- package/src/compute/sandbox-detect.js +122 -0
- package/src/compute/sandbox-linux.js +164 -0
- package/src/compute/sandbox-macos.js +167 -0
- package/src/compute/sandbox-windows.js +63 -0
- package/src/compute/schema.sql +118 -0
- package/src/compute/staleness.js +239 -0
- package/src/compute/synonyms.js +367 -0
- package/src/compute/traverse.js +180 -0
- package/src/cost/aggregator.js +229 -0
- package/src/cost/pricing.js +134 -0
- package/src/cost/readers/claude.js +179 -0
- package/src/cost/readers/codex.js +131 -0
- package/src/cost/readers/gemini.js +111 -0
- package/src/cost/savings.js +243 -0
- package/src/cross-dispatcher.js +437 -0
- package/src/cross-orchestrator-cli.js +1885 -0
- package/src/cross-orchestrator.js +598 -0
- package/src/cross-project-search.js +114 -0
- package/src/dashboard-client.html +1180 -0
- package/src/dashboard-server.js +895 -0
- package/src/design-companion.js +81 -0
- package/src/dispatch/colon-syntax.js +732 -0
- package/src/dispatch-planner.js +235 -0
- package/src/dream/cooldown.js +105 -0
- package/src/dream/runner.mjs +373 -0
- package/src/dream/staleness-wiring.js +195 -0
- package/src/feedback-detector.js +57 -0
- package/src/hero-line.js +115 -0
- package/src/importers/claude-mem.js +152 -0
- package/src/importers/cli.js +311 -0
- package/src/importers/common.js +84 -0
- package/src/importers/discover.js +235 -0
- package/src/importers/rtk.js +107 -0
- package/src/intent-router.js +221 -0
- package/src/lib/atomic-io.js +201 -0
- package/src/lib/cache.js +33 -0
- package/src/lib/npm-view.js +104 -0
- package/src/lib/status-card.js +95 -0
- package/src/lib/token.js +85 -0
- package/src/memory/fts5.js +349 -0
- package/src/memory/migration-runner.js +116 -0
- package/src/memory/migrations/001-fts5-init.js +26 -0
- package/src/memory/migrations/002-tier-semantic.js +60 -0
- package/src/memory/migrations/003-stale-candidate.js +60 -0
- package/src/memory/reader.js +300 -0
- package/src/memory/recall-counter.js +76 -0
- package/src/memory/schema.sql +79 -0
- package/src/memory/search.js +431 -0
- package/src/memory/staleness.js +237 -0
- package/src/memory/tier-promotion.js +377 -0
- package/src/memory/tokenize.js +63 -0
- package/src/project-type-detector.js +866 -0
- package/src/prompt-check.js +171 -0
- package/src/ralph-allowlist.js +88 -0
- package/src/receipts.js +129 -0
- package/src/redactor.js +107 -0
- package/src/sandbox.js +275 -0
- package/src/sanitizer.js +69 -0
- package/src/scan-resume.js +167 -0
- package/src/schema.js +82 -0
- package/src/search-bm25.js +108 -0
- package/src/server.js +1414 -0
- package/src/swarm-config.js +80 -0
- package/src/trident/dispatch.js +211 -0
- package/src/trident/lens-health.js +253 -0
- package/src/update-apply.js +79 -0
- package/src/update-check.js +136 -0
- package/src/vectors.js +178 -0
- package/templates/design/bento-grid.md +84 -0
- package/templates/design/brutalist-luxe.md +82 -0
- package/templates/design/cinematic-dark.md +82 -0
- package/templates/design/data-dense-dashboard.md +88 -0
- package/templates/design/editorial-warm.md +81 -0
- package/templates/design/glassmorphic.md +84 -0
- package/templates/design/magazine-editorial.md +84 -0
- package/templates/design/maximalist-vibrant.md +85 -0
- package/templates/design/neo-swiss-tech.md +85 -0
- package/templates/design/swiss-minimal.md +80 -0
- package/templates/design/terminal-native.md +83 -0
- package/templates/design/warm-organic.md +84 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* python-resolver.js -- Resolve a Python interpreter for the compute runner.
|
|
3
|
+
*
|
|
4
|
+
* Behavior (per V3-F14 routing):
|
|
5
|
+
* - When IJFW_HOST=wayland, the bundled python-build-standalone interpreter
|
|
6
|
+
* under ${WAYLAND_HOME or profileHome}/runtime/python/bin/python3 is used.
|
|
7
|
+
* If missing, we fail loud (PythonNotFoundError) -- never silently fall
|
|
8
|
+
* back to system Python in a Wayland host.
|
|
9
|
+
* - Otherwise, probe for python3 then python on PATH. Fail loud if neither
|
|
10
|
+
* is found.
|
|
11
|
+
*
|
|
12
|
+
* Errors carry `code` and `interpreter` for the caller to surface to users.
|
|
13
|
+
*
|
|
14
|
+
* Zero external deps.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execFileSync } from 'child_process';
|
|
18
|
+
import { existsSync, statSync } from 'fs';
|
|
19
|
+
import { join, isAbsolute } from 'path';
|
|
20
|
+
import { homedir } from 'os';
|
|
21
|
+
|
|
22
|
+
export class PythonNotFoundError extends Error {
|
|
23
|
+
constructor(message, { code = 'PYTHON_NOT_FOUND', interpreter = null } = {}) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'PythonNotFoundError';
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.interpreter = interpreter;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isExecutableFile(p) {
|
|
32
|
+
try {
|
|
33
|
+
if (!existsSync(p)) return false;
|
|
34
|
+
const st = statSync(p);
|
|
35
|
+
return st.isFile();
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Locate a binary on PATH using execFile (no shell).
|
|
42
|
+
function whichPython(name) {
|
|
43
|
+
try {
|
|
44
|
+
if (process.platform === 'win32') {
|
|
45
|
+
const out = execFileSync('where', [name], {
|
|
46
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
47
|
+
windowsHide: true,
|
|
48
|
+
}).toString().trim();
|
|
49
|
+
const first = out ? out.split(/\r?\n/)[0] : null;
|
|
50
|
+
return first && isExecutableFile(first) ? first : null;
|
|
51
|
+
}
|
|
52
|
+
const out = execFileSync('/bin/sh', ['-c', 'command -v "$1" 2>/dev/null || true', '--', name], {
|
|
53
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
54
|
+
}).toString().trim();
|
|
55
|
+
return out && isExecutableFile(out) ? out : null;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* resolvePython(projectRoot) -> { interpreter, source }
|
|
63
|
+
* - source: 'wayland-bundled' | 'system'
|
|
64
|
+
*
|
|
65
|
+
* Throws PythonNotFoundError on miss. Never silently falls back across
|
|
66
|
+
* the wayland-bundled / system boundary -- that boundary is a deliberate
|
|
67
|
+
* security signal (bundled = signed + reproducible; system = trust-on-first-use).
|
|
68
|
+
*/
|
|
69
|
+
export function resolvePython(/* projectRoot */) {
|
|
70
|
+
const host = process.env.IJFW_HOST;
|
|
71
|
+
|
|
72
|
+
if (host === 'wayland') {
|
|
73
|
+
const profile = process.env.WAYLAND_HOME || join(homedir(), '.wayland');
|
|
74
|
+
if (!isAbsolute(profile)) {
|
|
75
|
+
throw new PythonNotFoundError(
|
|
76
|
+
`[ijfw compute] WAYLAND_HOME must be absolute when IJFW_HOST=wayland (got "${profile}").`,
|
|
77
|
+
{ code: 'PYTHON_HOST_PATH_INVALID' }
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const candidate = process.platform === 'win32'
|
|
81
|
+
? join(profile, 'runtime', 'python', 'python.exe')
|
|
82
|
+
: join(profile, 'runtime', 'python', 'bin', 'python3');
|
|
83
|
+
|
|
84
|
+
if (isExecutableFile(candidate)) {
|
|
85
|
+
return { interpreter: candidate, source: 'wayland-bundled' };
|
|
86
|
+
}
|
|
87
|
+
throw new PythonNotFoundError(
|
|
88
|
+
`[ijfw compute] Bundled Python not found at ${candidate}. ` +
|
|
89
|
+
`Install Wayland's python-build-standalone runtime or unset IJFW_HOST=wayland.`,
|
|
90
|
+
{ code: 'PYTHON_BUNDLED_MISSING', interpreter: candidate }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Non-Wayland host: probe system PATH. Fail loud on miss.
|
|
95
|
+
const py3 = whichPython('python3');
|
|
96
|
+
if (py3) return { interpreter: py3, source: 'system' };
|
|
97
|
+
const py = whichPython('python');
|
|
98
|
+
if (py) return { interpreter: py, source: 'system' };
|
|
99
|
+
|
|
100
|
+
throw new PythonNotFoundError(
|
|
101
|
+
'[ijfw compute] No Python interpreter found on PATH. ' +
|
|
102
|
+
'Install Python 3 (python3 or python) or run under a Wayland host with ' +
|
|
103
|
+
'IJFW_HOST=wayland and the bundled runtime.',
|
|
104
|
+
{ code: 'PYTHON_NOT_ON_PATH' }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runner-vm.js -- vm.Script JS-only path for the compute runner.
|
|
3
|
+
*
|
|
4
|
+
* Enabled via vmOnly=true (or IJFW_COMPUTE_VM_ONLY=1 at runner level).
|
|
5
|
+
* Per V3-F6 enumerated bans:
|
|
6
|
+
* - codeGeneration.strings = false -> disables string-to-code (the dynamic
|
|
7
|
+
* code-generation primitive) plus the
|
|
8
|
+
* Function constructor
|
|
9
|
+
* - codeGeneration.wasm = false -> disables WebAssembly.instantiate*
|
|
10
|
+
* - context prototype frozen -> defends against prototype pollution
|
|
11
|
+
* - dynamic import() not exposed -> not configured -> throws at parse
|
|
12
|
+
* - Atomics / SharedArrayBuffer / WebAssembly -> seeded as undefined at
|
|
13
|
+
* vm.createContext() so the keys exist on globalThis but evaluate to
|
|
14
|
+
* undefined. This is a best-effort drop; V8 may re-expose intrinsics in
|
|
15
|
+
* some builds, in which case the OS-level sandbox is the actual security
|
|
16
|
+
* boundary. See "Honest framing" below.
|
|
17
|
+
* - setTimeout / setImmediate / setInterval -> not exposed; vm.Script has
|
|
18
|
+
* no separate event loop, so async-timer-escape is impossible
|
|
19
|
+
* - vm.Script's native `timeout` option enforces hard wall-clock kill
|
|
20
|
+
*
|
|
21
|
+
* The script gets `console.log` / `console.error` only. stdout is captured
|
|
22
|
+
* and returned. Errors are surfaced as throw -- never swallowed.
|
|
23
|
+
*
|
|
24
|
+
* Honest framing: vm.Script is a Node-internal isolation primitive, NOT a
|
|
25
|
+
* security boundary by Node's own docs. We pair it with the OS-level sandbox
|
|
26
|
+
* applied to the parent process for defense in depth. This file is the
|
|
27
|
+
* deterministic, JS-only fast-path; if you need true isolation, prefer the
|
|
28
|
+
* subprocess sandbox in runner.js.
|
|
29
|
+
*
|
|
30
|
+
* V3-F6 honesty downgrade (1.3.0-alpha.1):
|
|
31
|
+
* Function/eval/import/prototype: blocked.
|
|
32
|
+
* Atomics/SAB/WebAssembly: best-effort dropped at context-creation. If a
|
|
33
|
+
* future Node/V8 release re-exposes them via host realm intrinsics, the
|
|
34
|
+
* subprocess+OS sandbox path remains the enforced security boundary.
|
|
35
|
+
*
|
|
36
|
+
* Zero external deps.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import vm from 'vm';
|
|
40
|
+
|
|
41
|
+
export class VmOnlyJsError extends Error {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = 'VmOnlyJsError';
|
|
45
|
+
this.code = 'VM_ONLY_JS_ONLY';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class VmTimeoutError extends Error {
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = 'VmTimeoutError';
|
|
53
|
+
this.code = 'VM_TIMEOUT';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const HARD_CAP_MS = 300_000;
|
|
58
|
+
const DEFAULT_MS = 30_000;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* runVm({ script, timeoutMs, projectRoot })
|
|
62
|
+
* -> { stdout, durationMs, timedOut: false }
|
|
63
|
+
*
|
|
64
|
+
* Throws on script error (with original error attached) or VmTimeoutError on
|
|
65
|
+
* wall-clock timeout.
|
|
66
|
+
*/
|
|
67
|
+
export function runVm({ script, timeoutMs, projectRoot } = {}) {
|
|
68
|
+
if (typeof script !== 'string' || script.length === 0) {
|
|
69
|
+
throw new TypeError('runVm: `script` must be a non-empty string');
|
|
70
|
+
}
|
|
71
|
+
// Deliberately ignore projectRoot here -- vm.Script has no FS access by
|
|
72
|
+
// design. The parameter exists for parity with the subprocess runner so
|
|
73
|
+
// the caller doesn't need a separate code path.
|
|
74
|
+
void projectRoot;
|
|
75
|
+
|
|
76
|
+
const timeout = clampTimeout(timeoutMs);
|
|
77
|
+
const start = Date.now();
|
|
78
|
+
|
|
79
|
+
// Capture buffers -- console.log writes here; we never expose process.stdout.
|
|
80
|
+
const stdoutChunks = [];
|
|
81
|
+
const stderrChunks = [];
|
|
82
|
+
|
|
83
|
+
const safeConsole = Object.freeze({
|
|
84
|
+
log: (...a) => stdoutChunks.push(stringify(a)),
|
|
85
|
+
error: (...a) => stderrChunks.push(stringify(a)),
|
|
86
|
+
warn: (...a) => stderrChunks.push(stringify(a)),
|
|
87
|
+
info: (...a) => stdoutChunks.push(stringify(a)),
|
|
88
|
+
debug: () => {},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Build a context with NO ambient capabilities. Note: `vm.createContext`
|
|
92
|
+
// assigns the passed object to be the context's globalThis -- we only put
|
|
93
|
+
// `console` on it. No setTimeout, no fetch, no process, no require, no
|
|
94
|
+
// import.meta. We seed Atomics / SharedArrayBuffer / WebAssembly as
|
|
95
|
+
// explicit `undefined` keys at context creation so they're not exposed
|
|
96
|
+
// from the host realm intrinsics. (Per V3-F6 honesty note: this is
|
|
97
|
+
// best-effort. V8 may re-expose them in some builds.)
|
|
98
|
+
const sandbox = {
|
|
99
|
+
console: safeConsole,
|
|
100
|
+
Atomics: undefined,
|
|
101
|
+
SharedArrayBuffer: undefined,
|
|
102
|
+
WebAssembly: undefined,
|
|
103
|
+
};
|
|
104
|
+
const context = vm.createContext(sandbox, {
|
|
105
|
+
name: 'ijfw-compute-vm',
|
|
106
|
+
codeGeneration: { strings: false, wasm: false },
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// V3-F6: freeze the prototype chain visible inside the context. Atomics /
|
|
110
|
+
// SAB / WebAssembly are dropped at context-creation above (not via post-hoc
|
|
111
|
+
// delete), so the freeze prelude only handles prototype pollution defence.
|
|
112
|
+
const freezePrelude = `
|
|
113
|
+
Object.freeze(Object.prototype);
|
|
114
|
+
Object.freeze(Array.prototype);
|
|
115
|
+
Object.freeze(Function.prototype);
|
|
116
|
+
Object.freeze(String.prototype);
|
|
117
|
+
Object.freeze(Number.prototype);
|
|
118
|
+
Object.freeze(Boolean.prototype);
|
|
119
|
+
Object.freeze(Object);
|
|
120
|
+
Object.freeze(Array);
|
|
121
|
+
`;
|
|
122
|
+
try {
|
|
123
|
+
vm.runInContext(freezePrelude, context, { timeout: preludeTimeout() });
|
|
124
|
+
} catch (e) {
|
|
125
|
+
// Freezing fails if the host is exotic; surface honestly rather than
|
|
126
|
+
// continuing under a false-pretence frozen context.
|
|
127
|
+
throw new Error(
|
|
128
|
+
`[ijfw compute] vm prelude (prototype freeze) failed: ${e && e.message}. ` +
|
|
129
|
+
'Refusing to run untrusted code in an unfrozen context.'
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let result;
|
|
134
|
+
try {
|
|
135
|
+
const compiled = new vm.Script(script, { filename: 'ijfw-compute.js' });
|
|
136
|
+
result = compiled.runInContext(context, { timeout, displayErrors: true });
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// vm's timeout surfaces as an Error with code 'ERR_SCRIPT_EXECUTION_TIMEOUT'
|
|
139
|
+
// on modern Node. Match by code when available, fall back to message.
|
|
140
|
+
if (e && (e.code === 'ERR_SCRIPT_EXECUTION_TIMEOUT' || /Script execution timed out/.test(String(e.message)))) {
|
|
141
|
+
throw new VmTimeoutError(
|
|
142
|
+
`[ijfw compute] vm.Script timed out after ${timeout}ms.`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
throw e;
|
|
146
|
+
} finally {
|
|
147
|
+
void result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const stdout = stdoutChunks.join('\n');
|
|
151
|
+
const stderr = stderrChunks.join('\n');
|
|
152
|
+
return {
|
|
153
|
+
stdout,
|
|
154
|
+
stderr,
|
|
155
|
+
durationMs: Date.now() - start,
|
|
156
|
+
timedOut: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function clampTimeout(t) {
|
|
161
|
+
const fromEnv = Number(process.env.IJFW_COMPUTE_TIMEOUT_MS || 0);
|
|
162
|
+
let ms = Number(t || 0) || fromEnv || DEFAULT_MS;
|
|
163
|
+
if (!Number.isFinite(ms) || ms <= 0) ms = DEFAULT_MS;
|
|
164
|
+
if (ms > HARD_CAP_MS) ms = HARD_CAP_MS;
|
|
165
|
+
return Math.floor(ms);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// L4: configurable freeze-prelude timeout. Default 5000ms (was 1000ms);
|
|
169
|
+
// override via IJFW_COMPUTE_VM_PRELUDE_TIMEOUT_MS for high-load hosts.
|
|
170
|
+
const PRELUDE_DEFAULT_MS = 5_000;
|
|
171
|
+
function preludeTimeout() {
|
|
172
|
+
const v = Number(process.env.IJFW_COMPUTE_VM_PRELUDE_TIMEOUT_MS || 0);
|
|
173
|
+
if (!Number.isFinite(v) || v <= 0) return PRELUDE_DEFAULT_MS;
|
|
174
|
+
return Math.min(Math.floor(v), HARD_CAP_MS);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Stringify args in a console.log-like way without inheriting host globals.
|
|
178
|
+
function stringify(args) {
|
|
179
|
+
return args
|
|
180
|
+
.map((a) => {
|
|
181
|
+
if (typeof a === 'string') return a;
|
|
182
|
+
try { return JSON.stringify(a); } catch { return String(a); }
|
|
183
|
+
})
|
|
184
|
+
.join(' ');
|
|
185
|
+
}
|