open-memex 0.7.0 → 0.7.1-alpha.1
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/AGENTS.md +2 -0
- package/CHANGELOG.md +9 -0
- package/README.md +16 -2
- package/README.zh-CN.md +5 -2
- package/dist/.build-stamp +1 -1
- package/dist/doctor.js +65 -7
- package/dist/runtime-floor.js +47 -0
- package/dist/store/db.js +8 -0
- package/docs/V2-DESIGN.md +31 -1
- package/package.json +2 -2
- package/scripts/smoke-pure.ts +31 -0
- package/src/doctor.ts +72 -7
- package/src/runtime-floor.ts +56 -0
- package/src/store/db.ts +12 -0
package/AGENTS.md
CHANGED
|
@@ -16,6 +16,8 @@ via `prepublishOnly` — Node refuses `--experimental-strip-types` for files und
|
|
|
16
16
|
|
|
17
17
|
`src/store/db.ts` picks the backend at runtime by sniffing `globalThis.Bun`. Both backends share the same surface (`new Database(path)`, `.exec`, `.prepare().run/all/get`, `.close`). Any DB code you write must stay on that common subset — do not import `better-sqlite3` or `bun:sqlite` directly outside `db.ts`. The Node driver is pinned to the `better-sqlite3` 13 N-API line (D72): the pre-13 `node::ObjectWrap` build aborts intermittently on Node 24.19+ when a prepared statement is finalized by GC.
|
|
18
18
|
|
|
19
|
+
D74 — the Node floor is **22.14**, not 22.6. `better-sqlite3` 13 is compiled against Node-API 10, which Node gained in 22.14.0; below that floor `require()` succeeds and `new Database()` segfaults the process with no diagnostic (measured: Node 22.12.0 dies with `0xC0000005` on win32-x64, Node 22.23.3 / 24.20.0 are fine; upstream WiseLibs/better-sqlite3#1514). The gate lives in `src/runtime-floor.ts` (pure, unit-tested) and is enforced in `loadDatabase` (`src/store/db.ts`) so every entry point fails with an actionable message instead of a silent crash; `doctor` reports the floor plus a live driver probe run in a child process (a segfault cannot be caught in-process, so the probe must be spawned, and it must resolve the driver from this package's directory, not the caller's cwd). `engines.node` is `>=22.14.0`. Never lower it back to the `--experimental-strip-types` floor of 22.6 — that number is a different constraint and predates the driver bump.
|
|
20
|
+
|
|
19
21
|
Tool logic is host-agnostic and lives in `src/tools/ops.ts` (plain functions + shared zod arg shapes + `TOOL_DESCRIPTIONS`). `src/tools/memory.ts` (opencode) and `src/mcp.ts` are thin adapters — when adding or changing a tool, change `ops.ts` once and both hosts pick it up.
|
|
20
22
|
|
|
21
23
|
**Plugin code must not runtime-import a host SDK package** (D58, extended by D60): `@opencode-ai/plugin` (v1) and `@opencode/plugin` (v2) are devDependencies, and neither host resolves them for plugins loaded from a global npm install — the import fails and the host silently skips the plugin (no error, no tools). Type-only imports are fine. `src/tools/memory.ts` carries a local stand-in for the v1 SDK's `tool()` (a runtime identity), and `src/opencode-v2.ts` duck-types the v2 context, for exactly this reason. The plugin entry is a dual export (`{ id, setup, server }`, D60): v1 ≥1.18.29 calls `server`, opencode 2 calls `setup`; both adapt `src/plugin-core.ts`.
|
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,15 @@ the `@alpha` tag are development snapshots and are not listed individually.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## The Node floor is 22.14, because the driver says so (D74)
|
|
13
|
+
|
|
14
|
+
- **Fixed: every command could die with no output on Node older than 22.14.** `better-sqlite3` 13 (the D72 N-API line) is compiled against Node-API 10, which Node only gained in **22.14.0**. Below that, `require()` succeeds and the first `new Database()` segfaults the process - no message, no stack, exit 139 on macOS/Linux and `0xC0000005` on Windows. Reproduced on win32-x64 with Node 22.12.0; Node 22.23.3 and 24.20.0 are fine, and Node 20 can never work with v13 (upstream [WiseLibs/better-sqlite3#1514](https://github.com/WiseLibs/better-sqlite3/issues/1514), open). open-memex now refuses to load the driver below the floor and says exactly what to do (upgrade Node, or pin `better-sqlite3@^12.11.1`) instead of dying.
|
|
15
|
+
- **`engines.node` corrected to `>=22.14.0`.** It claimed `>=22.6`, which was the `--experimental-strip-types` floor - a different constraint that predates the driver bump, and wrong for the driver.
|
|
16
|
+
- **`open-memex doctor` gained a `sqlite driver` check** that loads the driver and opens an in-memory database in a **child process** (a segfault cannot be caught in-process) and reports the exit code or signal. It resolves the driver from this package's own directory, so it works when `doctor` runs with cwd set to a user project.
|
|
17
|
+
- Both READMEs gained a troubleshooting entry for the crash signature (`exit 139` / `0xC0000005` / "Segmentation fault"). No protocol, schema, or data change.
|
|
18
|
+
|
|
19
|
+
## [0.7.0] - 2026-10-04
|
|
20
|
+
|
|
12
21
|
## One thing you said is one memory (D73)
|
|
13
22
|
|
|
14
23
|
Saying "remember ." used to save the same statement up to three times: the keyword hook stored your sentence verbatim with no agent in the loop, then the agent - which could see the chat but not the store - saved its own paraphrase beside it, and `memory_add`'s near-duplicate check scored the two too far apart (0.21-0.38 against a 0.80 bar) to notice. `open-memex audit` reported zero near-duplicates while three copies sat in the folder, and search returned all of them.
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ depth once you're up and running.
|
|
|
53
53
|
|
|
54
54
|
## Quick start
|
|
55
55
|
|
|
56
|
-
You need **Node.js ≥ 22.
|
|
56
|
+
You need **Node.js ≥ 22.14** (check with `node -v`). Then:
|
|
57
57
|
|
|
58
58
|
```sh
|
|
59
59
|
npm install -g open-memex
|
|
@@ -163,7 +163,9 @@ no one remembers to write down.
|
|
|
163
163
|
|
|
164
164
|
### Requirements
|
|
165
165
|
|
|
166
|
-
- **Node.js ≥ 22.
|
|
166
|
+
- **Node.js ≥ 22.14** (`open-memex doctor` verifies this for you). The floor is
|
|
167
|
+
the SQLite driver's: `better-sqlite3` 13 is built against Node-API 10, which
|
|
168
|
+
Node gained in 22.14.0 — older Node segfaults on the first database open.
|
|
167
169
|
|
|
168
170
|
### Install the CLI
|
|
169
171
|
|
|
@@ -805,6 +807,18 @@ walks through the mental model end to end.
|
|
|
805
807
|
|
|
806
808
|
## Troubleshooting
|
|
807
809
|
|
|
810
|
+
**Every command dies with no output, or the process crashes (`exit 139`,
|
|
811
|
+
`0xC0000005`, "Segmentation fault").**
|
|
812
|
+
Your Node is older than **22.14** and the bundled SQLite driver cannot load on
|
|
813
|
+
it. `better-sqlite3` 13 is compiled against Node-API 10, which Node only gained
|
|
814
|
+
in 22.14.0; on anything older `require()` succeeds and then the first database
|
|
815
|
+
open segfaults the process with no diagnostic. open-memex now refuses to load
|
|
816
|
+
the driver and says so, but anything already crashing was almost certainly this.
|
|
817
|
+
Check with `node -v`, then either upgrade Node (`nvm install 22.14 && nvm use
|
|
818
|
+
22.14`, or any current 22.x/24.x) or pin the driver down with `npm install
|
|
819
|
+
better-sqlite3@^12.11.1`. `open-memex doctor` reports both the Node floor and a
|
|
820
|
+
live driver probe. Known upstream: WiseLibs/better-sqlite3#1514.
|
|
821
|
+
|
|
808
822
|
**`open-memex` is not recognized / command not found.**
|
|
809
823
|
A global `npm install -g` puts the `open-memex` launcher in npm's global bin
|
|
810
824
|
folder. If your terminal can't find it, that folder isn't on your `PATH`:
|
package/README.zh-CN.md
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
## 快速开始
|
|
44
44
|
|
|
45
|
-
需要 **Node.js ≥ 22.
|
|
45
|
+
需要 **Node.js ≥ 22.14**(用 `node -v` 检查)。然后:
|
|
46
46
|
|
|
47
47
|
```sh
|
|
48
48
|
npm install -g open-memex
|
|
@@ -118,7 +118,7 @@ open-memex 用两种方式接编辑器:原生的 **opencode 插件**,和标
|
|
|
118
118
|
|
|
119
119
|
### 环境要求
|
|
120
120
|
|
|
121
|
-
- **Node.js ≥ 22.
|
|
121
|
+
- **Node.js ≥ 22.14**(`open-memex doctor` 会替你核查)。这个下限来自 SQLite 驱动:`better-sqlite3` 13 是按 Node-API 10 编译的,而 Node 直到 22.14.0 才支持它——更老的 Node 在第一次打开数据库时会直接段错误崩溃。
|
|
122
122
|
|
|
123
123
|
### 安装 CLI
|
|
124
124
|
|
|
@@ -561,6 +561,9 @@ npx -y open-memex mcp # 无需安装
|
|
|
561
561
|
|
|
562
562
|
## 故障排查
|
|
563
563
|
|
|
564
|
+
**每条命令都毫无输出地死掉,或进程直接崩溃(`exit 139`、`0xC0000005`、"Segmentation fault")。**
|
|
565
|
+
你的 Node 比 **22.14** 老,内置的 SQLite 驱动在它上面加载不了。`better-sqlite3` 13 是按 Node-API 10 编译的,而 Node 直到 22.14.0 才支持;在更老的版本上 `require()` 会成功,然后第一次打开数据库就把进程段错误掉,没有任何诊断信息。open-memex 现在会拒绝加载驱动并明确告诉你,但如果之前就是毫无输出一路崩溃,基本可以确定是这个原因。用 `node -v` 确认后,二选一:升级 Node(`nvm install 22.14 && nvm use 22.14`,或任意当前的 22.x/24.x),或者把驱动降回去:`npm install better-sqlite3@^12.11.1`。`open-memex doctor` 会同时报告 Node 下限和一次真实的驱动探测。上游已知问题:WiseLibs/better-sqlite3#1514。
|
|
566
|
+
|
|
564
567
|
**`open-memex` 无法识别 / 命令找不到。**
|
|
565
568
|
全局 `npm install -g` 把 `open-memex` 启动器放在 npm 的全局 bin 文件夹里。终端找不到命令,就是这个文件夹不在 PATH 上:
|
|
566
569
|
|
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e9cc36b1db649d899fdd7298be01622709e4f6d8d4e583764c294bd009cfd0e6
|
package/dist/doctor.js
CHANGED
|
@@ -10,6 +10,7 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { loadConfig, configSource, DEFAULT_CONFIG, stripJsonComments } from "./config.js";
|
|
11
11
|
import { paths, homeOverride } from "./paths.js";
|
|
12
12
|
import { resolveCwdScope } from "./scope.js";
|
|
13
|
+
import { MIN_NODE_VERSION, nativeDriverFloorMessage, nodeSupportsNativeDriver, } from "./runtime-floor.js";
|
|
13
14
|
import { db, backendName } from "./store/db.js";
|
|
14
15
|
import { LEGACY_PACKAGE_NAME, opencodeGlobalConfigPath, userMcpConfigPath } from "./init.js";
|
|
15
16
|
// D64: the MCP server exposes eleven tools; checking only the five the
|
|
@@ -29,12 +30,69 @@ const EXPECTED_TOOLS = [
|
|
|
29
30
|
];
|
|
30
31
|
function nodeCheck() {
|
|
31
32
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
33
|
+
// Two independent floors, and the higher one wins: 22.6 for
|
|
34
|
+
// --experimental-strip-types, 22.14 for the native SQLite driver (D74).
|
|
35
|
+
const stripTypesOk = major > 22 || (major === 22 && minor >= 6);
|
|
36
|
+
const driverOk = nodeSupportsNativeDriver(process.versions.node);
|
|
37
|
+
const ok = stripTypesOk && driverOk;
|
|
38
|
+
let detail = `v${process.versions.node}`;
|
|
39
|
+
if (!stripTypesOk) {
|
|
40
|
+
detail += ` (need >= 22.6 for --experimental-strip-types)`;
|
|
41
|
+
}
|
|
42
|
+
else if (!driverOk) {
|
|
43
|
+
detail += ` (need >= ${MIN_NODE_VERSION}: better-sqlite3 13 is built against Node-API 10 and segfaults below it)`;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
detail += " (>= 22.14: ok for type stripping and the SQLite driver)";
|
|
47
|
+
}
|
|
48
|
+
return { name: "node", ok, detail };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* D74: the native driver used to fail by segfaulting — invisible to any
|
|
52
|
+
* in-process try/catch. Now that `loadDatabase` refuses below the floor, the
|
|
53
|
+
* load is safe to attempt here, so a broken driver reports itself here instead
|
|
54
|
+
* of killing the first command the user runs.
|
|
55
|
+
*/
|
|
56
|
+
function driverCheck() {
|
|
57
|
+
if (typeof globalThis.Bun !== "undefined") {
|
|
58
|
+
return { name: "sqlite driver", ok: true, detail: "bun:sqlite (built-in, no native module)" };
|
|
59
|
+
}
|
|
60
|
+
// Resolve the driver from THIS package's directory, not from the caller's
|
|
61
|
+
// cwd: `doctor` routinely runs with cwd set to a user project, where a bare
|
|
62
|
+
// `require("better-sqlite3")` would not find our node_modules at all.
|
|
63
|
+
const pkgRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
64
|
+
try {
|
|
65
|
+
const probe = execFileSync(process.execPath, [
|
|
66
|
+
"-e",
|
|
67
|
+
"const p=process.argv[1];const D=require(require.resolve('better-sqlite3',{paths:[p]}));const db=new D(':memory:');db.exec('create table t(a)');db.close();process.stdout.write('ok')",
|
|
68
|
+
pkgRoot,
|
|
69
|
+
], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 30_000 });
|
|
70
|
+
return {
|
|
71
|
+
name: "sqlite driver",
|
|
72
|
+
ok: probe.includes("ok"),
|
|
73
|
+
detail: probe.includes("ok")
|
|
74
|
+
? "better-sqlite3 loaded and opened an in-memory database"
|
|
75
|
+
: `probe returned ${JSON.stringify(probe)}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const e = err;
|
|
80
|
+
// A native crash arrives as a huge unsigned status (0xC0000005 on Windows,
|
|
81
|
+
// 139 via SIGSEGV elsewhere). Print both forms - the hex is the one people
|
|
82
|
+
// recognize from Event Viewer or a shell's `exit=139`.
|
|
83
|
+
const how = e.signal
|
|
84
|
+
? `signal ${e.signal}`
|
|
85
|
+
: typeof e.status === "number" && e.status > 255
|
|
86
|
+
? `exit code ${e.status} (0x${(e.status >>> 0).toString(16).toUpperCase()})`
|
|
87
|
+
: `exit code ${e.status}`;
|
|
88
|
+
return {
|
|
89
|
+
name: "sqlite driver",
|
|
90
|
+
ok: false,
|
|
91
|
+
detail: `better-sqlite3 could not open a database (${how}). ` +
|
|
92
|
+
(e.stderr ? String(e.stderr).trim().split("\n").slice(-2).join(" ") + " " : "") +
|
|
93
|
+
nativeDriverFloorMessage(),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
38
96
|
}
|
|
39
97
|
function configCheck() {
|
|
40
98
|
try {
|
|
@@ -514,7 +572,7 @@ function vscodeMcpCheck() {
|
|
|
514
572
|
}
|
|
515
573
|
export async function runDoctor() {
|
|
516
574
|
console.log("open-memex doctor");
|
|
517
|
-
const checks = [nodeCheck(), configCheck(), scopeCheck(), storageCheck(), lockingCheck(), opencodePluginCheck(), vscodeMcpCheck()];
|
|
575
|
+
const checks = [nodeCheck(), configCheck(), driverCheck(), scopeCheck(), storageCheck(), lockingCheck(), opencodePluginCheck(), vscodeMcpCheck()];
|
|
518
576
|
checks.push(await mcpCheck());
|
|
519
577
|
checks.push(legacyCheck());
|
|
520
578
|
let allOk = true;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime floor for the native SQLite driver (D74).
|
|
3
|
+
*
|
|
4
|
+
* `better-sqlite3` 13 is built against Node-API 10, and Node only gained
|
|
5
|
+
* Node-API 10 in **22.14.0**. On an older Node the module registers and then
|
|
6
|
+
* dies with a segmentation fault inside `new Database(...)` — no message, no
|
|
7
|
+
* stack, exit 139 on macOS/Linux and `0xC0000005` on Windows. Upstream:
|
|
8
|
+
* WiseLibs/better-sqlite3#1514 (open; reproduces on win32-x64 and
|
|
9
|
+
* darwin-arm64, Node 20 and 22; `v12.11.1` is unaffected). Node 20 can never
|
|
10
|
+
* work with v13 — Node-API 10 never landed on that line — and it is EoL.
|
|
11
|
+
*
|
|
12
|
+
* So the floor is not "whatever `engines` claimed" (it said >=22.6, which was
|
|
13
|
+
* the `--experimental-strip-types` floor, not the driver's) but the exact
|
|
14
|
+
* Node-API 10 boundary. Below it we refuse loudly instead of segfaulting.
|
|
15
|
+
*
|
|
16
|
+
* Pure module — no imports, no db — so the comparison is unit-testable.
|
|
17
|
+
*/
|
|
18
|
+
export const MIN_NODE_VERSION = "22.14.0";
|
|
19
|
+
export const NODE_FLOOR = { major: 22, minor: 14, patch: 0 };
|
|
20
|
+
/** Parse `major.minor.patch`, ignoring any prerelease suffix (`23.1.0-nightly`). */
|
|
21
|
+
export function parseNodeVersion(version) {
|
|
22
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
23
|
+
if (!m)
|
|
24
|
+
return null;
|
|
25
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
|
26
|
+
}
|
|
27
|
+
/** True when `version` is at or above the Node-API 10 floor. */
|
|
28
|
+
export function nodeSupportsNativeDriver(version) {
|
|
29
|
+
const v = parseNodeVersion(version);
|
|
30
|
+
if (!v)
|
|
31
|
+
return false;
|
|
32
|
+
const f = NODE_FLOOR;
|
|
33
|
+
if (v.major !== f.major)
|
|
34
|
+
return v.major > f.major;
|
|
35
|
+
if (v.minor !== f.minor)
|
|
36
|
+
return v.minor > f.minor;
|
|
37
|
+
return v.patch >= f.patch;
|
|
38
|
+
}
|
|
39
|
+
/** The actionable message used by the driver guard and `doctor`. */
|
|
40
|
+
export function nativeDriverFloorMessage(version = process.versions.node) {
|
|
41
|
+
return (`better-sqlite3 13 needs Node >= ${MIN_NODE_VERSION} (it is built against ` +
|
|
42
|
+
`Node-API 10), but this runtime is v${version}. On older Node the driver ` +
|
|
43
|
+
`segfaults on the first database open, which is why open-memex refuses to ` +
|
|
44
|
+
`load it. Upgrade Node (nvm install ${MIN_NODE_VERSION.split(".").slice(0, 2).join(".")} ` +
|
|
45
|
+
`&& nvm use ${MIN_NODE_VERSION.split(".").slice(0, 2).join(".")}), or pin the driver ` +
|
|
46
|
+
`down with: npm install better-sqlite3@^12.11.1`);
|
|
47
|
+
}
|
package/dist/store/db.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { paths } from "../paths.js";
|
|
3
|
+
import { nativeDriverFloorMessage, nodeSupportsNativeDriver, } from "../runtime-floor.js";
|
|
3
4
|
// Runtime-agnostic SQLite:
|
|
4
5
|
// - Inside opencode (Bun runtime): bun:sqlite (built-in, no native module load)
|
|
5
6
|
// - Under Node CLI: better-sqlite3
|
|
@@ -11,6 +12,13 @@ function loadDatabase() {
|
|
|
11
12
|
// bun:sqlite is a built-in module; only resolvable under the Bun runtime.
|
|
12
13
|
return require("bun:sqlite").Database;
|
|
13
14
|
}
|
|
15
|
+
// D74: better-sqlite3 13 is built against Node-API 10, which Node gained in
|
|
16
|
+
// 22.14.0. Below that floor `require` succeeds and `new Database()` then
|
|
17
|
+
// segfaults the process with no diagnostic — so refuse before we can crash
|
|
18
|
+
// and say what to do instead (upstream: WiseLibs/better-sqlite3#1514).
|
|
19
|
+
if (!nodeSupportsNativeDriver(process.versions.node)) {
|
|
20
|
+
throw new Error(nativeDriverFloorMessage());
|
|
21
|
+
}
|
|
14
22
|
return require("better-sqlite3");
|
|
15
23
|
}
|
|
16
24
|
let _db = null;
|
package/docs/V2-DESIGN.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenMemex — Design Document (protocol v0.2)
|
|
2
2
|
|
|
3
|
-
**Status:** FROZEN — protocol v0.2 (2026-09-26). Decisions D1–
|
|
3
|
+
**Status:** FROZEN — protocol v0.2 (2026-09-26). Decisions D1–D74 are settled; open questions are tracked at the end of this document.
|
|
4
4
|
**Author:** Stone, with 小沐
|
|
5
5
|
**Changelog vs v1:** incorporates round-3 review from Perplexity, Grok, Gemini, ChatGPT, DeepSeek.
|
|
6
6
|
Key changes: Design Principles section; `role` separated from `type`; two iron rules;
|
|
@@ -1469,6 +1469,36 @@ requirement: personal data never touches third-party services). Benchmarks to tr
|
|
|
1469
1469
|
entry back before deleting. The HTML rendering of the same data layer
|
|
1470
1470
|
follows as a separate change; auto-draft stays gated on the unlock
|
|
1471
1471
|
conditions in the design doc.
|
|
1472
|
+
- **D74 — the Node floor is 22.14, because the driver sets it, not us.** D72
|
|
1473
|
+
moved the Node driver to `better-sqlite3` 13 (the N-API rewrite) to stop the
|
|
1474
|
+
Node 24.19+ exit-path abort. That upgrade carried an unstated requirement:
|
|
1475
|
+
v13 is compiled against **Node-API 10**, and Node only gained Node-API 10 in
|
|
1476
|
+
**22.14.0**. Below that floor `require("better-sqlite3")` succeeds and then
|
|
1477
|
+
`new Database()` segfaults the process — no message, no stack, exit 139 on
|
|
1478
|
+
macOS/Linux, `0xC0000005` on Windows. Measured on win32-x64: Node 22.12.0
|
|
1479
|
+
dies on the first open, Node 22.23.3 and 24.20.0 work; Node 20 can never work
|
|
1480
|
+
with v13, since Node-API 10 never landed on that line (EoL regardless). This
|
|
1481
|
+
is upstream and still open — WiseLibs/better-sqlite3#1514, plus #1516 for the
|
|
1482
|
+
separate `npm ci` gyp problem on Windows, where `gypfile: false` does not stop
|
|
1483
|
+
npm from shelling out to node-gyp. `engines.node` had been `>=22.6`, which was
|
|
1484
|
+
the `--experimental-strip-types` floor — a real but *different* constraint
|
|
1485
|
+
that predated the driver bump; leaving it would have shipped a package whose
|
|
1486
|
+
declared support window crashes on its own dependency. Three changes:
|
|
1487
|
+
(1) `engines.node` is `>=22.14.0`, and the two floors are documented as two
|
|
1488
|
+
floors rather than merged into one number; (2) `loadDatabase` refuses below
|
|
1489
|
+
the new floor and throws the actionable message (upgrade Node, or pin
|
|
1490
|
+
`better-sqlite3@^12.11.1` — v12 is unaffected), because a crash no in-process
|
|
1491
|
+
`try`/`catch` can report is the worst possible failure mode for a CLI;
|
|
1492
|
+
(3) `doctor` reports the floor *and* probes the driver in a **child process**,
|
|
1493
|
+
since a segfault cannot be caught in-process — the probe resolves the driver
|
|
1494
|
+
from this package's own directory, because `doctor` routinely runs with cwd
|
|
1495
|
+
set to a user project where a bare `require` finds nothing. The version
|
|
1496
|
+
comparison lives in `src/runtime-floor.ts`, pure and unit-tested, because the
|
|
1497
|
+
boundary is a fact to be tested (22.13.9 below, 22.14.0 exactly at, 22.23.3
|
|
1498
|
+
and 24.20.0 above) rather than a constant to be trusted. Deliberately NOT
|
|
1499
|
+
done: pinning back to v12 (that re-opens D72's Node 24 abort), or attempting
|
|
1500
|
+
to fix either upstream bug here.
|
|
1501
|
+
|
|
1472
1502
|
- **D73 — one user statement, one memory: the keyword hook hands off to the
|
|
1473
1503
|
agent.** The keyword hook and the agent are two writers for one user turn,
|
|
1474
1504
|
and only one of them knows the sentence is already stored. Observed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-memex",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1-alpha.1",
|
|
4
4
|
"description": "Local-first memory layer and protocol for AI coding agents. Markdown source of truth, SQLite FTS5 index, zero cloud.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -38,6 +38,6 @@
|
|
|
38
38
|
"open-memex": "dist/cli.js"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=22.
|
|
41
|
+
"node": ">=22.14.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/scripts/smoke-pure.ts
CHANGED
|
@@ -9,6 +9,12 @@ import { resolveProjectScope, resolveCwdScope, pickScopeRoot, PERSONAL_SCOPE } f
|
|
|
9
9
|
import { cjkIndexText, cjkQueryExpr, hasCjk } from "../src/retrieve/cjk.ts";
|
|
10
10
|
import { toFtsQuery } from "../src/retrieve/query.ts";
|
|
11
11
|
import { contentHash, similarity, NEAR_DUP_THRESHOLD } from "../src/store/lifecycle.ts";
|
|
12
|
+
import {
|
|
13
|
+
MIN_NODE_VERSION,
|
|
14
|
+
nativeDriverFloorMessage,
|
|
15
|
+
nodeSupportsNativeDriver,
|
|
16
|
+
parseNodeVersion,
|
|
17
|
+
} from "../src/runtime-floor.ts";
|
|
12
18
|
import {
|
|
13
19
|
TURN_ECHO_THRESHOLD,
|
|
14
20
|
TURN_ECHO_WINDOW_MS,
|
|
@@ -608,6 +614,31 @@ console.log("== env var names (D62) ==");
|
|
|
608
614
|
}
|
|
609
615
|
}
|
|
610
616
|
|
|
617
|
+
console.log("== runtime floor (native driver, D74) ==");
|
|
618
|
+
// better-sqlite3 13 is built against Node-API 10, which Node gained in 22.14.0.
|
|
619
|
+
// Below that floor `require` succeeds and `new Database()` segfaults the
|
|
620
|
+
// process with no diagnostic, so the version gate is the only thing standing
|
|
621
|
+
// between an old Node and a silent crash. The 22.12.0 / 22.23.3 boundary is
|
|
622
|
+
// the measured one (win32-x64, WiseLibs/better-sqlite3#1514).
|
|
623
|
+
ok("floor is 22.14.0", MIN_NODE_VERSION === "22.14.0");
|
|
624
|
+
ok("22.12.0 is below the floor (segfaults)", !nodeSupportsNativeDriver("22.12.0"));
|
|
625
|
+
ok("22.13.9 is below the floor", !nodeSupportsNativeDriver("22.13.9"));
|
|
626
|
+
ok("22.14.0 is exactly the floor", nodeSupportsNativeDriver("22.14.0"));
|
|
627
|
+
ok("22.14.1 is above", nodeSupportsNativeDriver("22.14.1"));
|
|
628
|
+
ok("22.23.3 is above (measured working)", nodeSupportsNativeDriver("22.23.3"));
|
|
629
|
+
ok("24.20.0 is above (measured working)", nodeSupportsNativeDriver("24.20.0"));
|
|
630
|
+
ok("25.x is above", nodeSupportsNativeDriver("25.3.0"));
|
|
631
|
+
ok("20.x is below (Node-API 10 never landed on 20)", !nodeSupportsNativeDriver("20.19.6"));
|
|
632
|
+
ok("v-prefix tolerated", nodeSupportsNativeDriver("v22.14.0"));
|
|
633
|
+
ok("prerelease suffix tolerated", nodeSupportsNativeDriver("23.1.0-nightly20260101"));
|
|
634
|
+
ok("garbage is below, not a crash", !nodeSupportsNativeDriver("not-a-version"));
|
|
635
|
+
ok("parses major/minor/patch", JSON.stringify(parseNodeVersion("22.14.3")) === '{"major":22,"minor":14,"patch":3}');
|
|
636
|
+
const floorMsg = nativeDriverFloorMessage("22.12.0");
|
|
637
|
+
ok("message names the floor", floorMsg.includes("22.14.0"));
|
|
638
|
+
ok("message names the running version", floorMsg.includes("22.12.0"));
|
|
639
|
+
ok("message offers a fix (upgrade or downgrade)", /Upgrade Node/.test(floorMsg) && /better-sqlite3@\^12/.test(floorMsg));
|
|
640
|
+
ok("message explains the segfault", /segfaults/.test(floorMsg));
|
|
641
|
+
|
|
611
642
|
console.log("== lifecycle pure: contentHash / similarity ==");
|
|
612
643
|
ok("hash deterministic + whitespace-insensitive", contentHash("hello world\n") === contentHash("hello world"));
|
|
613
644
|
ok("hash differs on content", contentHash("hello world") !== contentHash("hello mars"));
|
package/src/doctor.ts
CHANGED
|
@@ -10,6 +10,11 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { loadConfig, configSource, DEFAULT_CONFIG, stripJsonComments } from "./config.ts";
|
|
11
11
|
import { paths, homeOverride } from "./paths.ts";
|
|
12
12
|
import { resolveCwdScope } from "./scope.ts";
|
|
13
|
+
import {
|
|
14
|
+
MIN_NODE_VERSION,
|
|
15
|
+
nativeDriverFloorMessage,
|
|
16
|
+
nodeSupportsNativeDriver,
|
|
17
|
+
} from "./runtime-floor.ts";
|
|
13
18
|
import { db, backendName } from "./store/db.ts";
|
|
14
19
|
import { LEGACY_PACKAGE_NAME, opencodeGlobalConfigPath, userMcpConfigPath } from "./init.ts";
|
|
15
20
|
|
|
@@ -37,12 +42,72 @@ const EXPECTED_TOOLS = [
|
|
|
37
42
|
|
|
38
43
|
function nodeCheck(): Check {
|
|
39
44
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
45
|
+
// Two independent floors, and the higher one wins: 22.6 for
|
|
46
|
+
// --experimental-strip-types, 22.14 for the native SQLite driver (D74).
|
|
47
|
+
const stripTypesOk = major > 22 || (major === 22 && minor >= 6);
|
|
48
|
+
const driverOk = nodeSupportsNativeDriver(process.versions.node);
|
|
49
|
+
const ok = stripTypesOk && driverOk;
|
|
50
|
+
let detail = `v${process.versions.node}`;
|
|
51
|
+
if (!stripTypesOk) {
|
|
52
|
+
detail += ` (need >= 22.6 for --experimental-strip-types)`;
|
|
53
|
+
} else if (!driverOk) {
|
|
54
|
+
detail += ` (need >= ${MIN_NODE_VERSION}: better-sqlite3 13 is built against Node-API 10 and segfaults below it)`;
|
|
55
|
+
} else {
|
|
56
|
+
detail += " (>= 22.14: ok for type stripping and the SQLite driver)";
|
|
57
|
+
}
|
|
58
|
+
return { name: "node", ok, detail };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* D74: the native driver used to fail by segfaulting — invisible to any
|
|
63
|
+
* in-process try/catch. Now that `loadDatabase` refuses below the floor, the
|
|
64
|
+
* load is safe to attempt here, so a broken driver reports itself here instead
|
|
65
|
+
* of killing the first command the user runs.
|
|
66
|
+
*/
|
|
67
|
+
function driverCheck(): Check {
|
|
68
|
+
if (typeof (globalThis as { Bun?: unknown }).Bun !== "undefined") {
|
|
69
|
+
return { name: "sqlite driver", ok: true, detail: "bun:sqlite (built-in, no native module)" };
|
|
70
|
+
}
|
|
71
|
+
// Resolve the driver from THIS package's directory, not from the caller's
|
|
72
|
+
// cwd: `doctor` routinely runs with cwd set to a user project, where a bare
|
|
73
|
+
// `require("better-sqlite3")` would not find our node_modules at all.
|
|
74
|
+
const pkgRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
75
|
+
try {
|
|
76
|
+
const probe = execFileSync(
|
|
77
|
+
process.execPath,
|
|
78
|
+
[
|
|
79
|
+
"-e",
|
|
80
|
+
"const p=process.argv[1];const D=require(require.resolve('better-sqlite3',{paths:[p]}));const db=new D(':memory:');db.exec('create table t(a)');db.close();process.stdout.write('ok')",
|
|
81
|
+
pkgRoot,
|
|
82
|
+
],
|
|
83
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 30_000 },
|
|
84
|
+
);
|
|
85
|
+
return {
|
|
86
|
+
name: "sqlite driver",
|
|
87
|
+
ok: probe.includes("ok"),
|
|
88
|
+
detail: probe.includes("ok")
|
|
89
|
+
? "better-sqlite3 loaded and opened an in-memory database"
|
|
90
|
+
: `probe returned ${JSON.stringify(probe)}`,
|
|
91
|
+
};
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const e = err as { status?: number | null; signal?: string | null; stderr?: string };
|
|
94
|
+
// A native crash arrives as a huge unsigned status (0xC0000005 on Windows,
|
|
95
|
+
// 139 via SIGSEGV elsewhere). Print both forms - the hex is the one people
|
|
96
|
+
// recognize from Event Viewer or a shell's `exit=139`.
|
|
97
|
+
const how = e.signal
|
|
98
|
+
? `signal ${e.signal}`
|
|
99
|
+
: typeof e.status === "number" && e.status > 255
|
|
100
|
+
? `exit code ${e.status} (0x${(e.status >>> 0).toString(16).toUpperCase()})`
|
|
101
|
+
: `exit code ${e.status}`;
|
|
102
|
+
return {
|
|
103
|
+
name: "sqlite driver",
|
|
104
|
+
ok: false,
|
|
105
|
+
detail:
|
|
106
|
+
`better-sqlite3 could not open a database (${how}). ` +
|
|
107
|
+
(e.stderr ? String(e.stderr).trim().split("\n").slice(-2).join(" ") + " " : "") +
|
|
108
|
+
nativeDriverFloorMessage(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
46
111
|
}
|
|
47
112
|
|
|
48
113
|
function configCheck(): Check {
|
|
@@ -540,7 +605,7 @@ function vscodeMcpCheck(): Check {
|
|
|
540
605
|
|
|
541
606
|
export async function runDoctor(): Promise<boolean> {
|
|
542
607
|
console.log("open-memex doctor");
|
|
543
|
-
const checks: Check[] = [nodeCheck(), configCheck(), scopeCheck(), storageCheck(), lockingCheck(), opencodePluginCheck(), vscodeMcpCheck()];
|
|
608
|
+
const checks: Check[] = [nodeCheck(), configCheck(), driverCheck(), scopeCheck(), storageCheck(), lockingCheck(), opencodePluginCheck(), vscodeMcpCheck()];
|
|
544
609
|
checks.push(await mcpCheck());
|
|
545
610
|
checks.push(legacyCheck());
|
|
546
611
|
let allOk = true;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime floor for the native SQLite driver (D74).
|
|
3
|
+
*
|
|
4
|
+
* `better-sqlite3` 13 is built against Node-API 10, and Node only gained
|
|
5
|
+
* Node-API 10 in **22.14.0**. On an older Node the module registers and then
|
|
6
|
+
* dies with a segmentation fault inside `new Database(...)` — no message, no
|
|
7
|
+
* stack, exit 139 on macOS/Linux and `0xC0000005` on Windows. Upstream:
|
|
8
|
+
* WiseLibs/better-sqlite3#1514 (open; reproduces on win32-x64 and
|
|
9
|
+
* darwin-arm64, Node 20 and 22; `v12.11.1` is unaffected). Node 20 can never
|
|
10
|
+
* work with v13 — Node-API 10 never landed on that line — and it is EoL.
|
|
11
|
+
*
|
|
12
|
+
* So the floor is not "whatever `engines` claimed" (it said >=22.6, which was
|
|
13
|
+
* the `--experimental-strip-types` floor, not the driver's) but the exact
|
|
14
|
+
* Node-API 10 boundary. Below it we refuse loudly instead of segfaulting.
|
|
15
|
+
*
|
|
16
|
+
* Pure module — no imports, no db — so the comparison is unit-testable.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const MIN_NODE_VERSION = "22.14.0";
|
|
20
|
+
|
|
21
|
+
export interface NodeFloor {
|
|
22
|
+
major: number;
|
|
23
|
+
minor: number;
|
|
24
|
+
patch: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const NODE_FLOOR: NodeFloor = { major: 22, minor: 14, patch: 0 };
|
|
28
|
+
|
|
29
|
+
/** Parse `major.minor.patch`, ignoring any prerelease suffix (`23.1.0-nightly`). */
|
|
30
|
+
export function parseNodeVersion(version: string): NodeFloor | null {
|
|
31
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
32
|
+
if (!m) return null;
|
|
33
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** True when `version` is at or above the Node-API 10 floor. */
|
|
37
|
+
export function nodeSupportsNativeDriver(version: string): boolean {
|
|
38
|
+
const v = parseNodeVersion(version);
|
|
39
|
+
if (!v) return false;
|
|
40
|
+
const f = NODE_FLOOR;
|
|
41
|
+
if (v.major !== f.major) return v.major > f.major;
|
|
42
|
+
if (v.minor !== f.minor) return v.minor > f.minor;
|
|
43
|
+
return v.patch >= f.patch;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The actionable message used by the driver guard and `doctor`. */
|
|
47
|
+
export function nativeDriverFloorMessage(version: string = process.versions.node): string {
|
|
48
|
+
return (
|
|
49
|
+
`better-sqlite3 13 needs Node >= ${MIN_NODE_VERSION} (it is built against ` +
|
|
50
|
+
`Node-API 10), but this runtime is v${version}. On older Node the driver ` +
|
|
51
|
+
`segfaults on the first database open, which is why open-memex refuses to ` +
|
|
52
|
+
`load it. Upgrade Node (nvm install ${MIN_NODE_VERSION.split(".").slice(0, 2).join(".")} ` +
|
|
53
|
+
`&& nvm use ${MIN_NODE_VERSION.split(".").slice(0, 2).join(".")}), or pin the driver ` +
|
|
54
|
+
`down with: npm install better-sqlite3@^12.11.1`
|
|
55
|
+
);
|
|
56
|
+
}
|
package/src/store/db.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { paths } from "../paths.ts";
|
|
3
|
+
import {
|
|
4
|
+
MIN_NODE_VERSION,
|
|
5
|
+
nativeDriverFloorMessage,
|
|
6
|
+
nodeSupportsNativeDriver,
|
|
7
|
+
} from "../runtime-floor.ts";
|
|
3
8
|
|
|
4
9
|
// Runtime-agnostic SQLite:
|
|
5
10
|
// - Inside opencode (Bun runtime): bun:sqlite (built-in, no native module load)
|
|
@@ -18,6 +23,13 @@ function loadDatabase(): AnyDatabaseCtor {
|
|
|
18
23
|
// bun:sqlite is a built-in module; only resolvable under the Bun runtime.
|
|
19
24
|
return require("bun:sqlite").Database as AnyDatabaseCtor;
|
|
20
25
|
}
|
|
26
|
+
// D74: better-sqlite3 13 is built against Node-API 10, which Node gained in
|
|
27
|
+
// 22.14.0. Below that floor `require` succeeds and `new Database()` then
|
|
28
|
+
// segfaults the process with no diagnostic — so refuse before we can crash
|
|
29
|
+
// and say what to do instead (upstream: WiseLibs/better-sqlite3#1514).
|
|
30
|
+
if (!nodeSupportsNativeDriver(process.versions.node)) {
|
|
31
|
+
throw new Error(nativeDriverFloorMessage());
|
|
32
|
+
}
|
|
21
33
|
return require("better-sqlite3") as AnyDatabaseCtor;
|
|
22
34
|
}
|
|
23
35
|
|