diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 05b63a2..317edd6 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "b12-memory", "source": "./", "description": "Persistent cross-session memory with automatic extraction, semantic search, spaced repetition, and contradiction detection. Supports Claude Code, Codex, Gemini, VS Code, Cursor, and 5+ more platforms.", - "version": "11.83.0", + "version": "11.83.1", "category": "memory", "tags": [ "memory", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b6ea4c9..0e3bb9a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "b12-memory", - "version": "11.83.0", + "version": "11.83.1", "description": "Persistent cross-session memory system with automatic extraction, semantic search, and spaced repetition. Works across 10+ AI coding platforms.", "author": { "name": "Doruk Ardahan", diff --git a/CHANGELOG.md b/CHANGELOG.md index 7538e3f..193be23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,31 +2,31 @@ All notable changes to B12 are documented in this file. -## [Unreleased] +## [v11.83.1] — 2026-10-21 -### Changed +Hook output reliability, untyped memories shown as general, and an installer that keeps other tools' hooks. -- Config / install help form now lists Google Antigravity alongside the other documented hosts. +### Added +- `.b12-ignore`: an empty file in a Claude Code config folder makes every install mode skip that folder. -### Fixed +### Changed +- Graph expansion no longer pulls session summaries or progress rows in as related memories, matching the primary retrieval paths. +- The stdio proxy's default reconnect budget (`B12_MCP_RECONNECT_BUDGET`) is now 45 seconds, so open sessions survive a daemon restart that needs a second launchd spawn. +- The config and install help form lists Google Antigravity alongside the other documented hosts. -- Running the test suite on a machine with B12 installed no longer touches the live install. Installer tests used to rebind the live `com.b12.mcp.daemon` launchd job to a temporary plist and stop the live embed daemon, and a test session wrote a summary into the live database. A root `conftest.py` now isolates `HOME`, the daemon sockets and `launchctl`/`crontab` before any test module loads. -- `B12_EMBED_RUNTIME_DIR` and `B12_MCP_DAEMON_SOCK` are now honored by every hook and script. About 30 call sites, plus the installer's socket wait and uninstall, still used hardcoded `/tmp/b12-*` paths, so a custom runtime dir split clients from the daemon. The installer now also writes custom values into the launchd job. Defaults are unchanged. -- `install.sh` no longer deletes hooks that other tools added to a setup's `settings.json`. It replaced the whole `hooks` block with the B12 template; it now swaps only B12's own handlers (current `~/.B12/hooks/` and legacy `~/.claude*/hooks/memory-*` paths) and keeps the rest. -- `install.sh --all` and the skill install no longer write into `~/.claude-*` folders that are not Claude Code setups (for example a transcript archive). A folder counts as a setup when it holds something Claude Code creates there (`settings.json`, `.claude.json`, `CLAUDE.md`, `projects/`, `skills/` and similar); `~/.claude` always does, and a `.b12-ignore` file opts any folder out. The `.b12-ignore` opt-out applies to every install mode, including the default `~/.claude` and an explicit target directory. -- Daemon runtime self-heal now works on macOS. When Homebrew deleted the versioned Python keg a daemon was running from, `proc_pidpath` failed with ENOENT and the check fell back to the venv launcher, which still exists, so the daemons kept running on the deleted runtime and `b12_health` reported them as fine. A deleted binary is now reported as missing. -- Open sessions no longer drop B12 when a daemon restart needs a second launchd spawn. The stdio proxy's default reconnect budget (`B12_MCP_RECONNECT_BUDGET`) is now 45 seconds; the old 30 seconds equalled launchd's respawn throttle, so if the first spawn exited at once the budget ran out just before the daemon came back and every session needed a manual `/mcp` reconnect. -- Memories stored without a `memory_type` no longer surface as a bare `null`. The display line was built by concatenating the type in SQL, and concatenating NULL yields NULL, so proactive surfacing, recall and the session-start and subagent context blocks lost those memories' content. They now show as `[general]`, the schema default, and the proactive-surface hook drops any result without a display before it counts, injects or records the results, so a malformed daemon response no longer spends the token budget on an empty block or marks unseen memories as already surfaced. The type filters had the same NULL trap: `memory_type NOT IN (...)` and `memory_type != '...'` are NULL for an untyped row, so the keyword, cold-semantic, session-start and subagent queries dropped those memories entirely, and the feedback digest never decayed or archived them. Every type exclusion now keeps untyped rows, treating them like `general`; in the hooks an untyped row that carries the `session-summary` tag is still kept out, as a summary. Graph expansion (in `memory-retrieval.sh` and the OpenCode plugin) now applies the same summary exclusion as the primary retrieval paths, so session summaries and progress rows, typed or tagged, are no longer pulled in as graph neighbours. `memory-browse.sh` had the same concatenation: its delete preview came back empty, so an untyped memory could not be deleted ("No active memory found"), and its stats printed a blank line for the untyped group, now labelled `none`. The OpenCode plugin's graph expansion and its session-start project and universal blocks (which printed `[null]`) are fixed the same way. Python display paths had the Python version of the bug: the recall daemon and the resurface, topic-shift and cross-session pickers sent an untyped memory's type as `''`, which the retrieval hook rendered as `[|src:...]`, and the MCP session-context, quality, stats and surfacing outputs printed `[None]`; `memory_refine` turned an explicit JSON `null` type into the string `"None"`. They now send and print `general` (`none` for stats groups), and the retrieval hook also maps an empty type to `general` for a daemon that has not been restarted yet. -- The Gemini CLI SessionEnd adapter's background worker no longer inherits the adapter's stdout, stderr or the fd 3 copy of stderr, so it cannot hold the host's pipe open while the B12 hook runs. A test now checks every backgrounded job in `hooks/` (multi-line blocks, one-line groups, plain commands, pipelines and and-or lists, at the end of a line or before another command), applies its redirections in order (including `exec` redirections such as `exec 3>&2`, the bodies of foreground groups, and `$( )` and backtick substitutions, quoted or not, process substitutions, and commands continued over several lines), and fails if any command, including an earlier pipeline stage, is left with stdout or stderr on the hook's own pipes. The Gemini tool-call adapter's checkpoint job is wrapped in one redirected group and now also closes the fd 3 copy of stderr from `exec 3>&2`, which kept the host's stderr open until the checkpoint script finished. The `printf` feeding the input parser in the working-context and proactive-surface hooks now discards its stderr so the process substitution meets the same rule. -- Hook watchdogs no longer hold the host's stdout pipe open after the hook exits. The orphaned watchdog `sleep` delayed EOF until the timer expired, and Claude Code then refused hook output that injected context, so proactively surfaced memories and tag corrections were dropped. -- The MCP server and daemon no longer flood stderr with SDK validation dumps for newer-protocol client probes (`server/discover`, pre-initialization `roots/list_changed`) or with one INFO line per request. Each known probe is logged once as a single line; all other warnings are unchanged. In daemon mode this stderr is an unrotated launchd log that had grown by megabytes per day. -- Installer now migrates launchd jobs that still run B12 Python scripts with `/usr/bin/python3` (Python 3.9 on macOS) to the B12 venv interpreter. Older consolidation jobs failed on every run because the scripts require Python 3.11+. Jobs that were unloaded or disabled stay off, and `/bin/bash` jobs are untouched. -- `memory-consolidate.py` exits with a clear message under Python older than 3.11 instead of a traceback. +### Fixed +- Hook output reaches Claude Code again. Hook watchdogs and backgrounded workers no longer keep the host's stdout or stderr pipe open after the hook exits, which had caused proactively surfaced memories and tag corrections to be refused. This covers the Gemini CLI SessionEnd worker and the Gemini tool-call checkpoint job, which also kept the `exec 3>&2` copy of stderr. +- Memories without a `memory_type` show as `[general]` everywhere (recall, proactive surfacing, the session-start and subagent blocks, MCP outputs, `memory-browse.sh` and the OpenCode plugin) instead of `null`, `[]` or `[None]`, and type filters no longer drop them. `memory-browse.sh delete` can delete them again, and proactive surfacing no longer spends its token budget on results without a display. +- `install.sh` keeps hooks that other tools added to `settings.json` and no longer writes into `~/.claude-*` folders that are not Claude Code setups. +- The installer moves launchd jobs that still run B12 scripts with `/usr/bin/python3` to the B12 venv interpreter, and `memory-consolidate.py` exits with a clear message below Python 3.11. +- Daemon runtime self-heal works on macOS when Homebrew removes the Python version a daemon was started from. +- The MCP server and daemon log each newer-protocol client probe once instead of writing SDK validation dumps to stderr. +- `B12_EMBED_RUNTIME_DIR` and `B12_MCP_DAEMON_SOCK` are honored by every hook and script. ### Internal - -- `scripts/release.sh` now replaces the `## [Unreleased]` block, as the repo writes it, with the curated release section. It only matched `## Unreleased`, so a release would have landed above the pending entries and left them under it. The CHANGELOG also gains the missing v10.5 and v11.21.0 entries. -- The test suite passes on macOS. Socket-based tests now use a short temp dir (`tmp_path` there exceeds the 104-byte AF_UNIX limit), the SessionEnd retention tests use the platform's DB path instead of the Linux one (on macOS they were checking an unused file), and two tests that simulate a deleted runtime through a symlinked launcher, which only works on Linux, are skipped on macOS. +- The test suite no longer touches a live install (temporary HOME, runtime dir and sockets, `launchctl` and `crontab` stubs) and passes on macOS. +- A repo-wide test fails if a backgrounded hook job leaves an fd on the hook's own pipes; shapes it does not model are tracked in #220. +- `scripts/release.sh` replaces the `## [Unreleased]` block, and the CHANGELOG gains the missing v10.5 and v11.21.0 entries. ## [v11.83.0] — 2026-08-17 diff --git a/README.md b/README.md index a3dea1c..d2d7afa 100644 --- a/README.md +++ b/README.md @@ -600,6 +600,14 @@ Dependency update PRs are review-gated too: Dependabot can suggest updates, but ## Changelog (recent) +### v11.83.1 (2026-10-21) + +- Hook output reaches Claude Code again: watchdogs and background workers no longer hold the host's pipes open. +- Memories without a type show as `[general]` everywhere and are no longer dropped by type filters. +- `install.sh` keeps other tools' hooks and honors a `.b12-ignore` opt-out. + +See [CHANGELOG.md](CHANGELOG.md) for the full notes. + ### v11.83.0 (2026-08-17) See [CHANGELOG.md](CHANGELOG.md) for the full notes. diff --git a/install.sh b/install.sh index f0f642d..9a4d81a 100755 --- a/install.sh +++ b/install.sh @@ -3697,7 +3697,7 @@ restart_embed_daemon_if_running() { # Main # ═════════════════════════════════════════════ -echo "B12 Memory System Installer (v11.83.0 — multi-platform)" +echo "B12 Memory System Installer (v11.83.1 — multi-platform)" echo "─────────────────────────────────" # Full setup: create venv first diff --git a/package-lock.json b/package-lock.json index 054bb05..59ed108 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "b12-memory-system", - "version": "11.83.0", + "version": "11.83.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "b12-memory-system", - "version": "11.83.0" + "version": "11.83.1" } } } diff --git a/package.json b/package.json index 428f860..c43abd7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "b12-memory-system", - "version": "11.83.0", + "version": "11.83.1", "private": true, "description": "Persistent memory system for AI coding assistants", "repository": { diff --git a/plugins/opencode/package.json b/plugins/opencode/package.json index 84e3a94..dbc1635 100644 --- a/plugins/opencode/package.json +++ b/plugins/opencode/package.json @@ -1,6 +1,6 @@ { "name": "b12-opencode-plugin", - "version": "11.83.0", + "version": "11.83.1", "description": "B12 Memory System - OpenCode Plugin", "type": "module", "main": "./dist/index.js", diff --git a/pyproject.toml b/pyproject.toml index e4a0d82..df6944b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "b12-memory" -version = "11.83.0" +version = "11.83.1" description = "Persistent cross-session memory system for AI coding assistants" readme = "README.md" license = "MIT" diff --git a/scripts/b12_health.py b/scripts/b12_health.py index 0921019..fee20eb 100644 --- a/scripts/b12_health.py +++ b/scripts/b12_health.py @@ -34,7 +34,7 @@ # ── Constants ──────────────────────────────────────────────────────── -VERSION = "11.83.0" # keep in sync with package.json / plugin.json / B12_VERSION (see docs/releasing.md) +VERSION = "11.83.1" # keep in sync with package.json / plugin.json / B12_VERSION (see docs/releasing.md) _HOME = Path.home() _B12_DIR = Path(os.environ.get("B12_DATA_DIR", _HOME / ".B12")) diff --git a/scripts/b12_mcp_server.py b/scripts/b12_mcp_server.py index 4e34357..4e57b8d 100755 --- a/scripts/b12_mcp_server.py +++ b/scripts/b12_mcp_server.py @@ -69,7 +69,7 @@ # falls back to legacy in-process stdio mode below — non-Claude-Code consumers # (Codex, Gemini, Kimi, OpenCode, Grok) see zero behaviour change either way. MCP_DAEMON_SOCK = os.environ.get("B12_MCP_DAEMON_SOCK", f"/tmp/b12-mcp-{_UID}.sock") -B12_VERSION = "v11.83.0" +B12_VERSION = "v11.83.1" # Fix C — resilient proxy reconnect. When the daemon-side socket closes while the # host CLI's stdin is still open (daemon restart by launchd/RSS-guard, redeploy,