Commit graph

3 commits

Author SHA1 Message Date
Tejas Chopra
44136ed042
fix(wrap): make RTK opt-in (off by default) across wrap subcommands (#2344)
## 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>
2026-07-17 16:47:19 -07:00
Rod Boev
8522fcbc40
fix(code): quarantine Perl parser from code-aware compression (#2204)
## Description

`headroom proxy` can wedge when code-aware compression enters the
tree-sitter Perl external scanner and the native scan keeps the GIL
indefinitely. Current main still has two routes into that scanner:
explicit `perl` or `pl` hints flow through `CodeAwareCompressor`, and
`detect_language()` can nominate Perl on non-Perl code because its
prefilter matches generic sigils such as decorators, JSDoc tags, and
shell variables before phase 2 parses every surviving candidate grammar.

This change quarantines Perl at the code-aware compression funnel
without widening scope. Perl remains recognized at the input boundary,
but live proxy compression no longer requests a Perl parser. Non-Perl
code keeps its existing code-aware behavior. Real Perl falls back
through the existing safe Kompress or passthrough contract instead of
entering tree-sitter. The diff stays inside
`headroom/transforms/code_compressor.py` plus focused parser-safety
regressions.

Refs #2185

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] 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

- Added a Perl quarantine list in
`headroom/transforms/code_compressor.py` and used it to stop Perl
candidate parsing during language detection.
- Added a hard `_get_parser()` guard so no live code-aware path can
construct a Perl parser.
- Routed resolved explicit Perl hints through the existing safe fallback
or passthrough contract before AST compression.
- Added focused parser-safety regressions for non-Perl candidate bleed,
explicit `perl` and `pl`, inferred real Perl, mixed fenced Perl content,
fallback-disabled passthrough, and non-Perl negative space.

## Testing

- [x] Unit tests pass (`uv run pytest tests/test_perl_scanner_safety.py
-q`)
- [x] Linting passes (`uv run ruff check
headroom/transforms/code_compressor.py
tests/test_perl_scanner_safety.py`)
- [ ] Type checking passes (`uv run mypy headroom`)
- [x] New tests added for new functionality when applicable
- [ ] Manual testing performed

### Test Output

```text
uv run pytest tests/test_perl_scanner_safety.py -q
8 passed in 1.93s
uv run ruff check headroom/transforms/code_compressor.py tests/test_perl_scanner_safety.py
All checks passed!
uv run ruff format headroom/transforms/code_compressor.py tests/test_perl_scanner_safety.py --check
2 files already formatted
```

## Real Behavior Proof

- Environment: Windows, Python from the synced `uv` environment, `dev`
and `code` extras installed, no provider call
- Exact command / steps: Run `uv run pytest
tests/test_perl_scanner_safety.py -q`.
- Observed result: `8 passed in 1.93s`; the suite proves explicit `perl`
and `pl`, inferred real Perl, mixed fenced Perl, and non-Perl
negative-space routes all avoid Perl parser entry.
- Not tested: the reporter's macOS payload and long-running concurrent
workload

## 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
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] 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
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- Use `Refs #2185`, not `Closes #2185`. The wedge surface is this PR's
scope, but #2185 also carries a separate orphaned `headroom mcp serve`
report that this slice does not address.
- This is a reachability fix. It does not repair the upstream Perl
scanner and it does not harden other grammars against the same class of
native wedge.
- `CHANGELOG.md` remains unchanged because Headroom generates release
notes from conventional commits.
- Merged PR https://github.com/headroomlabs-ai/headroom/pull/2114
addresses a different cooperative compression stall and stays separate
from this native parser-entry slice.

---------

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-07-14 20:18:51 -07:00
David Wells
2a954b69b4
feat(wrap): add ZCode desktop app support (#1845)
## Description

Add `headroom wrap zcode` and `headroom unwrap zcode` commands for the
ZCode desktop app (zcode.z.ai). ZCode is a desktop Electron IDE built by
Z.AI, optimized for GLM-5.2 models. It has no CLI binary, so this
follows the Pattern-B (proxy-only, print instructions) approach — same
as Cursor, Cline, and Continue.

**Upstream auto-detection:** `headroom wrap zcode` now reads
`~/.zcode/v2/config.json` to detect the enabled provider and
automatically configures the proxy upstream — no manual flags needed.

Closes #1844

## 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

- New module: `headroom/providers/zcode/__init__.py` and `runtime.py`
(ZCodeProxyTargets, ZCodeUpstream, build_proxy_targets, detect_upstream,
upstream_to_proxy_urls, render_setup_lines)
- New CLI command: `headroom wrap zcode` in `headroom/cli/wrap.py:4815`
— starts proxy, injects RTK into AGENTS.md, prints Base URL setup
instructions
- New CLI command: `headroom unwrap zcode` in
`headroom/cli/wrap.py:5960` — removes RTK markers, stops proxy
- New helper: `zcode_config_dir()` in `headroom/install/paths.py`
- Updated `_run_proxy_only_watcher` to accept
`anthropic_api_url`/`openai_api_url` params
- Updated README.md: ZCode row in compatibility matrix, unwrap list,
wrap command list
- Updated CHANGELOG.md: entry under [Unreleased] > Added

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`) — pre-existing numpy type
stubs issue prevents full mypy run
- [x] New tests added for new functionality (24 tests in
`tests/test_cli/test_wrap_zcode.py`)
- [x] Manual testing performed

### Test Output

```text
tests/test_cli/test_wrap_zcode.py ........................               [100%]

24 passed, 1 warning in 0.22s
```

## Real Behavior Proof

- Environment: macOS 15.5, Python 3.12.13, headroom installed via `pip
install -e .[dev]`
- Exact command / steps: `headroom wrap zcode --port 9000` then
`headroom unwrap zcode --port 9000`
- Observed result: Wrap detects provider from `~/.zcode/v2/config.json`
(e.g. "Z.ai Coding"), starts proxy with correct upstream on port 9000,
injects RTK into AGENTS.md, prints detected provider + upstream + Base
URL setup instructions. Unwrap removes RTK markers, deletes empty
AGENTS.md, stops proxy.
- Not tested: Actual ZCode app integration (ZCode is a desktop Electron
app; Base URL configuration is manual in Settings > Model Settings)

## Review Readiness

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

## Checklist

- [x] My code follows the project style guidelines
- [x] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
— N/A: code follows existing patterns
- [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
- [x] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)

N/A — CLI-only changes

## Additional Notes

- **Pattern-B approach:** ZCode is a desktop Electron app with no CLI
binary. Same pattern as Cursor, Cline, and Continue — proxy-only, print
instructions.
- **Upstream auto-detection:** Reads `~/.zcode/v2/config.json`, finds
the enabled provider, and passes its `baseURL` to the proxy. Falls back
to Z.ai Anthropic endpoint if no config found.
- **httpProxy investigation:** ZCode has an `httpProxy` setting in
`~/.zcode/v2/setting.json`, but it is an Electron-level forward proxy
(CONNECT tunneling), incompatible with headroom reverse proxy. The Base
URL approach in Model Settings is the correct integration point.
- **No dependencies added:** This PR adds zero new dependencies.

---------

Co-authored-by: Epicism <epicism@Epiphanie.local>
Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-14 16:07:39 -04:00