Commit graph

2 commits

Author SHA1 Message Date
Abhay Singh
e240df2b69
fix(learn/grok): detect a Windows absolute project path (#2283)
## Description

The Grok `learn` plugin can't detect a Windows project path, so on
Windows it attributes every project's learnings to the wrong directory.

`discover_projects` decodes the URL-encoded workspace directory name
(which is the recorded absolute cwd) and decides whether it's absolute
with a `startswith("/")` check:

```python
decoded = unquote(workspace_dir.name)
project_path = Path(decoded) if decoded.startswith("/") else Path.cwd()
```

A Windows absolute path (e.g. `C:\Users\me\proj`, URL-encoded as
`C%3A%5CUsers%5Cme%5Cproj`) does not start with `/`, so the check fails
and `project_path` silently falls back to `Path.cwd()`. The learnings
are then attributed to whatever directory `headroom learn` happened to
run in, and the plugin looks for `GROK.md` / `AGENTS.md` under the wrong
path (so it never finds them).

The rest of the codebase already handles Windows drive-letter paths:
`memory/traffic_learner.py` guards with `ref.startswith("/") or
(len(ref) > 2 and ref[1] == ":")`, and the Claude plugin has a full
Windows-aware decode plus a session-cwd fallback. The Grok plugin's
naive `startswith("/")` is the outlier.

## Fix

Use `Path(decoded).is_absolute()`, which recognises both POSIX (`/...`)
and Windows drive-letter (`C:\...`) absolute paths on their respective
platforms:

```python
decoded_path = Path(decoded)
project_path = decoded_path if decoded_path.is_absolute() else Path.cwd()
```

On POSIX this is equivalent to the old check (no behavior change); on
Windows the drive-letter path now resolves correctly instead of
collapsing to cwd.

Closes #

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

- `headroom/learn/plugins/grok.py`: use `Path(decoded).is_absolute()`
instead of `decoded.startswith("/")` in `discover_projects`.
- `tests/test_learn_grok_plugin.py`: new test that an absolute workspace
path resolves to that path (platform-aware: the Windows branch is the
real guard, the POSIX branch confirms no regression).
- `CHANGELOG.md`: Bug Fixes entry.

## Testing

- [ ] 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
$ uvx ruff@0.15.17 check headroom/learn/plugins/grok.py tests/test_learn_grok_plugin.py
All checks passed!
$ uvx mypy@1.20.2 --ignore-missing-imports headroom/learn/plugins/grok.py
Success: no issues found in 1 source file
```

## Real Behavior Proof

- Environment: **Windows 11**, Python 3.12, `uvx ruff@0.15.17` / `uvx
mypy@1.20.2`. This bug is Windows-specific, and I ran the proof on
Windows where it actually reproduces. A full `pytest` OOM-kills this box
(ML stack import), so I reproduced the decode+resolve with a
dependency-free script and left the full pytest to CI.
- Exact command / steps: took the URL-encoded workspace name
`C%3A%5Cproj%5Capp`, decoded it, and ran it through the OLD
`startswith("/")` and NEW `is_absolute()` resolution. Confirmed directly
that `Path(r"C:\proj\app").is_absolute()` is `True` while
`r"C:\proj\app".startswith("/")` is `False`.
- Observed result: OLD → `cwd-fallback` (wrong); NEW → `C:\proj\app`
(correct). A relative workspace name still falls back to cwd under both;
a POSIX abs path resolves identically under both.
- Not tested: a live Grok CLI history on Windows end-to-end; full local
`pytest` deferred to CI (OOM).

## 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
- [ ] 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
- [ ] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

The "unit tests pass locally" box is unchecked because the full suite
imports the ML stack, which I can't run here. Because the bug is
Windows-specific and `Path.is_absolute()` is platform-dependent, the
added test is platform-aware: on Windows (where I verified the fix) its
drive-letter branch is the real regression guard; on the Linux CI runner
it exercises the POSIX branch, confirming the change doesn't regress the
existing behavior. The standalone proof above covers the Windows fix
directly.

---------

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
2026-08-11 23:45:13 -05:00
roman-t3a
cb388f6af2
feat(wrap): add first-class Grok CLI support (#1823)
## Description

Adds first-class Grok CLI integration so Headroom can wrap, compress,
and learn from Grok sessions the same way it does for Claude Code and
Codex.

Grok routes inference through `GROK_CLI_CHAT_PROXY_BASE_URL`; Headroom
sets that to the local proxy so chat traffic is compressed before
forwarding to xAI. MCP retrieval and session learning follow the same
patterns as Codex.

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)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Add `headroom/providers/grok/` provider slice (`runtime.py`,
`install.py`) with `GROK_CLI_CHAT_PROXY_BASE_URL` routing and project
attribution prefix
- Add `headroom wrap grok` and `headroom unwrap grok` CLI commands (MCP
registration, RTK/context-tool setup, proxy launch)
- Add `GrokRegistrar` for marker-delimited `[mcp_servers.headroom]`
injection in `~/.grok/config.toml`
- Add `headroom learn --agent grok` plugin parsing
`~/.grok/sessions/*/updates.jsonl` and `GrokWriter` targeting `GROK.md`
- Register Grok in install planner, install registry, MCP install list,
and agent savings tracking
- Update README agent compatibility matrix and unwrap support list
- Add unit tests for provider, wrap CLI, MCP registrar, and learn plugin

## 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 tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py tests/test_mcp_registry_grok.py tests/test_learn_grok_plugin.py -q
============================== 10 passed in 0.15s ==============================

$ uv run ruff check headroom/providers/grok headroom/mcp_registry/grok.py headroom/learn/plugins/grok.py headroom/cli/wrap.py headroom/learn/writer.py
All checks passed!

$ uv run mypy headroom/providers/grok headroom/mcp_registry/grok.py headroom/learn/plugins/grok.py
Success: no issues found in 5 source files
```

## Real Behavior Proof

- Environment: macOS (darwin), Python 3.13.14 via `uv`, repo at
`/Users/s/dev/headroom`, Grok CLI at `/Users/s/.grok/bin/grok`
- Exact command / steps: `cd /Users/s/dev/headroom && uv run pytest
tests/test_provider_grok.py tests/test_cli/test_wrap_grok.py
tests/test_mcp_registry_grok.py tests/test_learn_grok_plugin.py -q`; `uv
run ruff check headroom/providers/grok headroom/mcp_registry/grok.py
headroom/learn/plugins/grok.py`; `uv run python -c "from
headroom.providers.grok import build_launch_env; env, display =
build_launch_env(8787, environ={}); print(display[0])"`; `uv run python
-c "from pathlib import Path; import tempfile; from
headroom.mcp_registry.grok import GrokRegistrar; from
headroom.mcp_registry.install import build_headroom_spec;
td=tempfile.mkdtemp(); reg=GrokRegistrar(home_dir=Path(td));
print(reg.register_server(build_headroom_spec('http://127.0.0.1:8787'),
force=True).status.value)"`
- Observed result: pytest reported `10 passed`; ruff reported `All
checks passed!`; `build_launch_env` printed
`GROK_CLI_CHAT_PROXY_BASE_URL=http://127.0.0.1:8787/v1`; MCP registrar
returned `registered` and wrote the Headroom marker block to
`config.toml`
- Not tested: live `headroom wrap grok` session with authenticated Grok
API traffic through a running Headroom proxy (requires maintainer
environment with active `grok login`)

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

## Screenshots (if applicable)

N/A — CLI/integration change only.

## Additional Notes

- Follows the provider-slice pattern from `b17c6d81` / `93a1f211`
(Codex/Cursor/Aider extraction).
- Routing uses the session env var only (not `config.toml` endpoint
override) so `grok login` session auth continues to work.
- Manual E2E wrap/unwrap with real Grok sessions is left for maintainer
verification.

---------

Co-authored-by: JerrettDavis <mxjerrett@gmail.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-07-15 18:51:38 +00:00