headroom/tests/test_cli/test_tokensave_setup.py
Paperinik dca9853ed9
feat(wrap): make tokensave the primary coding-task compressor, Serena the backup (#1230)
## Description

Makes **tokensave**
([github.com/aovestdipaperino/tokensave](https://github.com/aovestdipaperino/tokensave))
the **primary coding-task compressor** that `headroom wrap` installs,
and demotes **Serena** to a **backup**. tokensave is a local semantic
code-graph MCP server (`tokensave serve`): the agent queries it for
symbols, call chains, and impact analysis instead of grepping/reading
whole files — the same role Serena filled, but as a pre-indexed graph.
Serena now only registers when tokensave is unavailable (or when forced
with `--serena`).

Closes #

## Type of Change

- [ ] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- `headroom/graph/tokensave_installer.py` (new): fetch the prebuilt
tokensave release binary for the platform (release-binary only — no
`cargo` compile at wrap time); honors `HEADROOM_BINARIES_OFFLINE`;
returns `None` (→ Serena) when no asset exists (e.g. x86_64 macOS) or
the download fails.
- `mcp_registry`: `build_tokensave_spec()`; registration/disable/migrate
go through the existing `ServerSpec` + ownership-ledger flow, identical
to Serena.
- `cli/wrap.py`: new `_setup_coding_compressor` primary/backup policy;
tokensave setup/disable/migrate/index helpers. New flags
`--no-tokensave` (skip primary) and `--serena` (force backup on);
`--no-serena` now means "never register the backup". Default wrap
removes a previously Headroom-installed Serena entry once tokensave is
primary (user-managed entries preserved). `--code-graph` repointed to
tokensave; the legacy `codebase-memory-mcp` install path is dropped
(unwrap still cleans up legacy entries). `unwrap claude|codex` remove a
ledger-owned tokensave entry.
- Strands `HeadroomBundle`: `enable_tokensave_mcp=True` (primary);
`enable_serena_mcp` now defaults `False` (backup).
- `docs/content/docs/proxy.mdx`: `--code-graph` description updated from
codebase-memory-mcp to tokensave.
- Tests: tokensave installer (incl. error paths),
register/disable/migrate, primary/backup policy, and the
binary-resolution/indexing helpers. A scoped
`tests/test_cli/conftest.py` offline guard keeps the CLI suite hermetic.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
$ uv run pytest -q tests/test_graph_tokensave.py tests/test_cli/test_tokensave_setup.py tests/test_cli/test_tokensave_helpers.py
41 passed

$ uv run pytest -q tests/test_cli/ tests/test_graph.py tests/test_graph_tokensave.py
421 passed   # full CLI + graph suites, incl. all pre-existing Serena/unwrap/registry tests

$ uv run pytest -q tests/test_mcp_registry/ tests/test_proxy_healthchecks.py
passed

$ uv run ruff format --check headroom/ tests/      # 822 files already formatted
$ uv run ruff check <changed files>                # All checks passed!
$ uv run mypy headroom/graph/tokensave_installer.py headroom/mcp_registry/install.py
Success: no issues found in 2 source files

# Coverage on new module
headroom/graph/tokensave_installer.py    99%
```

## Real Behavior Proof

- Environment: macOS (darwin arm64), Python 3.14, `uv` dev env;
tokensave 7.0.2 binary present on PATH and exercised against this repo's
`.tokensave/` graph during development. The installer pins release
**v7.0.2** (SHA-256-verified) across macOS arm64, Linux aarch64/x86_64,
and Windows x86_64/aarch64.
- Exact command / steps: `headroom wrap claude` registers `tokensave
serve` as the primary MCP code-graph server and indexes the project;
with the binary removed from PATH and `HEADROOM_BINARIES_OFFLINE=1`, the
same command falls back to registering Serena. Behavior is pinned by the
unit tests (binary-present → tokensave registered + Serena entry
removed; binary-absent → Serena fallback; `--serena` forces backup on;
`--no-serena` suppresses it; `--no-tokensave` disables primary).
- Observed result: tokensave registered as primary on the binary-present
path; Serena registered on the unavailable path; unwrap removes only
ledger-owned entries.
- Not tested: live end-to-end agent session inside Claude Code / Codex
against a real provider API; Windows/Linux release-asset download
(covered by unit tests with mocked archives, not a live fetch).

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Additional Notes

- CHANGELOG is left untouched: this repo generates it via release-please
from Conventional Commits, so a manual edit is N/A.
- `strands/bundle.py` shows 0% patch coverage because that module
hard-imports the optional `strands` SDK, which CI does not install (the
pre-existing `_make_serena_client` was likewise uncovered) — not a
regression.
- A `test (3)` shard failure on `headroom.memory.bridge` is a
pre-existing offline-CI flake (cannot reach huggingface.co); it touches
no file in this PR and the scoped offline guard only applies under
`tests/test_cli/`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 16:55:37 -05:00

227 lines
9 KiB
Python

"""tokensave is the primary coding-task compressor; Serena is the backup.
These tests pin the wrap-time policy in :func:`_setup_coding_compressor` and
the tokensave register/disable/migrate helpers, mirroring the Serena tests.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from headroom.cli import wrap as wrap_cli
from headroom.mcp_registry import build_tokensave_spec
from headroom.mcp_registry.base import RegisterResult, RegisterStatus, ServerSpec
from headroom.mcp_registry.ledger import headroom_installed_matching, record_install
_FAKE_BIN = Path("/usr/local/bin/tokensave")
def _equivalent(a: ServerSpec, b: ServerSpec) -> bool:
return (a.command, tuple(a.args), dict(a.env)) == (b.command, tuple(b.args), dict(b.env))
class _FakeRegistrar:
"""Registrar mirroring real ``register_server`` overwrite semantics."""
def __init__(self, name: str = "claude", *, detected: bool = True, server=None):
self.name = name
self.display_name = name.capitalize()
self._detected = detected
self._server = server
self.force_calls: list[bool] = []
self.unregistered: list[str] = []
def detect(self) -> bool:
return self._detected
def get_server(self, server_name: str):
return self._server if server_name == "tokensave" else None
def register_server(self, spec: ServerSpec, *, force: bool = False) -> RegisterResult:
self.force_calls.append(force)
if self._server is not None and not _equivalent(self._server, spec) and not force:
return RegisterResult(RegisterStatus.MISMATCH, "differs")
self._server = spec
return RegisterResult(RegisterStatus.REGISTERED, "ok")
def unregister_server(self, server_name: str) -> bool:
self.unregistered.append(server_name)
self._server = None
return True
@pytest.fixture(autouse=True)
def _workspace(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
monkeypatch.setenv("HEADROOM_WORKSPACE_DIR", str(tmp_path / ".headroom"))
# Never touch the network or run the real binary during these unit tests.
monkeypatch.setattr(wrap_cli, "_index_tokensave_project", lambda *a, **k: None)
# ---------------------------------------------------------------------------
# _setup_tokensave_mcp
# ---------------------------------------------------------------------------
def test_setup_registers_and_records_when_binary_available(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(wrap_cli, "_ensure_tokensave_binary", lambda verbose=False: _FAKE_BIN)
registrar = _FakeRegistrar()
assert wrap_cli._setup_tokensave_mcp(registrar) is True
assert registrar._server is not None
assert registrar._server.name == "tokensave"
assert registrar._server.command == str(_FAKE_BIN)
# Ledger now proves Headroom owns the entry.
assert headroom_installed_matching("claude", registrar.get_server("tokensave"))
def test_setup_returns_false_when_binary_unavailable(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(wrap_cli, "_ensure_tokensave_binary", lambda verbose=False: None)
registrar = _FakeRegistrar()
assert wrap_cli._setup_tokensave_mcp(registrar) is False
assert registrar._server is None # nothing registered
def test_setup_skips_when_agent_not_detected(monkeypatch: pytest.MonkeyPatch) -> None:
sentinel = {"called": False}
def _should_not_run(verbose=False):
sentinel["called"] = True
return _FAKE_BIN
monkeypatch.setattr(wrap_cli, "_ensure_tokensave_binary", _should_not_run)
registrar = _FakeRegistrar(detected=False)
assert wrap_cli._setup_tokensave_mcp(registrar) is False
assert sentinel["called"] is False # never even fetched the binary
def test_setup_migrates_stale_headroom_entry(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(wrap_cli, "_ensure_tokensave_binary", lambda verbose=False: _FAKE_BIN)
# A stale Headroom-installed entry (different binary path) is on disk.
stale = build_tokensave_spec("/old/path/tokensave")
record_install("claude", stale)
registrar = _FakeRegistrar(server=stale)
assert wrap_cli._setup_tokensave_mcp(registrar) is True
# Force-updated to the current spec.
assert registrar.force_calls[-1] is True
assert registrar._server.command == str(_FAKE_BIN)
def test_setup_preserves_user_managed_mismatch(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(wrap_cli, "_ensure_tokensave_binary", lambda verbose=False: _FAKE_BIN)
# User-managed entry (NOT in ledger) that differs from our spec.
user = ServerSpec(name="tokensave", command="/custom/tokensave", args=("serve",))
registrar = _FakeRegistrar(server=user)
wrap_cli._setup_tokensave_mcp(registrar)
# Never force-overwrote a user-managed entry.
assert True not in registrar.force_calls
assert registrar._server.command == "/custom/tokensave"
# ---------------------------------------------------------------------------
# _disable_tokensave_mcp
# ---------------------------------------------------------------------------
def test_disable_removes_headroom_installed(capsys: pytest.CaptureFixture[str]) -> None:
spec = build_tokensave_spec(str(_FAKE_BIN))
record_install("claude", spec)
registrar = _FakeRegistrar(server=spec)
wrap_cli._disable_tokensave_mcp(registrar, verbose=True)
assert registrar.unregistered == ["tokensave"]
assert "Removed previously-installed tokensave MCP" in capsys.readouterr().out
def test_disable_preserves_user_managed(capsys: pytest.CaptureFixture[str]) -> None:
user = ServerSpec(name="tokensave", command="/custom/tokensave")
registrar = _FakeRegistrar(server=user)
wrap_cli._disable_tokensave_mcp(registrar, verbose=True)
assert registrar.unregistered == []
assert "user-managed" in capsys.readouterr().out
def test_disable_noop_when_absent(capsys: pytest.CaptureFixture[str]) -> None:
registrar = _FakeRegistrar(server=None)
wrap_cli._disable_tokensave_mcp(registrar, verbose=True)
assert registrar.unregistered == []
assert "Skipping tokensave MCP" in capsys.readouterr().out
# ---------------------------------------------------------------------------
# _setup_coding_compressor — primary/backup policy
# ---------------------------------------------------------------------------
def _spy_compressor(monkeypatch: pytest.MonkeyPatch, *, tokensave_ok: bool) -> dict:
calls: dict[str, object] = {"serena_setup": False, "serena_disabled": None, "tokensave": None}
def fake_setup_tokensave(reg, *, verbose=False, force=False):
calls["tokensave"] = "setup"
return tokensave_ok
def fake_disable_tokensave(reg, *, verbose=False):
calls["tokensave"] = "disabled"
def fake_setup_serena(reg, *, context, verbose=False, force=False):
calls["serena_setup"] = True
def fake_disable_serena(reg, *, verbose=False, reason="--no-serena"):
calls["serena_disabled"] = reason
monkeypatch.setattr(wrap_cli, "_setup_tokensave_mcp", fake_setup_tokensave)
monkeypatch.setattr(wrap_cli, "_disable_tokensave_mcp", fake_disable_tokensave)
monkeypatch.setattr(wrap_cli, "_setup_serena_mcp", fake_setup_serena)
monkeypatch.setattr(wrap_cli, "_disable_serena_mcp", fake_disable_serena)
return calls
def test_policy_tokensave_primary_disables_serena(monkeypatch: pytest.MonkeyPatch) -> None:
calls = _spy_compressor(monkeypatch, tokensave_ok=True)
wrap_cli._setup_coding_compressor(_FakeRegistrar(), serena_context="claude-code")
assert calls["tokensave"] == "setup"
assert calls["serena_setup"] is False
assert calls["serena_disabled"] == "tokensave is now the primary code-graph compressor"
def test_policy_serena_fallback_when_tokensave_unavailable(
monkeypatch: pytest.MonkeyPatch,
) -> None:
calls = _spy_compressor(monkeypatch, tokensave_ok=False)
wrap_cli._setup_coding_compressor(_FakeRegistrar(), serena_context="claude-code")
assert calls["serena_setup"] is True
def test_policy_force_serena_even_when_tokensave_ok(monkeypatch: pytest.MonkeyPatch) -> None:
calls = _spy_compressor(monkeypatch, tokensave_ok=True)
wrap_cli._setup_coding_compressor(_FakeRegistrar(), serena_context="claude-code", serena=True)
assert calls["serena_setup"] is True
def test_policy_no_serena_suppresses_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
calls = _spy_compressor(monkeypatch, tokensave_ok=False)
wrap_cli._setup_coding_compressor(
_FakeRegistrar(), serena_context="claude-code", no_serena=True
)
assert calls["serena_setup"] is False
assert calls["serena_disabled"] == "--no-serena"
def test_policy_no_tokensave_disables_and_falls_back(monkeypatch: pytest.MonkeyPatch) -> None:
calls = _spy_compressor(monkeypatch, tokensave_ok=True)
wrap_cli._setup_coding_compressor(
_FakeRegistrar(), serena_context="claude-code", no_tokensave=True
)
assert calls["tokensave"] == "disabled"
# tokensave disabled → treated as unavailable → Serena fallback registers.
assert calls["serena_setup"] is True