Development Guide
Local Setup
git clone https://github.com/jtwolfe/phixr.git
cd phixr
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Copy and configure environment:
cp .env.example .env.local
# Edit .env.local with your GitLab details
Running Locally
Bare Python (fastest iteration):
source venv/bin/activate
python -m phixr.main
This starts Phixr on http://localhost:8000. Redis is optional – the session store falls back to in-memory if unavailable.
With containers (full stack):
podman compose --profile full-stack up -d
Running Tests
# All unit tests
pytest tests/unit/ -v
# Single test file
pytest tests/unit/test_comment_handler.py -v
# Single test class
pytest tests/unit/test_integration_service.py::TestSessionCreation -v
# With coverage
pytest --cov=phixr --cov-report=html
Tests use pytest-asyncio for async test support. Mark async tests with @pytest.mark.asyncio.
Docker-dependent tests are marked @pytest.mark.docker and skipped without a running Docker daemon.
Project Structure
phixr/
main.py # FastAPI app, routes, startup
config/
settings.py # Pydantic settings (from env vars)
sandbox_config.py # OpenCode/sandbox configuration
handlers/
comment_handler.py # Routes @phixr interactions
commands/
parser.py # Parses /session, /end, messages
integration/
opencode_integration_service.py # Core orchestration
session_store.py # Redis-backed session persistence
bridge/
opencode_client.py # HTTP + SSE client for OpenCode API
context/
extractor.py # Issue context extraction from GitLab
git/
branch_manager.py # Branch creation and MR checks
collaboration/
vibe_room_manager.py # Vibe room state management
models/
execution_models.py # Session, VibeRoom, etc.
issue_context.py # IssueContext dataclass
utils/
gitlab_client.py # GitLab API wrapper
webhooks/
gitlab_webhook.py # Webhook routing and validation
web/
templates/ # Jinja2 templates (vibe room UI)
static/ # CSS, JS assets
tests/
unit/ # Unit tests (mocked dependencies)
integration/ # Integration tests (Docker required)
conftest.py # Shared fixtures
docs/ # Documentation (GitHub Pages site)
docker/ # Dockerfiles
Key Patterns
Adding a New Command
- Add pattern to
commands/parser.py(regex + parse method) - Add handler method to
handlers/comment_handler.py - Add route in
handle_issue_comment()switch - Add tests in
tests/unit/test_command_parser.pyandtest_comment_handler.py
Session Store Access
The SessionStore provides Redis-backed persistence with dict fallback:
# Write
store.save_session(session_id, session.model_dump())
store.set_opencode_id(session_id, oc_id)
store.set_issue_session(project_id, issue_id, session_id)
# Read
data = store.get_session(session_id)
oc_id = store.get_opencode_id(session_id)
session_id = store.get_issue_session(project_id, issue_id)
# Cleanup
store.clear_issue_session(project_id, issue_id)
store.clear_issue_session_by_session_id(session_id)
OpenCode Client Usage
# Create session
oc_session = await client.create_session()
# Send prompt
await client.send_prompt_async(session_id, prompt, system=system_instructions)
# Subscribe to events
async for event in client.subscribe_events():
if event["type"] == "permission.asked":
await client.reply_permission(perm_id, approved=True)
# Get messages
messages = await client.get_messages(session_id)
Building the Docker Image
podman compose --profile full-stack build
The Dockerfile is at docker/Dockerfile. It uses python:3.11-slim and installs from requirements.txt.
Documentation Site
The docs/ directory is a Jekyll site for GitHub Pages. To preview locally:
cd docs
bundle install # first time only
bundle exec jekyll serve
Or just push to GitHub – Pages builds automatically from the docs/ folder on the main branch.