From 6740cfb9925fe3fb578369400314062445e8978f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 14:27:02 +0000 Subject: [PATCH] Version Packages --- .changeset/docs-ide-matrix.md | 27 -- .changeset/feat-bun-sqlite-support.md | 12 - .changeset/feat-enrich.md | 12 - .changeset/feat-import.md | 62 --- .changeset/feat-new-ides.md | 28 -- .changeset/feat-xdg-dirs.md | 40 -- .changeset/fix-better-sqlite3-v12.md | 16 - .changeset/fix-opencode-installer.md | 27 -- .changeset/fix-pack-release-optional-deps.md | 12 - .changeset/fix-privacy-enforcement.md | 37 -- .changeset/fix-session-start-cwd-scope.md | 20 - .changeset/fix-windows-hide-console.md | 13 - .../fix-windows-shellquote-backslash.md | 17 - .changeset/fix-windows-ux.md | 31 -- .changeset/fix-worker-hardening.md | 31 -- .changeset/issue-sweep-fixes.md | 42 -- .changeset/transformers-v3.md | 20 - apps/cli/CHANGELOG.md | 370 ++++++++++++++++++ apps/cli/package.json | 9 +- apps/mcp-server/CHANGELOG.md | 23 ++ apps/mcp-server/package.json | 2 +- apps/worker/CHANGELOG.md | 20 + apps/worker/package.json | 2 +- evals/CHANGELOG.md | 8 + evals/package.json | 2 +- packages/compress/CHANGELOG.md | 40 ++ packages/compress/package.json | 7 +- packages/config/CHANGELOG.md | 140 +++++++ packages/config/package.json | 6 +- packages/core/CHANGELOG.md | 47 +++ packages/core/package.json | 6 +- packages/embedding/CHANGELOG.md | 98 +++++ packages/embedding/package.json | 6 +- packages/hooks/CHANGELOG.md | 66 ++++ packages/hooks/package.json | 6 +- packages/installers/CHANGELOG.md | 160 ++++++++ packages/installers/package.json | 6 +- packages/storage/CHANGELOG.md | 107 +++++ packages/storage/package.json | 6 +- 39 files changed, 1118 insertions(+), 466 deletions(-) delete mode 100644 .changeset/docs-ide-matrix.md delete mode 100644 .changeset/feat-bun-sqlite-support.md delete mode 100644 .changeset/feat-enrich.md delete mode 100644 .changeset/feat-import.md delete mode 100644 .changeset/feat-new-ides.md delete mode 100644 .changeset/feat-xdg-dirs.md delete mode 100644 .changeset/fix-better-sqlite3-v12.md delete mode 100644 .changeset/fix-opencode-installer.md delete mode 100644 .changeset/fix-pack-release-optional-deps.md delete mode 100644 .changeset/fix-privacy-enforcement.md delete mode 100644 .changeset/fix-session-start-cwd-scope.md delete mode 100644 .changeset/fix-windows-hide-console.md delete mode 100644 .changeset/fix-windows-shellquote-backslash.md delete mode 100644 .changeset/fix-windows-ux.md delete mode 100644 .changeset/fix-worker-hardening.md delete mode 100644 .changeset/issue-sweep-fixes.md delete mode 100644 .changeset/transformers-v3.md diff --git a/.changeset/docs-ide-matrix.md b/.changeset/docs-ide-matrix.md deleted file mode 100644 index f7191ab..0000000 --- a/.changeset/docs-ide-matrix.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@cavemem/installers': patch -'cavemem': patch ---- - -Surface which IDEs actually capture memory vs are query-only (#58) - -Users installing cavemem into Cursor, Gemini CLI, Antigravity, or IBM Bob had no way -to tell — short of an empty database — that those integrations are MCP query-only: -hooks never fire, so no new observations are ever captured there. `cavemem status` -silently listed every installed IDE the same way. - -Each `Installer` now declares `capture: 'full' | 'partial' | 'none'` (plus an optional -`captureNotes` caveat) reflecting what its `install()` actually wires up: Claude Code, -OpenCode, Codex CLI, GitHub Copilot, and Augment Code capture observations through -hooks (OpenCode via its bundled bridge plugin rather than a `hooks.json`-style file; -Codex and Copilot have no SessionEnd event; Augment has no UserPromptSubmit event). -Cursor, Gemini CLI, Antigravity, and IBM Bob register MCP only. - -`cavemem status` reads this metadata and annotates query-only IDEs inline — -`ides: claude-code, antigravity (query-only)` — instead of listing them -indistinguishably from IDEs that actually fill the database. The README gains an IDE -capability matrix (capture / query / notes) under Install, with footnotes on -OpenCode's bridge plugin and the Copilot/Codex/Augment missing-event caveats. - -No behavioral change to any installer's `install()`/`uninstall()` — this is metadata -and read-side reporting only. diff --git a/.changeset/feat-bun-sqlite-support.md b/.changeset/feat-bun-sqlite-support.md deleted file mode 100644 index 6380150..0000000 --- a/.changeset/feat-bun-sqlite-support.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@cavemem/storage': minor ---- - -Add bun:sqlite backend so cavemem runs natively under Bun without the better-sqlite3 native addon. - -When the process is Bun, `openDb` loads `bun:sqlite` via `createRequire` at runtime and -wraps it in the same `DbHandle` interface used by the better-sqlite3 path. The adapter -normalises the two API differences: `get()` returning `null` vs `undefined` on no-match, -and the absence of `{ readonly: false }` support on bun:sqlite. All SQL — FTS5, bm25, -snippet, binary blob storage — is identical across both backends. No package.json change -is needed; `bun:sqlite` is a Bun built-in. Node users are unaffected. diff --git a/.changeset/feat-enrich.md b/.changeset/feat-enrich.md deleted file mode 100644 index a4a9697..0000000 --- a/.changeset/feat-enrich.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@cavemem/compress': minor -'@cavemem/config': minor -'@cavemem/mcp-server': minor -'cavemem': minor ---- - -Opt-in web-search enrichment MCP tool (#55), phase 1. - -- **config (#55):** New `enrich` settings block: `enrich.enabled` (default `false`), `enrich.maxResults` (default 3, max 5), `enrich.timeoutMs` (default 8000). Off by default — when off, the enrich MCP tool is not registered and no network call is ever made. Picked up automatically by `cavemem config show` / `settingsDocs()`. -- **compress (#55):** New `redactSecrets(text)` export that masks common API-key shapes (OpenAI/Stripe `sk-…`, GitHub `ghp_`/`github_pat_`, AWS `AKIA…`, Slack `xox…`) as `[REDACTED]`. Gated by `settings.privacy.redactSecrets` at call sites. -- **mcp-server (#55):** New `enrich(query, note?)` tool, registered only when `enrich.enabled` is `true`. Searches DuckDuckGo's HTML endpoint (no API key), parses the top results with a hand-rolled linear-time parser, fetches each result page with a 500 KB byte cap and per-request timeout, strips it to plain text, and truncates to 2000 chars. Extracts are stored through `MemoryStore.addObservation` (compressed, privacy-redacted) under a dedicated synthetic `enrich` session, tagged `metadata: { source: 'web', url, query, note? }` for provenance; `query`/`note` are run through `redactPrivate` + `redactSecrets` before storage, and source URLs survive compression byte-for-byte. The tool returns `{ query, results: [{ title, url, extract, observation_id }], stored_ids }`. **SSRF-hardened:** every fetched URL and each manually-followed redirect hop (max 3) must be http(s) to a public host — loopback, RFC1918, link-local (`169.254/16`), and unique-local targets (including obfuscated numeric literals) are rejected without a request. Search failure returns an MCP error with nothing stored; individual blocked or dead result pages are skipped. diff --git a/.changeset/feat-import.md b/.changeset/feat-import.md deleted file mode 100644 index 6ce4f90..0000000 --- a/.changeset/feat-import.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -'@cavemem/storage': minor -'cavemem': minor ---- - -feat(cli,storage): add `cavemem import` to round-trip `cavemem export` output (#33) - -`cavemem export` had no matching `import`, so cross-device transfer (export -JSONL on machine A, load it on machine B) was a manual JSON-massaging -exercise. `cavemem import [--dry-run]` now round-trips exactly -what export emits — `session` and `observation` records; export does not -emit summaries, so import doesn't need to either. - -Sessions merge by id: a session whose id already exists in the target -database is skipped and counted. Observation ids are per-machine -AUTOINCREMENT values with no cross-device coordination — machine A's id=42 -and machine B's id=42 are routinely different observations — so the new -`Storage.importObservation` treats the exported id as a preference, not an -identity: an exact (session_id, ts, content) duplicate anywhere in the -table is skipped; a free id is used as-is; an id occupied by a *different* -observation gets a fresh AUTOINCREMENT id and is counted as "reassigned". -Nothing is ever overwritten, and re-running the same import is a no-op even -after a previous run reassigned ids. The summary line reports -imported/skipped/reassigned counts. Sessions referenced by observations are -imported first (or a minimal session row is synthesized as a defensive -fallback), so the `observations.session_id` foreign key never rejects a -valid row. - -The whole file is validated up front — a malformed line aborts with a -clear, line-numbered error and writes nothing — and the actual writes run -inside one SQLite transaction (new `Storage.transaction`, implemented in -the DbHandle plumbing for both the better-sqlite3 and bun:sqlite backends), -so a failure partway through also leaves the database untouched. -`--dry-run` runs the identical write path and rolls back at the end; when -the target database doesn't exist yet it runs against an in-memory -database instead, so not even an empty `data.db` is left behind. - -Exported `content` already passed through `@cavemem/compress` on the -source machine, so import writes it back verbatim (no re-compression) -rather than through `MemoryStore.addObservation` — recompressing on the -target could change the bytes if its `compression.intensity` setting -differs from the source's, which would break byte-identical -export → import → export round-trips. `Storage.createSession` now returns -whether the row was inserted or already existed, which import uses for its -skip counts. - -Imported observations get no `embeddings` row, so they're picked up by the -worker's embedding backfill loop the same way any newly-added observation -is; the FTS5 index is kept in sync via the same insert trigger every other -write path uses. Embedding vectors themselves are never transported. - -Also fixes two latent `cavemem export` bugs found while wiring this up: -the `Storage` constructor ran schema-init SQL (including a real -`INSERT OR IGNORE`) even when opened `{ readonly: true }`, which SQLite -rejects outright on a true read-only connection — export threw for every -user once the database existed. Readonly mode now skips schema-init, since -it's only ever used against a database an earlier writable `Storage` has -already initialized. And exporting with no database at all now exits -non-zero with a short message instead of an unhandled SQLITE_CANTOPEN. - -README documents the new command and the manual cross-device transfer -flow (export on A, stop the worker, import on B, restart). diff --git a/.changeset/feat-new-ides.md b/.changeset/feat-new-ides.md deleted file mode 100644 index 0bce7c9..0000000 --- a/.changeset/feat-new-ides.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'cavemem': minor -'@cavemem/installers': minor ---- - -Add four new IDE installers (#23, #8, #38, #10) - -- **GitHub Copilot / VS Code** (`--ide copilot`, #23) — full capture+query. Hooks are - written to a cavemem-owned `~/.copilot/hooks/cavemem.json` (Copilot's payloads are - Claude-Code-compatible, so existing handlers read them without translation; Copilot - has no SessionEnd event). MCP is registered in VS Code's user-level `mcp.json` - (platform-dependent path, root key `servers`, explicit `type: "stdio"`). -- **Augment Code** (`--ide augment`, #8) — full capture+query via `~/.augment/settings.json`. - Augment's hook `command` must be a script-file path, so the installer writes thin - wrapper scripts (`.sh`, or `.cmd` on Windows) into `~/.augment/cavemem-hooks/` that - exec the CLI. Augment has no UserPromptSubmit event; the Stop hook sets - `metadata.includeConversationData` because without it the payload carries no - assistant text and turn-summary capture would silently store nothing. The CLI's - `hook run` gains an Augment payload shim mapping `conversation_id` → `session_id`, - `workspace_roots[0]` → `cwd`, and `conversation.agentTextResponse` → `turn_summary`. -- **Antigravity** (`--ide antigravity`, #38) — query-only (no hooks system). Registers - MCP in `~/.gemini/config/mcp_config.json` and warns that sessions there capture no - new observations. -- **IBM Bob** (`--ide bob`, #10) — query-only. Registers MCP in `~/.bob/mcp.json` with - the same warning. - -All four merge non-destructively into existing configs, are idempotent on re-install, -and remove only cavemem-owned entries (plus Augment's wrapper-script dir) on uninstall. diff --git a/.changeset/feat-xdg-dirs.md b/.changeset/feat-xdg-dirs.md deleted file mode 100644 index 569996c..0000000 --- a/.changeset/feat-xdg-dirs.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -'@cavemem/config': minor -'cavemem': minor -'@cavemem/embedding': patch ---- - -feat(config): resolve cavemem home dir via CAVEMEM_HOME / XDG (#47) - -cavemem was hardcoded to `~/.cavemem` for settings.json, data.db, the worker -pidfile/state, and the model cache. Issue #47 asked for a way to stop -polluting `$HOME` and/or relocate the data dir. `@cavemem/config` now -resolves the cavemem home directory in this order: - -1. `CAVEMEM_HOME` env var, if set. -2. An existing `~/.cavemem` — zero breaking change for every current install. -3. `XDG_DATA_HOME/cavemem` whenever the var is explicitly set — on any - platform, not just Linux. Without the var, Linux uses the XDG default - `~/.local/share/cavemem`; macOS/Windows keep `~/.cavemem`. - -Non-absolute env values (no leading `/`, drive letter, or `~`) are ignored — -treated as unset, per the XDG spec. Hooks run with cwd = the project dir, so -a relative `CAVEMEM_HOME` would otherwise silently fragment the store -per-project. - -The resolution is pure `fs.existsSync` checks (no globbing) and cached per -process, so it's cheap on the hooks/worker hot path. `settings.dataDir` -still overrides the data location specifically when set explicitly in -settings.json — its default is now the resolved home dir above instead of a -hardcoded `~/.cavemem`; `.describe()` spells out the difference between the -two. To keep settings.json portable across machines (dotfile sync, restored -backups, containers), `saveSettings` omits `dataDir` from the persisted file -unless the user set it explicitly — the default is re-resolved on every -load. `saveSettings` also always writes to the resolved home now (previously -a custom `dataDir` would redirect the save to a location `loadSettings` -never reads). `cavemem doctor` / `cavemem status` already printed the -resolved `settings`/`dataDir` paths, so both surface the new resolution with -no command changes. - -`@cavemem/embedding`'s musl error message no longer hardcodes -`~/.cavemem/settings.json`, since that path is no longer always accurate. diff --git a/.changeset/fix-better-sqlite3-v12.md b/.changeset/fix-better-sqlite3-v12.md deleted file mode 100644 index 978aa9d..0000000 --- a/.changeset/fix-better-sqlite3-v12.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@cavemem/storage': patch -'cavemem': patch ---- - -fix(storage,cli): bump better-sqlite3 to ^12.0.0 for Node 26 (#37) - -Node 26 removed three V8 C++ APIs (`v8::Object::GetPrototype`, -`v8::Context::GetIsolate`, `v8::PropertyCallbackInfo::This`) that -better-sqlite3 ≤11.x relied on, so `npm install -g cavemem` fails with -`error C2039: 'GetPrototype': is not a member of 'v8::Object'` when there -is no prebuilt binary for the target Node ABI. better-sqlite3 v12 rewrites -those call sites and ships prebuilts for Node 20 through 26. The Storage -API surface used by this repo (`prepare`, `run`, `get`, `all`, `exec`, -FTS5, `bm25`, `snippet`, blob storage) is identical across v11 → v12, so -no code changes are needed. diff --git a/.changeset/fix-opencode-installer.md b/.changeset/fix-opencode-installer.md deleted file mode 100644 index 1d6821f..0000000 --- a/.changeset/fix-opencode-installer.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'cavemem': minor -'@cavemem/installers': minor ---- - -Enhance OpenCode integration with full-featured bridge plugin - -This changeset: - -1. **Adds `apps/cli/src/opencode-bridge.ts`** — a bundled OpenCode plugin that maps - opencode events to cavemem hooks (`session-start`, `session-end`, - `user-prompt-submit`, `post-tool-use`, `stop`). It buffers streaming assistant - text parts and flushes complete turn summaries, injects a system-prompt - reminder about available cavemem MCP tools, and retrieves prior-session context - from the 3 most recent ended sessions via `@cavemem/core`. - -2. **Fixes the OpenCode installer** to: - - Write the correct `mcp` schema (`type: 'local'`, `command: [...]`, - `enabled: true`) to `~/.config/opencode/opencode.json` - - Symlink the bundled `dist/opencode-bridge.js` into - `~/.config/opencode/plugins/cavemem.js` - - Register the plugin explicitly in the `plugin` array for clarity - - Clean up the legacy `~/.opencode/config.json` entry on install and uninstall - - Honor `XDG_CONFIG_HOME` - -3. **Ships the bridge** — `apps/cli/tsup.config.ts` gains an `opencodeBridge` - entry so `dist/opencodeBridge.js` is included in the published package. diff --git a/.changeset/fix-pack-release-optional-deps.md b/.changeset/fix-pack-release-optional-deps.md deleted file mode 100644 index 8e7ae60..0000000 --- a/.changeset/fix-pack-release-optional-deps.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'cavemem': patch ---- - -fix(cli): ship optionalDependencies in the legacy pack-release flow - -`pack-release.mjs` rebuilt the published `package.json` from a hardcoded -allowlist that only read `dependencies`, silently dropping -`optionalDependencies` — so a `pnpm publish:release` tarball could never -install `@huggingface/transformers` and the local embedding provider was -dead on arrival for that publish path. The CI `changeset publish` path was -unaffected. Optional deps now pass through verbatim. diff --git a/.changeset/fix-privacy-enforcement.md b/.changeset/fix-privacy-enforcement.md deleted file mode 100644 index 25cd150..0000000 --- a/.changeset/fix-privacy-enforcement.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@cavemem/config': minor -'@cavemem/compress': minor -'@cavemem/core': patch -'@cavemem/hooks': patch -'cavemem': minor ---- - -Wire up the three privacy settings that existed in the schema but had no consumer: - -- **config/hooks (#48):** `privacy.excludePatterns` is now enforced in - `post-tool-use.ts`. A tool call whose `file_path` / `path` / `notebook_path` - field — or a path-like token embedded in its input/output (e.g. a Bash - command) — matches an `excludePatterns` glob is skipped entirely; nothing - about the excluded content is stored or logged. Glob matching (`**` across - path segments, `*` within a segment) is a hand-rolled linear segment - matcher (`matchesGlob`) in `@cavemem/config` — no regex construction, so - repeated-globstar patterns cannot backtrack pathologically, and no new - dependency. Windows backslash paths are normalized to `/` before matching. -- **compress/core (#49):** `redactSecrets` was a documented setting with no - effect. Added `redactSecrets(text)` to `@cavemem/compress`, scrubbing - Bearer tokens, OpenAI-style `sk-` keys, AWS `AKIA…` access key ids, GitHub - `gh[pousr]_` tokens, `key = value` / `key: value` assignments whose key - name ends in a recognised secret word (`api_key`, `secret`, `token`, - `password`, `passwd`, `authorization`, or a `_key`/`-key` suffix — - env-var prefixes like `STRIPE_SECRET_KEY` included), and PEM private key - blocks with `[REDACTED]` (keeping the leading key name for assignments). - `MemoryStore.addObservation` and `addSummary` now run it before - compression when `settings.privacy.redactSecrets` is true (the default), - independent of the existing `redactPrivate` (`` tag) stripping. - The schema's `redactSecrets` description previously claimed it stripped - `` tags — corrected to describe actual secret scrubbing. -- **config/hooks (#50):** Added `capture.excludeTools` / `capture.includeTools` - (both default `[]`, same glob semantics as above, e.g. `"mcp__broker__*"`). - `post-tool-use.ts` consults them before storing: `excludeTools` always wins - over `includeTools`; a non-empty `includeTools` makes capture opt-in to - just those tools. diff --git a/.changeset/fix-session-start-cwd-scope.md b/.changeset/fix-session-start-cwd-scope.md deleted file mode 100644 index 7fa02a3..0000000 --- a/.changeset/fix-session-start-cwd-scope.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@cavemem/storage': patch -'@cavemem/hooks': patch -'cavemem': patch ---- - -fix(hooks,storage): scope session-start prior-session context to current cwd (#39) - -The `session-start` hook surfaced "Prior-session context" pulled from the -most recent N sessions across **all** projects on the machine. Opening -Claude Code in project A could inject summaries from last night's project B -session into the new kickoff, even though every session row already stores -`cwd`. Now `session-start.ts` widens the initial lookup from 4 → 20 and -filters by exact-`cwd` match before picking the top 3, falling back to the -old global behaviour only when the payload contains no `cwd` (so non-Claude -Code IDEs are unaffected). - -`Storage.searchFts(query, limit, cwd?)` also gained an optional `cwd` -parameter that joins `sessions` and restricts hits to that project; default -behaviour without `cwd` is unchanged. diff --git a/.changeset/fix-windows-hide-console.md b/.changeset/fix-windows-hide-console.md deleted file mode 100644 index fa0b292..0000000 --- a/.changeset/fix-windows-hide-console.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@cavemem/hooks': patch -'cavemem': patch ---- - -fix(cli,hooks): hide Windows console window on detached worker spawn (#11) - -All four detached `child_process.spawn` sites (lifecycle `start`/`viewer`, -`worker start`, and the hooks auto-spawn path) now pass `windowsHide: true`. -Without this, `CreateProcess` on Windows pops a visible console window for -each detached child, which on some setups blocks `cavemem start` and every -hook auto-spawn. POSIX platforms ignore the option, so no behaviour change -on macOS/Linux. diff --git a/.changeset/fix-windows-shellquote-backslash.md b/.changeset/fix-windows-shellquote-backslash.md deleted file mode 100644 index f0f1e8d..0000000 --- a/.changeset/fix-windows-shellquote-backslash.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@cavemem/installers': patch -'cavemem': patch ---- - -fix(installers): quote Windows paths in hook commands even without spaces (#41) - -`shellQuote` previously treated `\` as a bare-token character, so a default -Windows install path with no spaces was written unquoted into the hook -`command` string in `~/.claude/settings.json`. When Claude Code on Windows -runs the hook through MSYS-bash, unquoted backslashes are treated as escape -introducers and stripped, mangling the path -`C:\Users\...\node_modules\cavemem\dist\index.js` into -`CUsers...node_modulescavememdistindex.js` and the hook fails with -`MODULE_NOT_FOUND`. After this fix, any path containing a backslash gets -wrapped in double quotes; both cmd.exe and MSYS-bash preserve the -backslashes verbatim inside `"..."`. POSIX paths are unaffected. diff --git a/.changeset/fix-windows-ux.md b/.changeset/fix-windows-ux.md deleted file mode 100644 index 7929ae3..0000000 --- a/.changeset/fix-windows-ux.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -'@cavemem/installers': patch -'cavemem': patch ---- - -fix(cli,installers): warn loudly when `sh` is missing on Windows (#56, #57) - -Claude Code wraps hook `command` strings in `sh -c` even on Windows. If Git -for Windows' `Git\bin` isn't on the user's `Path`, `sh` doesn't resolve, -every hook fails non-blocking, and cavemem silently stops capturing memory — -`cavemem doctor` and `cavemem status` kept reporting healthy because the -failure never reached the CLI (#56). - -`checkWindowsSh()` (new, `@cavemem/installers`) checks `sh` resolvability on -win32 with an injectable resolver for testing; it's a no-op on every other -platform. `cavemem doctor` now runs it and exits non-zero with a loud -warning + the one-time fix (`C:\Program Files\Git\bin`, or the Scoop -`usr\bin` equivalent, onto user `Path`; verify with `where.exe sh`). -`cavemem install --ide claude-code` runs the same check and prints the same -warning, but does not refuse to install — the user may fix `Path` afterward -and hooks will start working without a re-install. - -**#57 (pwsh emission):** investigated switching the emitted hook `command` -to Claude Code's newer `shell` field (`"bash"` / `"powershell"`) or its -shell-free `args` exec form. Held off: we can't verify either against every -installed Claude Code version, and the current command has no shell -metacharacters, so it already tokenizes identically whether Claude Code -runs it through `sh` or falls back to PowerShell. The #56 fix above — make -the missing-`sh` failure loud instead of silent — is the actionable part we -could ship with confidence. See the Windows note in the README and the -comment in `packages/installers/src/claude-code.ts` for the full reasoning. diff --git a/.changeset/fix-worker-hardening.md b/.changeset/fix-worker-hardening.md deleted file mode 100644 index e48b8b0..0000000 --- a/.changeset/fix-worker-hardening.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -'@cavemem/config': patch -'cavemem': minor ---- - -fix(worker): authenticate the local viewer HTTP API and allow disabling idle shutdown (#51, #32) - -**#51 — unauthenticated worker HTTP API.** The worker's viewer (`127.0.0.1:`) -served `/api/sessions`, `/api/sessions/:id/observations` (expanded, human-readable -bodies), and `/api/search` with no auth, reachable by any local process or by a -malicious web page doing a DNS-rebinding / CSRF fetch. `apps/worker` now applies -defense in depth on every request: - -- A Host-header allowlist rejects (403) anything but `127.0.0.1:` / - `localhost:`, closing the DNS-rebinding path. -- An Origin check rejects (403) any present Origin that isn't - `http://127.0.0.1:` / `http://localhost:` — no CORS headers are added. -- `/api/*` now requires `Authorization: Bearer ` or `X-Cavemem-Token`. The - token is generated once with `crypto.randomBytes(32)`, persisted at - `/worker-token` (mode `0600`), and reused across restarts. - -The plain HTML viewer pages (`/`, `/sessions/:id`) stay token-free for zero-friction -browsing — the worker injects `window.__CAVEMEM_TOKEN__` into the served HTML so any -client-side code can call `/api/*` without the user doing anything. `cavemem viewer` -and `cavemem status` were already file/pid-based and needed no changes; neither -`apps/cli` nor `packages/hooks` call the worker over HTTP. - -**#32 — no way to disable idle shutdown.** `embedding.idleShutdownMs` previously had -to be a positive number, so the worker always self-exited after being idle. Setting -it to `0` now disables idle shutdown entirely (the worker runs until killed); -negative values are clamped to `0`. diff --git a/.changeset/issue-sweep-fixes.md b/.changeset/issue-sweep-fixes.md deleted file mode 100644 index 89cd907..0000000 --- a/.changeset/issue-sweep-fixes.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -'@cavemem/config': patch -'@cavemem/installers': minor -'@cavemem/embedding': patch -'cavemem': minor ---- - -Issue sweep: fix six bugs across config, installers, and embedding. - -- **config (#25):** Correct the inverted description for `search.alpha`. The - ranker computes `alpha * bm25 + (1 - alpha) * cosine`, so `1 = pure BM25` - and `0 = pure cosine`. Doc-only — no behavior change. -- **installers/claude-code (#19):** Write the cavemem MCP server entry to - `~/.claude.json` instead of `~/.claude/settings.json`. Newer Claude Code - reads MCP config from `~/.claude.json`; the previous location was silently - ignored. Hooks continue to live in `~/.claude/settings.json`. Legacy - `mcpServers.cavemem` entries in `settings.json` are migrated out on - install. -- **installers/claude-code (#12):** Stop overwriting pre-existing entries in - `hooks.SessionStart` / `PostToolUse` / etc. The installer now appends - cavemem's hook to whatever is already there and writes a one-shot - `settings.json.pre-cavemem-` backup before mutating a file with - prior hooks. Re-running install no longer duplicates cavemem entries. -- **installers/codex (#17):** Switch from `~/.codex/config.json` (which - Codex never read) to `~/.codex/config.toml` with the `[features] - codex_hooks = true` flag and an `[mcp_servers.cavemem]` table. Also write - `~/.codex/hooks.json` with `SessionStart` / `UserPromptSubmit` / - `PostToolUse` / `Stop` entries so observations are actually captured. - Adds `smol-toml` as a dependency (bundled into the CLI dist). -- **installers/opencode (#14):** Drop a generated plugin at - `~/.config/opencode/plugins/cavemem.js` that hooks into - `session.created` / `session.idle` / `tool.execute.before` / - `tool.execute.after` and forwards to `cavemem hook run …`. Previously the - installer only registered an MCP server and no hooks fired at all, so - observations were empty. Plugin is registered in `opencode.json` and - uses detached `child_process.spawn` so the IDE never blocks on a hook. - Path migrated to OpenCode's documented global config location - (`~/.config/opencode/`, honoring `XDG_CONFIG_HOME`). -- **embedding (#20):** Detect musl libc (Alpine, musl-built Node) before - importing `@xenova/transformers`. The bundled `onnxruntime-node` prebuilts - target glibc and have segfaulted on Alpine in the wild; we now throw a - clean error pointing at `embedding.provider: 'none' | 'ollama'`. diff --git a/.changeset/transformers-v3.md b/.changeset/transformers-v3.md deleted file mode 100644 index 5935a43..0000000 --- a/.changeset/transformers-v3.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -"@cavemem/embedding": patch -"cavemem": patch ---- - -Migrate the local embedding provider from `@xenova/transformers@2` to `@huggingface/transformers@3`. - -`@xenova/transformers` is deprecated and pins an old `onnxruntime-web` → -`onnx-proto` → `protobufjs` chain carrying multiple published `protobufjs` -advisories, including a critical arbitrary-code-execution issue -(GHSA-xq3m-2v4x-88gg). `@huggingface/transformers@3` is the maintained -successor: same `pipeline()` / `env` API, but resolves a current, patched -`protobufjs`, clearing those advisories. (The unrelated `qs` moderate -advisory some audits report comes from `express`/`body-parser`, not this -dependency chain, and is unaffected by this change.) - -The v2 `quantized: true` pipeline flag was removed in v3; it is replaced with -`dtype: 'q8'` (int8 weights, matching the old quantized default). Embedding -output is unchanged — same model (`Xenova/all-MiniLM-L6-v2`), same 384 dims — -so existing stored vectors stay compatible and no re-embed is triggered. diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md index c60a08a..9f95425 100644 --- a/apps/cli/CHANGELOG.md +++ b/apps/cli/CHANGELOG.md @@ -1,5 +1,375 @@ # cavemem +## 0.3.0 + +### Minor Changes + +- dec94ef: Opt-in web-search enrichment MCP tool (#55), phase 1. + + - **config (#55):** New `enrich` settings block: `enrich.enabled` (default `false`), `enrich.maxResults` (default 3, max 5), `enrich.timeoutMs` (default 8000). Off by default — when off, the enrich MCP tool is not registered and no network call is ever made. Picked up automatically by `cavemem config show` / `settingsDocs()`. + - **compress (#55):** New `redactSecrets(text)` export that masks common API-key shapes (OpenAI/Stripe `sk-…`, GitHub `ghp_`/`github_pat_`, AWS `AKIA…`, Slack `xox…`) as `[REDACTED]`. Gated by `settings.privacy.redactSecrets` at call sites. + - **mcp-server (#55):** New `enrich(query, note?)` tool, registered only when `enrich.enabled` is `true`. Searches DuckDuckGo's HTML endpoint (no API key), parses the top results with a hand-rolled linear-time parser, fetches each result page with a 500 KB byte cap and per-request timeout, strips it to plain text, and truncates to 2000 chars. Extracts are stored through `MemoryStore.addObservation` (compressed, privacy-redacted) under a dedicated synthetic `enrich` session, tagged `metadata: { source: 'web', url, query, note? }` for provenance; `query`/`note` are run through `redactPrivate` + `redactSecrets` before storage, and source URLs survive compression byte-for-byte. The tool returns `{ query, results: [{ title, url, extract, observation_id }], stored_ids }`. **SSRF-hardened:** every fetched URL and each manually-followed redirect hop (max 3) must be http(s) to a public host — loopback, RFC1918, link-local (`169.254/16`), and unique-local targets (including obfuscated numeric literals) are rejected without a request. Search failure returns an MCP error with nothing stored; individual blocked or dead result pages are skipped. + +- 2db720f: feat(cli,storage): add `cavemem import` to round-trip `cavemem export` output (#33) + + `cavemem export` had no matching `import`, so cross-device transfer (export + JSONL on machine A, load it on machine B) was a manual JSON-massaging + exercise. `cavemem import [--dry-run]` now round-trips exactly + what export emits — `session` and `observation` records; export does not + emit summaries, so import doesn't need to either. + + Sessions merge by id: a session whose id already exists in the target + database is skipped and counted. Observation ids are per-machine + AUTOINCREMENT values with no cross-device coordination — machine A's id=42 + and machine B's id=42 are routinely different observations — so the new + `Storage.importObservation` treats the exported id as a preference, not an + identity: an exact (session_id, ts, content) duplicate anywhere in the + table is skipped; a free id is used as-is; an id occupied by a _different_ + observation gets a fresh AUTOINCREMENT id and is counted as "reassigned". + Nothing is ever overwritten, and re-running the same import is a no-op even + after a previous run reassigned ids. The summary line reports + imported/skipped/reassigned counts. Sessions referenced by observations are + imported first (or a minimal session row is synthesized as a defensive + fallback), so the `observations.session_id` foreign key never rejects a + valid row. + + The whole file is validated up front — a malformed line aborts with a + clear, line-numbered error and writes nothing — and the actual writes run + inside one SQLite transaction (new `Storage.transaction`, implemented in + the DbHandle plumbing for both the better-sqlite3 and bun:sqlite backends), + so a failure partway through also leaves the database untouched. + `--dry-run` runs the identical write path and rolls back at the end; when + the target database doesn't exist yet it runs against an in-memory + database instead, so not even an empty `data.db` is left behind. + + Exported `content` already passed through `@cavemem/compress` on the + source machine, so import writes it back verbatim (no re-compression) + rather than through `MemoryStore.addObservation` — recompressing on the + target could change the bytes if its `compression.intensity` setting + differs from the source's, which would break byte-identical + export → import → export round-trips. `Storage.createSession` now returns + whether the row was inserted or already existed, which import uses for its + skip counts. + + Imported observations get no `embeddings` row, so they're picked up by the + worker's embedding backfill loop the same way any newly-added observation + is; the FTS5 index is kept in sync via the same insert trigger every other + write path uses. Embedding vectors themselves are never transported. + + Also fixes two latent `cavemem export` bugs found while wiring this up: + the `Storage` constructor ran schema-init SQL (including a real + `INSERT OR IGNORE`) even when opened `{ readonly: true }`, which SQLite + rejects outright on a true read-only connection — export threw for every + user once the database existed. Readonly mode now skips schema-init, since + it's only ever used against a database an earlier writable `Storage` has + already initialized. And exporting with no database at all now exits + non-zero with a short message instead of an unhandled SQLITE_CANTOPEN. + + README documents the new command and the manual cross-device transfer + flow (export on A, stop the worker, import on B, restart). + +- 7b7662d: Add four new IDE installers (#23, #8, #38, #10) + + - **GitHub Copilot / VS Code** (`--ide copilot`, #23) — full capture+query. Hooks are + written to a cavemem-owned `~/.copilot/hooks/cavemem.json` (Copilot's payloads are + Claude-Code-compatible, so existing handlers read them without translation; Copilot + has no SessionEnd event). MCP is registered in VS Code's user-level `mcp.json` + (platform-dependent path, root key `servers`, explicit `type: "stdio"`). + - **Augment Code** (`--ide augment`, #8) — full capture+query via `~/.augment/settings.json`. + Augment's hook `command` must be a script-file path, so the installer writes thin + wrapper scripts (`.sh`, or `.cmd` on Windows) into `~/.augment/cavemem-hooks/` that + exec the CLI. Augment has no UserPromptSubmit event; the Stop hook sets + `metadata.includeConversationData` because without it the payload carries no + assistant text and turn-summary capture would silently store nothing. The CLI's + `hook run` gains an Augment payload shim mapping `conversation_id` → `session_id`, + `workspace_roots[0]` → `cwd`, and `conversation.agentTextResponse` → `turn_summary`. + - **Antigravity** (`--ide antigravity`, #38) — query-only (no hooks system). Registers + MCP in `~/.gemini/config/mcp_config.json` and warns that sessions there capture no + new observations. + - **IBM Bob** (`--ide bob`, #10) — query-only. Registers MCP in `~/.bob/mcp.json` with + the same warning. + + All four merge non-destructively into existing configs, are idempotent on re-install, + and remove only cavemem-owned entries (plus Augment's wrapper-script dir) on uninstall. + +- 51e3608: feat(config): resolve cavemem home dir via CAVEMEM_HOME / XDG (#47) + + cavemem was hardcoded to `~/.cavemem` for settings.json, data.db, the worker + pidfile/state, and the model cache. Issue #47 asked for a way to stop + polluting `$HOME` and/or relocate the data dir. `@cavemem/config` now + resolves the cavemem home directory in this order: + + 1. `CAVEMEM_HOME` env var, if set. + 2. An existing `~/.cavemem` — zero breaking change for every current install. + 3. `XDG_DATA_HOME/cavemem` whenever the var is explicitly set — on any + platform, not just Linux. Without the var, Linux uses the XDG default + `~/.local/share/cavemem`; macOS/Windows keep `~/.cavemem`. + + Non-absolute env values (no leading `/`, drive letter, or `~`) are ignored — + treated as unset, per the XDG spec. Hooks run with cwd = the project dir, so + a relative `CAVEMEM_HOME` would otherwise silently fragment the store + per-project. + + The resolution is pure `fs.existsSync` checks (no globbing) and cached per + process, so it's cheap on the hooks/worker hot path. `settings.dataDir` + still overrides the data location specifically when set explicitly in + settings.json — its default is now the resolved home dir above instead of a + hardcoded `~/.cavemem`; `.describe()` spells out the difference between the + two. To keep settings.json portable across machines (dotfile sync, restored + backups, containers), `saveSettings` omits `dataDir` from the persisted file + unless the user set it explicitly — the default is re-resolved on every + load. `saveSettings` also always writes to the resolved home now (previously + a custom `dataDir` would redirect the save to a location `loadSettings` + never reads). `cavemem doctor` / `cavemem status` already printed the + resolved `settings`/`dataDir` paths, so both surface the new resolution with + no command changes. + + `@cavemem/embedding`'s musl error message no longer hardcodes + `~/.cavemem/settings.json`, since that path is no longer always accurate. + +- 73c52c3: Enhance OpenCode integration with full-featured bridge plugin + + This changeset: + + 1. **Adds `apps/cli/src/opencode-bridge.ts`** — a bundled OpenCode plugin that maps + opencode events to cavemem hooks (`session-start`, `session-end`, + `user-prompt-submit`, `post-tool-use`, `stop`). It buffers streaming assistant + text parts and flushes complete turn summaries, injects a system-prompt + reminder about available cavemem MCP tools, and retrieves prior-session context + from the 3 most recent ended sessions via `@cavemem/core`. + + 2. **Fixes the OpenCode installer** to: + + - Write the correct `mcp` schema (`type: 'local'`, `command: [...]`, + `enabled: true`) to `~/.config/opencode/opencode.json` + - Symlink the bundled `dist/opencode-bridge.js` into + `~/.config/opencode/plugins/cavemem.js` + - Register the plugin explicitly in the `plugin` array for clarity + - Clean up the legacy `~/.opencode/config.json` entry on install and uninstall + - Honor `XDG_CONFIG_HOME` + + 3. **Ships the bridge** — `apps/cli/tsup.config.ts` gains an `opencodeBridge` + entry so `dist/opencodeBridge.js` is included in the published package. + +- b5976a5: Wire up the three privacy settings that existed in the schema but had no consumer: + + - **config/hooks (#48):** `privacy.excludePatterns` is now enforced in + `post-tool-use.ts`. A tool call whose `file_path` / `path` / `notebook_path` + field — or a path-like token embedded in its input/output (e.g. a Bash + command) — matches an `excludePatterns` glob is skipped entirely; nothing + about the excluded content is stored or logged. Glob matching (`**` across + path segments, `*` within a segment) is a hand-rolled linear segment + matcher (`matchesGlob`) in `@cavemem/config` — no regex construction, so + repeated-globstar patterns cannot backtrack pathologically, and no new + dependency. Windows backslash paths are normalized to `/` before matching. + - **compress/core (#49):** `redactSecrets` was a documented setting with no + effect. Added `redactSecrets(text)` to `@cavemem/compress`, scrubbing + Bearer tokens, OpenAI-style `sk-` keys, AWS `AKIA…` access key ids, GitHub + `gh[pousr]_` tokens, `key = value` / `key: value` assignments whose key + name ends in a recognised secret word (`api_key`, `secret`, `token`, + `password`, `passwd`, `authorization`, or a `_key`/`-key` suffix — + env-var prefixes like `STRIPE_SECRET_KEY` included), and PEM private key + blocks with `[REDACTED]` (keeping the leading key name for assignments). + `MemoryStore.addObservation` and `addSummary` now run it before + compression when `settings.privacy.redactSecrets` is true (the default), + independent of the existing `redactPrivate` (`` tag) stripping. + The schema's `redactSecrets` description previously claimed it stripped + `` tags — corrected to describe actual secret scrubbing. + - **config/hooks (#50):** Added `capture.excludeTools` / `capture.includeTools` + (both default `[]`, same glob semantics as above, e.g. `"mcp__broker__*"`). + `post-tool-use.ts` consults them before storing: `excludeTools` always wins + over `includeTools`; a non-empty `includeTools` makes capture opt-in to + just those tools. + +- 6dc2ae5: fix(worker): authenticate the local viewer HTTP API and allow disabling idle shutdown (#51, #32) + + **#51 — unauthenticated worker HTTP API.** The worker's viewer (`127.0.0.1:`) + served `/api/sessions`, `/api/sessions/:id/observations` (expanded, human-readable + bodies), and `/api/search` with no auth, reachable by any local process or by a + malicious web page doing a DNS-rebinding / CSRF fetch. `apps/worker` now applies + defense in depth on every request: + + - A Host-header allowlist rejects (403) anything but `127.0.0.1:` / + `localhost:`, closing the DNS-rebinding path. + - An Origin check rejects (403) any present Origin that isn't + `http://127.0.0.1:` / `http://localhost:` — no CORS headers are added. + - `/api/*` now requires `Authorization: Bearer ` or `X-Cavemem-Token`. The + token is generated once with `crypto.randomBytes(32)`, persisted at + `/worker-token` (mode `0600`), and reused across restarts. + + The plain HTML viewer pages (`/`, `/sessions/:id`) stay token-free for zero-friction + browsing — the worker injects `window.__CAVEMEM_TOKEN__` into the served HTML so any + client-side code can call `/api/*` without the user doing anything. `cavemem viewer` + and `cavemem status` were already file/pid-based and needed no changes; neither + `apps/cli` nor `packages/hooks` call the worker over HTTP. + + **#32 — no way to disable idle shutdown.** `embedding.idleShutdownMs` previously had + to be a positive number, so the worker always self-exited after being idle. Setting + it to `0` now disables idle shutdown entirely (the worker runs until killed); + negative values are clamped to `0`. + +- f2e2f49: Issue sweep: fix six bugs across config, installers, and embedding. + + - **config (#25):** Correct the inverted description for `search.alpha`. The + ranker computes `alpha * bm25 + (1 - alpha) * cosine`, so `1 = pure BM25` + and `0 = pure cosine`. Doc-only — no behavior change. + - **installers/claude-code (#19):** Write the cavemem MCP server entry to + `~/.claude.json` instead of `~/.claude/settings.json`. Newer Claude Code + reads MCP config from `~/.claude.json`; the previous location was silently + ignored. Hooks continue to live in `~/.claude/settings.json`. Legacy + `mcpServers.cavemem` entries in `settings.json` are migrated out on + install. + - **installers/claude-code (#12):** Stop overwriting pre-existing entries in + `hooks.SessionStart` / `PostToolUse` / etc. The installer now appends + cavemem's hook to whatever is already there and writes a one-shot + `settings.json.pre-cavemem-` backup before mutating a file with + prior hooks. Re-running install no longer duplicates cavemem entries. + - **installers/codex (#17):** Switch from `~/.codex/config.json` (which + Codex never read) to `~/.codex/config.toml` with the `[features] +codex_hooks = true` flag and an `[mcp_servers.cavemem]` table. Also write + `~/.codex/hooks.json` with `SessionStart` / `UserPromptSubmit` / + `PostToolUse` / `Stop` entries so observations are actually captured. + Adds `smol-toml` as a dependency (bundled into the CLI dist). + - **installers/opencode (#14):** Drop a generated plugin at + `~/.config/opencode/plugins/cavemem.js` that hooks into + `session.created` / `session.idle` / `tool.execute.before` / + `tool.execute.after` and forwards to `cavemem hook run …`. Previously the + installer only registered an MCP server and no hooks fired at all, so + observations were empty. Plugin is registered in `opencode.json` and + uses detached `child_process.spawn` so the IDE never blocks on a hook. + Path migrated to OpenCode's documented global config location + (`~/.config/opencode/`, honoring `XDG_CONFIG_HOME`). + - **embedding (#20):** Detect musl libc (Alpine, musl-built Node) before + importing `@xenova/transformers`. The bundled `onnxruntime-node` prebuilts + target glibc and have segfaulted on Alpine in the wild; we now throw a + clean error pointing at `embedding.provider: 'none' | 'ollama'`. + +### Patch Changes + +- d2d022e: Surface which IDEs actually capture memory vs are query-only (#58) + + Users installing cavemem into Cursor, Gemini CLI, Antigravity, or IBM Bob had no way + to tell — short of an empty database — that those integrations are MCP query-only: + hooks never fire, so no new observations are ever captured there. `cavemem status` + silently listed every installed IDE the same way. + + Each `Installer` now declares `capture: 'full' | 'partial' | 'none'` (plus an optional + `captureNotes` caveat) reflecting what its `install()` actually wires up: Claude Code, + OpenCode, Codex CLI, GitHub Copilot, and Augment Code capture observations through + hooks (OpenCode via its bundled bridge plugin rather than a `hooks.json`-style file; + Codex and Copilot have no SessionEnd event; Augment has no UserPromptSubmit event). + Cursor, Gemini CLI, Antigravity, and IBM Bob register MCP only. + + `cavemem status` reads this metadata and annotates query-only IDEs inline — + `ides: claude-code, antigravity (query-only)` — instead of listing them + indistinguishably from IDEs that actually fill the database. The README gains an IDE + capability matrix (capture / query / notes) under Install, with footnotes on + OpenCode's bridge plugin and the Copilot/Codex/Augment missing-event caveats. + + No behavioral change to any installer's `install()`/`uninstall()` — this is metadata + and read-side reporting only. + +- a52553d: fix(storage,cli): bump better-sqlite3 to ^12.0.0 for Node 26 (#37) + + Node 26 removed three V8 C++ APIs (`v8::Object::GetPrototype`, + `v8::Context::GetIsolate`, `v8::PropertyCallbackInfo::This`) that + better-sqlite3 ≤11.x relied on, so `npm install -g cavemem` fails with + `error C2039: 'GetPrototype': is not a member of 'v8::Object'` when there + is no prebuilt binary for the target Node ABI. better-sqlite3 v12 rewrites + those call sites and ships prebuilts for Node 20 through 26. The Storage + API surface used by this repo (`prepare`, `run`, `get`, `all`, `exec`, + FTS5, `bm25`, `snippet`, blob storage) is identical across v11 → v12, so + no code changes are needed. + +- 252ce18: fix(cli): ship optionalDependencies in the legacy pack-release flow + + `pack-release.mjs` rebuilt the published `package.json` from a hardcoded + allowlist that only read `dependencies`, silently dropping + `optionalDependencies` — so a `pnpm publish:release` tarball could never + install `@huggingface/transformers` and the local embedding provider was + dead on arrival for that publish path. The CI `changeset publish` path was + unaffected. Optional deps now pass through verbatim. + +- 711f5b6: fix(hooks,storage): scope session-start prior-session context to current cwd (#39) + + The `session-start` hook surfaced "Prior-session context" pulled from the + most recent N sessions across **all** projects on the machine. Opening + Claude Code in project A could inject summaries from last night's project B + session into the new kickoff, even though every session row already stores + `cwd`. Now `session-start.ts` widens the initial lookup from 4 → 20 and + filters by exact-`cwd` match before picking the top 3, falling back to the + old global behaviour only when the payload contains no `cwd` (so non-Claude + Code IDEs are unaffected). + + `Storage.searchFts(query, limit, cwd?)` also gained an optional `cwd` + parameter that joins `sessions` and restricts hits to that project; default + behaviour without `cwd` is unchanged. + +- 33824f1: fix(cli,hooks): hide Windows console window on detached worker spawn (#11) + + All four detached `child_process.spawn` sites (lifecycle `start`/`viewer`, + `worker start`, and the hooks auto-spawn path) now pass `windowsHide: true`. + Without this, `CreateProcess` on Windows pops a visible console window for + each detached child, which on some setups blocks `cavemem start` and every + hook auto-spawn. POSIX platforms ignore the option, so no behaviour change + on macOS/Linux. + +- bf71913: fix(installers): quote Windows paths in hook commands even without spaces (#41) + + `shellQuote` previously treated `\` as a bare-token character, so a default + Windows install path with no spaces was written unquoted into the hook + `command` string in `~/.claude/settings.json`. When Claude Code on Windows + runs the hook through MSYS-bash, unquoted backslashes are treated as escape + introducers and stripped, mangling the path + `C:\Users\...\node_modules\cavemem\dist\index.js` into + `CUsers...node_modulescavememdistindex.js` and the hook fails with + `MODULE_NOT_FOUND`. After this fix, any path containing a backslash gets + wrapped in double quotes; both cmd.exe and MSYS-bash preserve the + backslashes verbatim inside `"..."`. POSIX paths are unaffected. + +- 69965ea: fix(cli,installers): warn loudly when `sh` is missing on Windows (#56, #57) + + Claude Code wraps hook `command` strings in `sh -c` even on Windows. If Git + for Windows' `Git\bin` isn't on the user's `Path`, `sh` doesn't resolve, + every hook fails non-blocking, and cavemem silently stops capturing memory — + `cavemem doctor` and `cavemem status` kept reporting healthy because the + failure never reached the CLI (#56). + + `checkWindowsSh()` (new, `@cavemem/installers`) checks `sh` resolvability on + win32 with an injectable resolver for testing; it's a no-op on every other + platform. `cavemem doctor` now runs it and exits non-zero with a loud + warning + the one-time fix (`C:\Program Files\Git\bin`, or the Scoop + `usr\bin` equivalent, onto user `Path`; verify with `where.exe sh`). + `cavemem install --ide claude-code` runs the same check and prints the same + warning, but does not refuse to install — the user may fix `Path` afterward + and hooks will start working without a re-install. + + **#57 (pwsh emission):** investigated switching the emitted hook `command` + to Claude Code's newer `shell` field (`"bash"` / `"powershell"`) or its + shell-free `args` exec form. Held off: we can't verify either against every + installed Claude Code version, and the current command has no shell + metacharacters, so it already tokenizes identically whether Claude Code + runs it through `sh` or falls back to PowerShell. The #56 fix above — make + the missing-`sh` failure loud instead of silent — is the actionable part we + could ship with confidence. See the Windows note in the README and the + comment in `packages/installers/src/claude-code.ts` for the full reasoning. + +- 061473a: Migrate the local embedding provider from `@xenova/transformers@2` to `@huggingface/transformers@3`. + + `@xenova/transformers` is deprecated and pins an old `onnxruntime-web` → + `onnx-proto` → `protobufjs` chain carrying multiple published `protobufjs` + advisories, including a critical arbitrary-code-execution issue + (GHSA-xq3m-2v4x-88gg). `@huggingface/transformers@3` is the maintained + successor: same `pipeline()` / `env` API, but resolves a current, patched + `protobufjs`, clearing those advisories. (The unrelated `qs` moderate + advisory some audits report comes from `express`/`body-parser`, not this + dependency chain, and is unaffected by this change.) + + The v2 `quantized: true` pipeline flag was removed in v3; it is replaced with + `dtype: 'q8'` (int8 weights, matching the old quantized default). Embedding + output is unchanged — same model (`Xenova/all-MiniLM-L6-v2`), same 384 dims — + so existing stored vectors stay compatible and no re-embed is triggered. + ## 0.2.1 ### Patch Changes diff --git a/apps/cli/package.json b/apps/cli/package.json index 6ac79e0..401155e 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "cavemem", - "version": "0.2.1", + "version": "0.3.0", "license": "MIT", "description": "Cross-agent persistent memory with compressed storage. Plug-and-play memory for Claude Code, Gemini CLI, OpenCode, Codex, Cursor, GitHub Copilot, Augment Code, Antigravity, and IBM Bob.", "keywords": [ @@ -36,7 +36,12 @@ "cavemem": "./dist/index.js" }, "main": "./dist/index.js", - "files": ["dist", "hooks-scripts", "README.md", "LICENSE"], + "files": [ + "dist", + "hooks-scripts", + "README.md", + "LICENSE" + ], "scripts": { "build": "tsup", "dev": "tsup --watch --onSuccess \"node dist/index.js\"", diff --git a/apps/mcp-server/CHANGELOG.md b/apps/mcp-server/CHANGELOG.md index 950a00e..e68939b 100644 --- a/apps/mcp-server/CHANGELOG.md +++ b/apps/mcp-server/CHANGELOG.md @@ -1,5 +1,28 @@ # @cavemem/mcp-server +## 0.3.0 + +### Minor Changes + +- dec94ef: Opt-in web-search enrichment MCP tool (#55), phase 1. + + - **config (#55):** New `enrich` settings block: `enrich.enabled` (default `false`), `enrich.maxResults` (default 3, max 5), `enrich.timeoutMs` (default 8000). Off by default — when off, the enrich MCP tool is not registered and no network call is ever made. Picked up automatically by `cavemem config show` / `settingsDocs()`. + - **compress (#55):** New `redactSecrets(text)` export that masks common API-key shapes (OpenAI/Stripe `sk-…`, GitHub `ghp_`/`github_pat_`, AWS `AKIA…`, Slack `xox…`) as `[REDACTED]`. Gated by `settings.privacy.redactSecrets` at call sites. + - **mcp-server (#55):** New `enrich(query, note?)` tool, registered only when `enrich.enabled` is `true`. Searches DuckDuckGo's HTML endpoint (no API key), parses the top results with a hand-rolled linear-time parser, fetches each result page with a 500 KB byte cap and per-request timeout, strips it to plain text, and truncates to 2000 chars. Extracts are stored through `MemoryStore.addObservation` (compressed, privacy-redacted) under a dedicated synthetic `enrich` session, tagged `metadata: { source: 'web', url, query, note? }` for provenance; `query`/`note` are run through `redactPrivate` + `redactSecrets` before storage, and source URLs survive compression byte-for-byte. The tool returns `{ query, results: [{ title, url, extract, observation_id }], stored_ids }`. **SSRF-hardened:** every fetched URL and each manually-followed redirect hop (max 3) must be http(s) to a public host — loopback, RFC1918, link-local (`169.254/16`), and unique-local targets (including obfuscated numeric literals) are rejected without a request. Search failure returns an MCP error with nothing stored; individual blocked or dead result pages are skipped. + +### Patch Changes + +- Updated dependencies [dec94ef] +- Updated dependencies [51e3608] +- Updated dependencies [b5976a5] +- Updated dependencies [6dc2ae5] +- Updated dependencies [f2e2f49] +- Updated dependencies [061473a] + - @cavemem/compress@0.3.0 + - @cavemem/config@0.3.0 + - @cavemem/embedding@0.3.0 + - @cavemem/core@0.3.0 + ## 0.2.1 ### Patch Changes diff --git a/apps/mcp-server/package.json b/apps/mcp-server/package.json index 15bb7f5..8499d74 100644 --- a/apps/mcp-server/package.json +++ b/apps/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/mcp-server", - "version": "0.2.1", + "version": "0.3.0", "license": "MIT", "private": true, "type": "module", diff --git a/apps/worker/CHANGELOG.md b/apps/worker/CHANGELOG.md index 4522908..88b374b 100644 --- a/apps/worker/CHANGELOG.md +++ b/apps/worker/CHANGELOG.md @@ -1,5 +1,25 @@ # @cavemem/worker +## 0.3.0 + +### Patch Changes + +- Updated dependencies [8367404] +- Updated dependencies [dec94ef] +- Updated dependencies [2db720f] +- Updated dependencies [51e3608] +- Updated dependencies [a52553d] +- Updated dependencies [b5976a5] +- Updated dependencies [711f5b6] +- Updated dependencies [6dc2ae5] +- Updated dependencies [f2e2f49] +- Updated dependencies [061473a] + - @cavemem/storage@0.3.0 + - @cavemem/compress@0.3.0 + - @cavemem/config@0.3.0 + - @cavemem/embedding@0.3.0 + - @cavemem/core@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/apps/worker/package.json b/apps/worker/package.json index 561965d..01271b7 100644 --- a/apps/worker/package.json +++ b/apps/worker/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/worker", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "private": true, "type": "module", diff --git a/evals/CHANGELOG.md b/evals/CHANGELOG.md index 80422f6..a398a90 100644 --- a/evals/CHANGELOG.md +++ b/evals/CHANGELOG.md @@ -1,5 +1,13 @@ # @cavemem/evals +## 0.3.0 + +### Patch Changes + +- Updated dependencies [dec94ef] +- Updated dependencies [b5976a5] + - @cavemem/compress@0.3.0 + ## 0.2.0 ### Patch Changes diff --git a/evals/package.json b/evals/package.json index 32533bf..7efc07f 100644 --- a/evals/package.json +++ b/evals/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/evals", - "version": "0.2.0", + "version": "0.3.0", "private": true, "type": "module", "scripts": { diff --git a/packages/compress/CHANGELOG.md b/packages/compress/CHANGELOG.md index a70a95e..c6ce4cb 100644 --- a/packages/compress/CHANGELOG.md +++ b/packages/compress/CHANGELOG.md @@ -1,5 +1,45 @@ # @cavemem/compress +## 0.3.0 + +### Minor Changes + +- dec94ef: Opt-in web-search enrichment MCP tool (#55), phase 1. + + - **config (#55):** New `enrich` settings block: `enrich.enabled` (default `false`), `enrich.maxResults` (default 3, max 5), `enrich.timeoutMs` (default 8000). Off by default — when off, the enrich MCP tool is not registered and no network call is ever made. Picked up automatically by `cavemem config show` / `settingsDocs()`. + - **compress (#55):** New `redactSecrets(text)` export that masks common API-key shapes (OpenAI/Stripe `sk-…`, GitHub `ghp_`/`github_pat_`, AWS `AKIA…`, Slack `xox…`) as `[REDACTED]`. Gated by `settings.privacy.redactSecrets` at call sites. + - **mcp-server (#55):** New `enrich(query, note?)` tool, registered only when `enrich.enabled` is `true`. Searches DuckDuckGo's HTML endpoint (no API key), parses the top results with a hand-rolled linear-time parser, fetches each result page with a 500 KB byte cap and per-request timeout, strips it to plain text, and truncates to 2000 chars. Extracts are stored through `MemoryStore.addObservation` (compressed, privacy-redacted) under a dedicated synthetic `enrich` session, tagged `metadata: { source: 'web', url, query, note? }` for provenance; `query`/`note` are run through `redactPrivate` + `redactSecrets` before storage, and source URLs survive compression byte-for-byte. The tool returns `{ query, results: [{ title, url, extract, observation_id }], stored_ids }`. **SSRF-hardened:** every fetched URL and each manually-followed redirect hop (max 3) must be http(s) to a public host — loopback, RFC1918, link-local (`169.254/16`), and unique-local targets (including obfuscated numeric literals) are rejected without a request. Search failure returns an MCP error with nothing stored; individual blocked or dead result pages are skipped. + +- b5976a5: Wire up the three privacy settings that existed in the schema but had no consumer: + + - **config/hooks (#48):** `privacy.excludePatterns` is now enforced in + `post-tool-use.ts`. A tool call whose `file_path` / `path` / `notebook_path` + field — or a path-like token embedded in its input/output (e.g. a Bash + command) — matches an `excludePatterns` glob is skipped entirely; nothing + about the excluded content is stored or logged. Glob matching (`**` across + path segments, `*` within a segment) is a hand-rolled linear segment + matcher (`matchesGlob`) in `@cavemem/config` — no regex construction, so + repeated-globstar patterns cannot backtrack pathologically, and no new + dependency. Windows backslash paths are normalized to `/` before matching. + - **compress/core (#49):** `redactSecrets` was a documented setting with no + effect. Added `redactSecrets(text)` to `@cavemem/compress`, scrubbing + Bearer tokens, OpenAI-style `sk-` keys, AWS `AKIA…` access key ids, GitHub + `gh[pousr]_` tokens, `key = value` / `key: value` assignments whose key + name ends in a recognised secret word (`api_key`, `secret`, `token`, + `password`, `passwd`, `authorization`, or a `_key`/`-key` suffix — + env-var prefixes like `STRIPE_SECRET_KEY` included), and PEM private key + blocks with `[REDACTED]` (keeping the leading key name for assignments). + `MemoryStore.addObservation` and `addSummary` now run it before + compression when `settings.privacy.redactSecrets` is true (the default), + independent of the existing `redactPrivate` (`` tag) stripping. + The schema's `redactSecrets` description previously claimed it stripped + `` tags — corrected to describe actual secret scrubbing. + - **config/hooks (#50):** Added `capture.excludeTools` / `capture.includeTools` + (both default `[]`, same glob semantics as above, e.g. `"mcp__broker__*"`). + `post-tool-use.ts` consults them before storing: `excludeTools` always wins + over `includeTools`; a non-empty `includeTools` makes capture opt-in to + just those tools. + ## 0.2.0 ### Patch Changes diff --git a/packages/compress/package.json b/packages/compress/package.json index 852518d..a6d602d 100644 --- a/packages/compress/package.json +++ b/packages/compress/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/compress", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -15,7 +15,10 @@ "import": "./dist/lexicon.js" } }, - "files": ["dist", "src/lexicon.json"], + "files": [ + "dist", + "src/lexicon.json" + ], "scripts": { "build": "tsup src/index.ts src/lexicon.ts --format esm --dts --clean", "dev": "tsup src/index.ts src/lexicon.ts --format esm --dts --watch", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 59ded91..532e1c7 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,145 @@ # @cavemem/config +## 0.3.0 + +### Minor Changes + +- dec94ef: Opt-in web-search enrichment MCP tool (#55), phase 1. + + - **config (#55):** New `enrich` settings block: `enrich.enabled` (default `false`), `enrich.maxResults` (default 3, max 5), `enrich.timeoutMs` (default 8000). Off by default — when off, the enrich MCP tool is not registered and no network call is ever made. Picked up automatically by `cavemem config show` / `settingsDocs()`. + - **compress (#55):** New `redactSecrets(text)` export that masks common API-key shapes (OpenAI/Stripe `sk-…`, GitHub `ghp_`/`github_pat_`, AWS `AKIA…`, Slack `xox…`) as `[REDACTED]`. Gated by `settings.privacy.redactSecrets` at call sites. + - **mcp-server (#55):** New `enrich(query, note?)` tool, registered only when `enrich.enabled` is `true`. Searches DuckDuckGo's HTML endpoint (no API key), parses the top results with a hand-rolled linear-time parser, fetches each result page with a 500 KB byte cap and per-request timeout, strips it to plain text, and truncates to 2000 chars. Extracts are stored through `MemoryStore.addObservation` (compressed, privacy-redacted) under a dedicated synthetic `enrich` session, tagged `metadata: { source: 'web', url, query, note? }` for provenance; `query`/`note` are run through `redactPrivate` + `redactSecrets` before storage, and source URLs survive compression byte-for-byte. The tool returns `{ query, results: [{ title, url, extract, observation_id }], stored_ids }`. **SSRF-hardened:** every fetched URL and each manually-followed redirect hop (max 3) must be http(s) to a public host — loopback, RFC1918, link-local (`169.254/16`), and unique-local targets (including obfuscated numeric literals) are rejected without a request. Search failure returns an MCP error with nothing stored; individual blocked or dead result pages are skipped. + +- 51e3608: feat(config): resolve cavemem home dir via CAVEMEM_HOME / XDG (#47) + + cavemem was hardcoded to `~/.cavemem` for settings.json, data.db, the worker + pidfile/state, and the model cache. Issue #47 asked for a way to stop + polluting `$HOME` and/or relocate the data dir. `@cavemem/config` now + resolves the cavemem home directory in this order: + + 1. `CAVEMEM_HOME` env var, if set. + 2. An existing `~/.cavemem` — zero breaking change for every current install. + 3. `XDG_DATA_HOME/cavemem` whenever the var is explicitly set — on any + platform, not just Linux. Without the var, Linux uses the XDG default + `~/.local/share/cavemem`; macOS/Windows keep `~/.cavemem`. + + Non-absolute env values (no leading `/`, drive letter, or `~`) are ignored — + treated as unset, per the XDG spec. Hooks run with cwd = the project dir, so + a relative `CAVEMEM_HOME` would otherwise silently fragment the store + per-project. + + The resolution is pure `fs.existsSync` checks (no globbing) and cached per + process, so it's cheap on the hooks/worker hot path. `settings.dataDir` + still overrides the data location specifically when set explicitly in + settings.json — its default is now the resolved home dir above instead of a + hardcoded `~/.cavemem`; `.describe()` spells out the difference between the + two. To keep settings.json portable across machines (dotfile sync, restored + backups, containers), `saveSettings` omits `dataDir` from the persisted file + unless the user set it explicitly — the default is re-resolved on every + load. `saveSettings` also always writes to the resolved home now (previously + a custom `dataDir` would redirect the save to a location `loadSettings` + never reads). `cavemem doctor` / `cavemem status` already printed the + resolved `settings`/`dataDir` paths, so both surface the new resolution with + no command changes. + + `@cavemem/embedding`'s musl error message no longer hardcodes + `~/.cavemem/settings.json`, since that path is no longer always accurate. + +- b5976a5: Wire up the three privacy settings that existed in the schema but had no consumer: + + - **config/hooks (#48):** `privacy.excludePatterns` is now enforced in + `post-tool-use.ts`. A tool call whose `file_path` / `path` / `notebook_path` + field — or a path-like token embedded in its input/output (e.g. a Bash + command) — matches an `excludePatterns` glob is skipped entirely; nothing + about the excluded content is stored or logged. Glob matching (`**` across + path segments, `*` within a segment) is a hand-rolled linear segment + matcher (`matchesGlob`) in `@cavemem/config` — no regex construction, so + repeated-globstar patterns cannot backtrack pathologically, and no new + dependency. Windows backslash paths are normalized to `/` before matching. + - **compress/core (#49):** `redactSecrets` was a documented setting with no + effect. Added `redactSecrets(text)` to `@cavemem/compress`, scrubbing + Bearer tokens, OpenAI-style `sk-` keys, AWS `AKIA…` access key ids, GitHub + `gh[pousr]_` tokens, `key = value` / `key: value` assignments whose key + name ends in a recognised secret word (`api_key`, `secret`, `token`, + `password`, `passwd`, `authorization`, or a `_key`/`-key` suffix — + env-var prefixes like `STRIPE_SECRET_KEY` included), and PEM private key + blocks with `[REDACTED]` (keeping the leading key name for assignments). + `MemoryStore.addObservation` and `addSummary` now run it before + compression when `settings.privacy.redactSecrets` is true (the default), + independent of the existing `redactPrivate` (`` tag) stripping. + The schema's `redactSecrets` description previously claimed it stripped + `` tags — corrected to describe actual secret scrubbing. + - **config/hooks (#50):** Added `capture.excludeTools` / `capture.includeTools` + (both default `[]`, same glob semantics as above, e.g. `"mcp__broker__*"`). + `post-tool-use.ts` consults them before storing: `excludeTools` always wins + over `includeTools`; a non-empty `includeTools` makes capture opt-in to + just those tools. + +### Patch Changes + +- 6dc2ae5: fix(worker): authenticate the local viewer HTTP API and allow disabling idle shutdown (#51, #32) + + **#51 — unauthenticated worker HTTP API.** The worker's viewer (`127.0.0.1:`) + served `/api/sessions`, `/api/sessions/:id/observations` (expanded, human-readable + bodies), and `/api/search` with no auth, reachable by any local process or by a + malicious web page doing a DNS-rebinding / CSRF fetch. `apps/worker` now applies + defense in depth on every request: + + - A Host-header allowlist rejects (403) anything but `127.0.0.1:` / + `localhost:`, closing the DNS-rebinding path. + - An Origin check rejects (403) any present Origin that isn't + `http://127.0.0.1:` / `http://localhost:` — no CORS headers are added. + - `/api/*` now requires `Authorization: Bearer ` or `X-Cavemem-Token`. The + token is generated once with `crypto.randomBytes(32)`, persisted at + `/worker-token` (mode `0600`), and reused across restarts. + + The plain HTML viewer pages (`/`, `/sessions/:id`) stay token-free for zero-friction + browsing — the worker injects `window.__CAVEMEM_TOKEN__` into the served HTML so any + client-side code can call `/api/*` without the user doing anything. `cavemem viewer` + and `cavemem status` were already file/pid-based and needed no changes; neither + `apps/cli` nor `packages/hooks` call the worker over HTTP. + + **#32 — no way to disable idle shutdown.** `embedding.idleShutdownMs` previously had + to be a positive number, so the worker always self-exited after being idle. Setting + it to `0` now disables idle shutdown entirely (the worker runs until killed); + negative values are clamped to `0`. + +- f2e2f49: Issue sweep: fix six bugs across config, installers, and embedding. + + - **config (#25):** Correct the inverted description for `search.alpha`. The + ranker computes `alpha * bm25 + (1 - alpha) * cosine`, so `1 = pure BM25` + and `0 = pure cosine`. Doc-only — no behavior change. + - **installers/claude-code (#19):** Write the cavemem MCP server entry to + `~/.claude.json` instead of `~/.claude/settings.json`. Newer Claude Code + reads MCP config from `~/.claude.json`; the previous location was silently + ignored. Hooks continue to live in `~/.claude/settings.json`. Legacy + `mcpServers.cavemem` entries in `settings.json` are migrated out on + install. + - **installers/claude-code (#12):** Stop overwriting pre-existing entries in + `hooks.SessionStart` / `PostToolUse` / etc. The installer now appends + cavemem's hook to whatever is already there and writes a one-shot + `settings.json.pre-cavemem-` backup before mutating a file with + prior hooks. Re-running install no longer duplicates cavemem entries. + - **installers/codex (#17):** Switch from `~/.codex/config.json` (which + Codex never read) to `~/.codex/config.toml` with the `[features] +codex_hooks = true` flag and an `[mcp_servers.cavemem]` table. Also write + `~/.codex/hooks.json` with `SessionStart` / `UserPromptSubmit` / + `PostToolUse` / `Stop` entries so observations are actually captured. + Adds `smol-toml` as a dependency (bundled into the CLI dist). + - **installers/opencode (#14):** Drop a generated plugin at + `~/.config/opencode/plugins/cavemem.js` that hooks into + `session.created` / `session.idle` / `tool.execute.before` / + `tool.execute.after` and forwards to `cavemem hook run …`. Previously the + installer only registered an MCP server and no hooks fired at all, so + observations were empty. Plugin is registered in `opencode.json` and + uses detached `child_process.spawn` so the IDE never blocks on a hook. + Path migrated to OpenCode's documented global config location + (`~/.config/opencode/`, honoring `XDG_CONFIG_HOME`). + - **embedding (#20):** Detect musl libc (Alpine, musl-built Node) before + importing `@xenova/transformers`. The bundled `onnxruntime-node` prebuilts + target glibc and have segfaulted on Alpine in the wild; we now throw a + clean error pointing at `embedding.provider: 'none' | 'ollama'`. + ## 0.2.0 ### Minor Changes diff --git a/packages/config/package.json b/packages/config/package.json index 967ca49..580c59e 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/config", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -11,7 +11,9 @@ "import": "./dist/index.js" } }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsup src/index.ts --format esm --dts --clean", "dev": "tsup src/index.ts --format esm --dts --watch", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 934ec5a..5950c0a 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,52 @@ # @cavemem/core +## 0.3.0 + +### Patch Changes + +- b5976a5: Wire up the three privacy settings that existed in the schema but had no consumer: + + - **config/hooks (#48):** `privacy.excludePatterns` is now enforced in + `post-tool-use.ts`. A tool call whose `file_path` / `path` / `notebook_path` + field — or a path-like token embedded in its input/output (e.g. a Bash + command) — matches an `excludePatterns` glob is skipped entirely; nothing + about the excluded content is stored or logged. Glob matching (`**` across + path segments, `*` within a segment) is a hand-rolled linear segment + matcher (`matchesGlob`) in `@cavemem/config` — no regex construction, so + repeated-globstar patterns cannot backtrack pathologically, and no new + dependency. Windows backslash paths are normalized to `/` before matching. + - **compress/core (#49):** `redactSecrets` was a documented setting with no + effect. Added `redactSecrets(text)` to `@cavemem/compress`, scrubbing + Bearer tokens, OpenAI-style `sk-` keys, AWS `AKIA…` access key ids, GitHub + `gh[pousr]_` tokens, `key = value` / `key: value` assignments whose key + name ends in a recognised secret word (`api_key`, `secret`, `token`, + `password`, `passwd`, `authorization`, or a `_key`/`-key` suffix — + env-var prefixes like `STRIPE_SECRET_KEY` included), and PEM private key + blocks with `[REDACTED]` (keeping the leading key name for assignments). + `MemoryStore.addObservation` and `addSummary` now run it before + compression when `settings.privacy.redactSecrets` is true (the default), + independent of the existing `redactPrivate` (`` tag) stripping. + The schema's `redactSecrets` description previously claimed it stripped + `` tags — corrected to describe actual secret scrubbing. + - **config/hooks (#50):** Added `capture.excludeTools` / `capture.includeTools` + (both default `[]`, same glob semantics as above, e.g. `"mcp__broker__*"`). + `post-tool-use.ts` consults them before storing: `excludeTools` always wins + over `includeTools`; a non-empty `includeTools` makes capture opt-in to + just those tools. + +- Updated dependencies [8367404] +- Updated dependencies [dec94ef] +- Updated dependencies [2db720f] +- Updated dependencies [51e3608] +- Updated dependencies [a52553d] +- Updated dependencies [b5976a5] +- Updated dependencies [711f5b6] +- Updated dependencies [6dc2ae5] +- Updated dependencies [f2e2f49] + - @cavemem/storage@0.3.0 + - @cavemem/compress@0.3.0 + - @cavemem/config@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index b2027f7..a056bdc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/core", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -11,7 +11,9 @@ "import": "./dist/index.js" } }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsup src/index.ts --format esm --dts --clean", "dev": "tsup src/index.ts --format esm --dts --watch", diff --git a/packages/embedding/CHANGELOG.md b/packages/embedding/CHANGELOG.md index 70d4259..addc5fb 100644 --- a/packages/embedding/CHANGELOG.md +++ b/packages/embedding/CHANGELOG.md @@ -1,5 +1,103 @@ # @cavemem/embedding +## 0.3.0 + +### Patch Changes + +- 51e3608: feat(config): resolve cavemem home dir via CAVEMEM_HOME / XDG (#47) + + cavemem was hardcoded to `~/.cavemem` for settings.json, data.db, the worker + pidfile/state, and the model cache. Issue #47 asked for a way to stop + polluting `$HOME` and/or relocate the data dir. `@cavemem/config` now + resolves the cavemem home directory in this order: + + 1. `CAVEMEM_HOME` env var, if set. + 2. An existing `~/.cavemem` — zero breaking change for every current install. + 3. `XDG_DATA_HOME/cavemem` whenever the var is explicitly set — on any + platform, not just Linux. Without the var, Linux uses the XDG default + `~/.local/share/cavemem`; macOS/Windows keep `~/.cavemem`. + + Non-absolute env values (no leading `/`, drive letter, or `~`) are ignored — + treated as unset, per the XDG spec. Hooks run with cwd = the project dir, so + a relative `CAVEMEM_HOME` would otherwise silently fragment the store + per-project. + + The resolution is pure `fs.existsSync` checks (no globbing) and cached per + process, so it's cheap on the hooks/worker hot path. `settings.dataDir` + still overrides the data location specifically when set explicitly in + settings.json — its default is now the resolved home dir above instead of a + hardcoded `~/.cavemem`; `.describe()` spells out the difference between the + two. To keep settings.json portable across machines (dotfile sync, restored + backups, containers), `saveSettings` omits `dataDir` from the persisted file + unless the user set it explicitly — the default is re-resolved on every + load. `saveSettings` also always writes to the resolved home now (previously + a custom `dataDir` would redirect the save to a location `loadSettings` + never reads). `cavemem doctor` / `cavemem status` already printed the + resolved `settings`/`dataDir` paths, so both surface the new resolution with + no command changes. + + `@cavemem/embedding`'s musl error message no longer hardcodes + `~/.cavemem/settings.json`, since that path is no longer always accurate. + +- f2e2f49: Issue sweep: fix six bugs across config, installers, and embedding. + + - **config (#25):** Correct the inverted description for `search.alpha`. The + ranker computes `alpha * bm25 + (1 - alpha) * cosine`, so `1 = pure BM25` + and `0 = pure cosine`. Doc-only — no behavior change. + - **installers/claude-code (#19):** Write the cavemem MCP server entry to + `~/.claude.json` instead of `~/.claude/settings.json`. Newer Claude Code + reads MCP config from `~/.claude.json`; the previous location was silently + ignored. Hooks continue to live in `~/.claude/settings.json`. Legacy + `mcpServers.cavemem` entries in `settings.json` are migrated out on + install. + - **installers/claude-code (#12):** Stop overwriting pre-existing entries in + `hooks.SessionStart` / `PostToolUse` / etc. The installer now appends + cavemem's hook to whatever is already there and writes a one-shot + `settings.json.pre-cavemem-` backup before mutating a file with + prior hooks. Re-running install no longer duplicates cavemem entries. + - **installers/codex (#17):** Switch from `~/.codex/config.json` (which + Codex never read) to `~/.codex/config.toml` with the `[features] +codex_hooks = true` flag and an `[mcp_servers.cavemem]` table. Also write + `~/.codex/hooks.json` with `SessionStart` / `UserPromptSubmit` / + `PostToolUse` / `Stop` entries so observations are actually captured. + Adds `smol-toml` as a dependency (bundled into the CLI dist). + - **installers/opencode (#14):** Drop a generated plugin at + `~/.config/opencode/plugins/cavemem.js` that hooks into + `session.created` / `session.idle` / `tool.execute.before` / + `tool.execute.after` and forwards to `cavemem hook run …`. Previously the + installer only registered an MCP server and no hooks fired at all, so + observations were empty. Plugin is registered in `opencode.json` and + uses detached `child_process.spawn` so the IDE never blocks on a hook. + Path migrated to OpenCode's documented global config location + (`~/.config/opencode/`, honoring `XDG_CONFIG_HOME`). + - **embedding (#20):** Detect musl libc (Alpine, musl-built Node) before + importing `@xenova/transformers`. The bundled `onnxruntime-node` prebuilts + target glibc and have segfaulted on Alpine in the wild; we now throw a + clean error pointing at `embedding.provider: 'none' | 'ollama'`. + +- 061473a: Migrate the local embedding provider from `@xenova/transformers@2` to `@huggingface/transformers@3`. + + `@xenova/transformers` is deprecated and pins an old `onnxruntime-web` → + `onnx-proto` → `protobufjs` chain carrying multiple published `protobufjs` + advisories, including a critical arbitrary-code-execution issue + (GHSA-xq3m-2v4x-88gg). `@huggingface/transformers@3` is the maintained + successor: same `pipeline()` / `env` API, but resolves a current, patched + `protobufjs`, clearing those advisories. (The unrelated `qs` moderate + advisory some audits report comes from `express`/`body-parser`, not this + dependency chain, and is unaffected by this change.) + + The v2 `quantized: true` pipeline flag was removed in v3; it is replaced with + `dtype: 'q8'` (int8 weights, matching the old quantized default). Embedding + output is unchanged — same model (`Xenova/all-MiniLM-L6-v2`), same 384 dims — + so existing stored vectors stay compatible and no re-embed is triggered. + +- Updated dependencies [dec94ef] +- Updated dependencies [51e3608] +- Updated dependencies [b5976a5] +- Updated dependencies [6dc2ae5] +- Updated dependencies [f2e2f49] + - @cavemem/config@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/packages/embedding/package.json b/packages/embedding/package.json index 587865e..8e95b1b 100644 --- a/packages/embedding/package.json +++ b/packages/embedding/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/embedding", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -11,7 +11,9 @@ "import": "./dist/index.js" } }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsup", "dev": "tsup --watch", diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 7c7c720..c503332 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -1,5 +1,71 @@ # @cavemem/hooks +## 0.3.0 + +### Patch Changes + +- b5976a5: Wire up the three privacy settings that existed in the schema but had no consumer: + + - **config/hooks (#48):** `privacy.excludePatterns` is now enforced in + `post-tool-use.ts`. A tool call whose `file_path` / `path` / `notebook_path` + field — or a path-like token embedded in its input/output (e.g. a Bash + command) — matches an `excludePatterns` glob is skipped entirely; nothing + about the excluded content is stored or logged. Glob matching (`**` across + path segments, `*` within a segment) is a hand-rolled linear segment + matcher (`matchesGlob`) in `@cavemem/config` — no regex construction, so + repeated-globstar patterns cannot backtrack pathologically, and no new + dependency. Windows backslash paths are normalized to `/` before matching. + - **compress/core (#49):** `redactSecrets` was a documented setting with no + effect. Added `redactSecrets(text)` to `@cavemem/compress`, scrubbing + Bearer tokens, OpenAI-style `sk-` keys, AWS `AKIA…` access key ids, GitHub + `gh[pousr]_` tokens, `key = value` / `key: value` assignments whose key + name ends in a recognised secret word (`api_key`, `secret`, `token`, + `password`, `passwd`, `authorization`, or a `_key`/`-key` suffix — + env-var prefixes like `STRIPE_SECRET_KEY` included), and PEM private key + blocks with `[REDACTED]` (keeping the leading key name for assignments). + `MemoryStore.addObservation` and `addSummary` now run it before + compression when `settings.privacy.redactSecrets` is true (the default), + independent of the existing `redactPrivate` (`` tag) stripping. + The schema's `redactSecrets` description previously claimed it stripped + `` tags — corrected to describe actual secret scrubbing. + - **config/hooks (#50):** Added `capture.excludeTools` / `capture.includeTools` + (both default `[]`, same glob semantics as above, e.g. `"mcp__broker__*"`). + `post-tool-use.ts` consults them before storing: `excludeTools` always wins + over `includeTools`; a non-empty `includeTools` makes capture opt-in to + just those tools. + +- 711f5b6: fix(hooks,storage): scope session-start prior-session context to current cwd (#39) + + The `session-start` hook surfaced "Prior-session context" pulled from the + most recent N sessions across **all** projects on the machine. Opening + Claude Code in project A could inject summaries from last night's project B + session into the new kickoff, even though every session row already stores + `cwd`. Now `session-start.ts` widens the initial lookup from 4 → 20 and + filters by exact-`cwd` match before picking the top 3, falling back to the + old global behaviour only when the payload contains no `cwd` (so non-Claude + Code IDEs are unaffected). + + `Storage.searchFts(query, limit, cwd?)` also gained an optional `cwd` + parameter that joins `sessions` and restricts hits to that project; default + behaviour without `cwd` is unchanged. + +- 33824f1: fix(cli,hooks): hide Windows console window on detached worker spawn (#11) + + All four detached `child_process.spawn` sites (lifecycle `start`/`viewer`, + `worker start`, and the hooks auto-spawn path) now pass `windowsHide: true`. + Without this, `CreateProcess` on Windows pops a visible console window for + each detached child, which on some setups blocks `cavemem start` and every + hook auto-spawn. POSIX platforms ignore the option, so no behaviour change + on macOS/Linux. + +- Updated dependencies [dec94ef] +- Updated dependencies [51e3608] +- Updated dependencies [b5976a5] +- Updated dependencies [6dc2ae5] +- Updated dependencies [f2e2f49] + - @cavemem/config@0.3.0 + - @cavemem/core@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 7f27481..43b8caf 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/hooks", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -11,7 +11,9 @@ "import": "./dist/index.js" } }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsup src/index.ts --format esm --dts --clean", "dev": "tsup src/index.ts --format esm --dts --watch", diff --git a/packages/installers/CHANGELOG.md b/packages/installers/CHANGELOG.md index cdef166..eb20c34 100644 --- a/packages/installers/CHANGELOG.md +++ b/packages/installers/CHANGELOG.md @@ -1,5 +1,165 @@ # @cavemem/installers +## 0.3.0 + +### Minor Changes + +- 7b7662d: Add four new IDE installers (#23, #8, #38, #10) + + - **GitHub Copilot / VS Code** (`--ide copilot`, #23) — full capture+query. Hooks are + written to a cavemem-owned `~/.copilot/hooks/cavemem.json` (Copilot's payloads are + Claude-Code-compatible, so existing handlers read them without translation; Copilot + has no SessionEnd event). MCP is registered in VS Code's user-level `mcp.json` + (platform-dependent path, root key `servers`, explicit `type: "stdio"`). + - **Augment Code** (`--ide augment`, #8) — full capture+query via `~/.augment/settings.json`. + Augment's hook `command` must be a script-file path, so the installer writes thin + wrapper scripts (`.sh`, or `.cmd` on Windows) into `~/.augment/cavemem-hooks/` that + exec the CLI. Augment has no UserPromptSubmit event; the Stop hook sets + `metadata.includeConversationData` because without it the payload carries no + assistant text and turn-summary capture would silently store nothing. The CLI's + `hook run` gains an Augment payload shim mapping `conversation_id` → `session_id`, + `workspace_roots[0]` → `cwd`, and `conversation.agentTextResponse` → `turn_summary`. + - **Antigravity** (`--ide antigravity`, #38) — query-only (no hooks system). Registers + MCP in `~/.gemini/config/mcp_config.json` and warns that sessions there capture no + new observations. + - **IBM Bob** (`--ide bob`, #10) — query-only. Registers MCP in `~/.bob/mcp.json` with + the same warning. + + All four merge non-destructively into existing configs, are idempotent on re-install, + and remove only cavemem-owned entries (plus Augment's wrapper-script dir) on uninstall. + +- 73c52c3: Enhance OpenCode integration with full-featured bridge plugin + + This changeset: + + 1. **Adds `apps/cli/src/opencode-bridge.ts`** — a bundled OpenCode plugin that maps + opencode events to cavemem hooks (`session-start`, `session-end`, + `user-prompt-submit`, `post-tool-use`, `stop`). It buffers streaming assistant + text parts and flushes complete turn summaries, injects a system-prompt + reminder about available cavemem MCP tools, and retrieves prior-session context + from the 3 most recent ended sessions via `@cavemem/core`. + + 2. **Fixes the OpenCode installer** to: + + - Write the correct `mcp` schema (`type: 'local'`, `command: [...]`, + `enabled: true`) to `~/.config/opencode/opencode.json` + - Symlink the bundled `dist/opencode-bridge.js` into + `~/.config/opencode/plugins/cavemem.js` + - Register the plugin explicitly in the `plugin` array for clarity + - Clean up the legacy `~/.opencode/config.json` entry on install and uninstall + - Honor `XDG_CONFIG_HOME` + + 3. **Ships the bridge** — `apps/cli/tsup.config.ts` gains an `opencodeBridge` + entry so `dist/opencodeBridge.js` is included in the published package. + +- f2e2f49: Issue sweep: fix six bugs across config, installers, and embedding. + + - **config (#25):** Correct the inverted description for `search.alpha`. The + ranker computes `alpha * bm25 + (1 - alpha) * cosine`, so `1 = pure BM25` + and `0 = pure cosine`. Doc-only — no behavior change. + - **installers/claude-code (#19):** Write the cavemem MCP server entry to + `~/.claude.json` instead of `~/.claude/settings.json`. Newer Claude Code + reads MCP config from `~/.claude.json`; the previous location was silently + ignored. Hooks continue to live in `~/.claude/settings.json`. Legacy + `mcpServers.cavemem` entries in `settings.json` are migrated out on + install. + - **installers/claude-code (#12):** Stop overwriting pre-existing entries in + `hooks.SessionStart` / `PostToolUse` / etc. The installer now appends + cavemem's hook to whatever is already there and writes a one-shot + `settings.json.pre-cavemem-` backup before mutating a file with + prior hooks. Re-running install no longer duplicates cavemem entries. + - **installers/codex (#17):** Switch from `~/.codex/config.json` (which + Codex never read) to `~/.codex/config.toml` with the `[features] +codex_hooks = true` flag and an `[mcp_servers.cavemem]` table. Also write + `~/.codex/hooks.json` with `SessionStart` / `UserPromptSubmit` / + `PostToolUse` / `Stop` entries so observations are actually captured. + Adds `smol-toml` as a dependency (bundled into the CLI dist). + - **installers/opencode (#14):** Drop a generated plugin at + `~/.config/opencode/plugins/cavemem.js` that hooks into + `session.created` / `session.idle` / `tool.execute.before` / + `tool.execute.after` and forwards to `cavemem hook run …`. Previously the + installer only registered an MCP server and no hooks fired at all, so + observations were empty. Plugin is registered in `opencode.json` and + uses detached `child_process.spawn` so the IDE never blocks on a hook. + Path migrated to OpenCode's documented global config location + (`~/.config/opencode/`, honoring `XDG_CONFIG_HOME`). + - **embedding (#20):** Detect musl libc (Alpine, musl-built Node) before + importing `@xenova/transformers`. The bundled `onnxruntime-node` prebuilts + target glibc and have segfaulted on Alpine in the wild; we now throw a + clean error pointing at `embedding.provider: 'none' | 'ollama'`. + +### Patch Changes + +- d2d022e: Surface which IDEs actually capture memory vs are query-only (#58) + + Users installing cavemem into Cursor, Gemini CLI, Antigravity, or IBM Bob had no way + to tell — short of an empty database — that those integrations are MCP query-only: + hooks never fire, so no new observations are ever captured there. `cavemem status` + silently listed every installed IDE the same way. + + Each `Installer` now declares `capture: 'full' | 'partial' | 'none'` (plus an optional + `captureNotes` caveat) reflecting what its `install()` actually wires up: Claude Code, + OpenCode, Codex CLI, GitHub Copilot, and Augment Code capture observations through + hooks (OpenCode via its bundled bridge plugin rather than a `hooks.json`-style file; + Codex and Copilot have no SessionEnd event; Augment has no UserPromptSubmit event). + Cursor, Gemini CLI, Antigravity, and IBM Bob register MCP only. + + `cavemem status` reads this metadata and annotates query-only IDEs inline — + `ides: claude-code, antigravity (query-only)` — instead of listing them + indistinguishably from IDEs that actually fill the database. The README gains an IDE + capability matrix (capture / query / notes) under Install, with footnotes on + OpenCode's bridge plugin and the Copilot/Codex/Augment missing-event caveats. + + No behavioral change to any installer's `install()`/`uninstall()` — this is metadata + and read-side reporting only. + +- bf71913: fix(installers): quote Windows paths in hook commands even without spaces (#41) + + `shellQuote` previously treated `\` as a bare-token character, so a default + Windows install path with no spaces was written unquoted into the hook + `command` string in `~/.claude/settings.json`. When Claude Code on Windows + runs the hook through MSYS-bash, unquoted backslashes are treated as escape + introducers and stripped, mangling the path + `C:\Users\...\node_modules\cavemem\dist\index.js` into + `CUsers...node_modulescavememdistindex.js` and the hook fails with + `MODULE_NOT_FOUND`. After this fix, any path containing a backslash gets + wrapped in double quotes; both cmd.exe and MSYS-bash preserve the + backslashes verbatim inside `"..."`. POSIX paths are unaffected. + +- 69965ea: fix(cli,installers): warn loudly when `sh` is missing on Windows (#56, #57) + + Claude Code wraps hook `command` strings in `sh -c` even on Windows. If Git + for Windows' `Git\bin` isn't on the user's `Path`, `sh` doesn't resolve, + every hook fails non-blocking, and cavemem silently stops capturing memory — + `cavemem doctor` and `cavemem status` kept reporting healthy because the + failure never reached the CLI (#56). + + `checkWindowsSh()` (new, `@cavemem/installers`) checks `sh` resolvability on + win32 with an injectable resolver for testing; it's a no-op on every other + platform. `cavemem doctor` now runs it and exits non-zero with a loud + warning + the one-time fix (`C:\Program Files\Git\bin`, or the Scoop + `usr\bin` equivalent, onto user `Path`; verify with `where.exe sh`). + `cavemem install --ide claude-code` runs the same check and prints the same + warning, but does not refuse to install — the user may fix `Path` afterward + and hooks will start working without a re-install. + + **#57 (pwsh emission):** investigated switching the emitted hook `command` + to Claude Code's newer `shell` field (`"bash"` / `"powershell"`) or its + shell-free `args` exec form. Held off: we can't verify either against every + installed Claude Code version, and the current command has no shell + metacharacters, so it already tokenizes identically whether Claude Code + runs it through `sh` or falls back to PowerShell. The #56 fix above — make + the missing-`sh` failure loud instead of silent — is the actionable part we + could ship with confidence. See the Windows note in the README and the + comment in `packages/installers/src/claude-code.ts` for the full reasoning. + +- Updated dependencies [dec94ef] +- Updated dependencies [51e3608] +- Updated dependencies [b5976a5] +- Updated dependencies [6dc2ae5] +- Updated dependencies [f2e2f49] + - @cavemem/config@0.3.0 + ## 0.2.0 ### Patch Changes diff --git a/packages/installers/package.json b/packages/installers/package.json index d6e2458..d20a9c1 100644 --- a/packages/installers/package.json +++ b/packages/installers/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/installers", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -11,7 +11,9 @@ "import": "./dist/index.js" } }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsup src/index.ts --format esm --dts --clean", "dev": "tsup src/index.ts --format esm --dts --watch", diff --git a/packages/storage/CHANGELOG.md b/packages/storage/CHANGELOG.md index f0da5db..1db0753 100644 --- a/packages/storage/CHANGELOG.md +++ b/packages/storage/CHANGELOG.md @@ -1,5 +1,112 @@ # @cavemem/storage +## 0.3.0 + +### Minor Changes + +- 8367404: Add bun:sqlite backend so cavemem runs natively under Bun without the better-sqlite3 native addon. + + When the process is Bun, `openDb` loads `bun:sqlite` via `createRequire` at runtime and + wraps it in the same `DbHandle` interface used by the better-sqlite3 path. The adapter + normalises the two API differences: `get()` returning `null` vs `undefined` on no-match, + and the absence of `{ readonly: false }` support on bun:sqlite. All SQL — FTS5, bm25, + snippet, binary blob storage — is identical across both backends. No package.json change + is needed; `bun:sqlite` is a Bun built-in. Node users are unaffected. + +- 2db720f: feat(cli,storage): add `cavemem import` to round-trip `cavemem export` output (#33) + + `cavemem export` had no matching `import`, so cross-device transfer (export + JSONL on machine A, load it on machine B) was a manual JSON-massaging + exercise. `cavemem import [--dry-run]` now round-trips exactly + what export emits — `session` and `observation` records; export does not + emit summaries, so import doesn't need to either. + + Sessions merge by id: a session whose id already exists in the target + database is skipped and counted. Observation ids are per-machine + AUTOINCREMENT values with no cross-device coordination — machine A's id=42 + and machine B's id=42 are routinely different observations — so the new + `Storage.importObservation` treats the exported id as a preference, not an + identity: an exact (session_id, ts, content) duplicate anywhere in the + table is skipped; a free id is used as-is; an id occupied by a _different_ + observation gets a fresh AUTOINCREMENT id and is counted as "reassigned". + Nothing is ever overwritten, and re-running the same import is a no-op even + after a previous run reassigned ids. The summary line reports + imported/skipped/reassigned counts. Sessions referenced by observations are + imported first (or a minimal session row is synthesized as a defensive + fallback), so the `observations.session_id` foreign key never rejects a + valid row. + + The whole file is validated up front — a malformed line aborts with a + clear, line-numbered error and writes nothing — and the actual writes run + inside one SQLite transaction (new `Storage.transaction`, implemented in + the DbHandle plumbing for both the better-sqlite3 and bun:sqlite backends), + so a failure partway through also leaves the database untouched. + `--dry-run` runs the identical write path and rolls back at the end; when + the target database doesn't exist yet it runs against an in-memory + database instead, so not even an empty `data.db` is left behind. + + Exported `content` already passed through `@cavemem/compress` on the + source machine, so import writes it back verbatim (no re-compression) + rather than through `MemoryStore.addObservation` — recompressing on the + target could change the bytes if its `compression.intensity` setting + differs from the source's, which would break byte-identical + export → import → export round-trips. `Storage.createSession` now returns + whether the row was inserted or already existed, which import uses for its + skip counts. + + Imported observations get no `embeddings` row, so they're picked up by the + worker's embedding backfill loop the same way any newly-added observation + is; the FTS5 index is kept in sync via the same insert trigger every other + write path uses. Embedding vectors themselves are never transported. + + Also fixes two latent `cavemem export` bugs found while wiring this up: + the `Storage` constructor ran schema-init SQL (including a real + `INSERT OR IGNORE`) even when opened `{ readonly: true }`, which SQLite + rejects outright on a true read-only connection — export threw for every + user once the database existed. Readonly mode now skips schema-init, since + it's only ever used against a database an earlier writable `Storage` has + already initialized. And exporting with no database at all now exits + non-zero with a short message instead of an unhandled SQLITE_CANTOPEN. + + README documents the new command and the manual cross-device transfer + flow (export on A, stop the worker, import on B, restart). + +### Patch Changes + +- a52553d: fix(storage,cli): bump better-sqlite3 to ^12.0.0 for Node 26 (#37) + + Node 26 removed three V8 C++ APIs (`v8::Object::GetPrototype`, + `v8::Context::GetIsolate`, `v8::PropertyCallbackInfo::This`) that + better-sqlite3 ≤11.x relied on, so `npm install -g cavemem` fails with + `error C2039: 'GetPrototype': is not a member of 'v8::Object'` when there + is no prebuilt binary for the target Node ABI. better-sqlite3 v12 rewrites + those call sites and ships prebuilts for Node 20 through 26. The Storage + API surface used by this repo (`prepare`, `run`, `get`, `all`, `exec`, + FTS5, `bm25`, `snippet`, blob storage) is identical across v11 → v12, so + no code changes are needed. + +- 711f5b6: fix(hooks,storage): scope session-start prior-session context to current cwd (#39) + + The `session-start` hook surfaced "Prior-session context" pulled from the + most recent N sessions across **all** projects on the machine. Opening + Claude Code in project A could inject summaries from last night's project B + session into the new kickoff, even though every session row already stores + `cwd`. Now `session-start.ts` widens the initial lookup from 4 → 20 and + filters by exact-`cwd` match before picking the top 3, falling back to the + old global behaviour only when the payload contains no `cwd` (so non-Claude + Code IDEs are unaffected). + + `Storage.searchFts(query, limit, cwd?)` also gained an optional `cwd` + parameter that joins `sessions` and restricts hits to that project; default + behaviour without `cwd` is unchanged. + +- Updated dependencies [dec94ef] +- Updated dependencies [51e3608] +- Updated dependencies [b5976a5] +- Updated dependencies [6dc2ae5] +- Updated dependencies [f2e2f49] + - @cavemem/config@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/packages/storage/package.json b/packages/storage/package.json index ce945f4..a18a2f4 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -1,6 +1,6 @@ { "name": "@cavemem/storage", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "type": "module", "main": "./dist/index.js", @@ -11,7 +11,9 @@ "import": "./dist/index.js" } }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsup src/index.ts --format esm --dts --clean", "dev": "tsup src/index.ts --format esm --dts --watch",