mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description
RTK CLI-command filtering was set up **by default** across ~16 `wrap`
subcommands (copilot, codex, aider, cursor, cline, continue, goose,
openhands, opencode, grok, omp, openclaude, vibe, …) via `if not
no_rtk:` — so users got rtk hooks / instruction injection without opting
in. `wrap claude` was the lone exception (already gated on
`--context-tool`).
This makes RTK **opt-in (off by default)** everywhere, so Headroom's own
savings are what's measured unless a user explicitly wants rtk.
Closes #
## Type of Change
- [x] Bug fix (behavior change: default flip)
## Changes Made
- **Central gate** `_rtk_opt_in()` — RTK runs only when explicitly
enabled via `--rtk` or `HEADROOM_RTK=1`. Guards the 3 RTK entry points
(`_setup_rtk`, `_ensure_rtk_binary`, `_inject_rtk_instructions`) so they
no-op by default — one small change instead of editing ~30 call sites.
- **`--rtk` opt-in flag** on all 18 tool subcommands via a shared
eager-callback option (`expose_value=False`, sets `HEADROOM_RTK=1`; no
subcommand signature changes).
- `wrap claude`'s legacy `--context-tool` still opts in (mirrored into
the gate).
- **`--no-rtk` kept** as an accepted, now-redundant no-op (back-compat).
- lean-ctx and all non-RTK behavior untouched.
## Testing
```text
pytest tests/test_wrap_rtk_opt_in.py -> 4 passed
ruff check / format -> clean
mypy headroom -> Success: no issues found in 504 source files
```
Verified `--rtk` appears in `wrap {claude,codex,copilot} --help`;
`_rtk_opt_in()` is False by default, True with `HEADROOM_RTK=1`; entry
points no-op + write nothing when off.
## Real Behavior Proof
- Env: local `.venv`, click CliRunner.
- Steps: import wrap; assert gate default-off / env-on; assert
`_setup_rtk`/`_ensure_rtk_binary` return None and
`_inject_rtk_instructions` returns False + writes no file when not opted
in; assert `--rtk` in subcommand help.
- Observed: all pass. Not tested: a live end-to-end wrap launch (proxy
spawn).
## Notes
Part 2 of 3 (RTK opt-in). Separate PRs cover the code-graph MCP engine
and the proxy-option cleanup. No `CHANGELOG.md` edit — release-please
generates it from the PR title (per the changelog guard).
---------
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
"""Regression tests for #1126 — `headroom wrap` instruction injection must read
|
|
and write user instruction files as UTF-8, so non-ASCII prose (typographic
|
|
quotes, em-dashes) does not crash on a cp1252 (Windows) locale.
|
|
|
|
The reads use `errors="replace"`, so a stray non-UTF-8 byte (e.g. `0x9d`, which
|
|
fails a bare `open()` on *any* locale) cannot abort the wrap either.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from headroom.cli.wrap import (
|
|
_MEMORY_AGENTS_MARKER,
|
|
_RTK_MARKER,
|
|
_inject_memory_agents_md,
|
|
_inject_rtk_instructions,
|
|
)
|
|
|
|
# (inject_fn, marker) for the two prose injectors that share the bug.
|
|
INJECTORS = [
|
|
pytest.param(_inject_rtk_instructions, _RTK_MARKER, id="rtk"),
|
|
pytest.param(_inject_memory_agents_md, _MEMORY_AGENTS_MARKER, id="memory_agents"),
|
|
]
|
|
|
|
# A typographic quote / em-dash (valid UTF-8) plus a stray byte that is
|
|
# undefined in cp1252 and an invalid UTF-8 start byte.
|
|
_EXISTING = "Be in “happy places” — really.\n".encode() + b"legacy \x9d byte\n"
|
|
|
|
|
|
@pytest.mark.parametrize("inject, marker", INJECTORS)
|
|
def test_inject_appends_into_file_with_non_ascii_and_stray_byte(
|
|
inject, marker, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
):
|
|
if marker == _RTK_MARKER:
|
|
monkeypatch.setenv("HEADROOM_RTK", "1")
|
|
target = tmp_path / "AGENTS.md"
|
|
target.write_bytes(_EXISTING)
|
|
|
|
# Before the fix this raised UnicodeDecodeError reading the existing file.
|
|
assert inject(target) is True
|
|
|
|
text = target.read_text(encoding="utf-8", errors="replace")
|
|
assert marker in text
|
|
# The pre-existing prose is preserved (append, not rewrite).
|
|
assert "happy places" in text
|
|
|
|
|
|
@pytest.mark.parametrize("inject, marker", INJECTORS)
|
|
def test_inject_creates_file_when_absent(
|
|
inject, marker, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
):
|
|
if marker == _RTK_MARKER:
|
|
monkeypatch.setenv("HEADROOM_RTK", "1")
|
|
target = tmp_path / "nested" / "AGENTS.md"
|
|
|
|
assert inject(target) is True
|
|
assert marker in target.read_text(encoding="utf-8")
|
|
|
|
|
|
@pytest.mark.parametrize("inject, marker", INJECTORS)
|
|
def test_inject_is_idempotent(inject, marker, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
|
if marker == _RTK_MARKER:
|
|
monkeypatch.setenv("HEADROOM_RTK", "1")
|
|
target = tmp_path / "AGENTS.md"
|
|
target.write_bytes(_EXISTING)
|
|
|
|
assert inject(target) is True
|
|
assert inject(target) is True # marker already present -> no duplicate / no crash
|
|
assert target.read_text(encoding="utf-8", errors="replace").count(marker) == 1
|