Skip to content

Development Guide

This guide covers setting up Zorac for development, testing, and contributing.

Development Setup

Prerequisites

  • Python 3.13+
  • uv package manager
  • Git

Quick Setup

# Clone the repository
git clone https://github.com/chris-colinsky/zorac.git
cd zorac

# Complete development setup (installs deps + pre-commit hooks)
make dev-setup

# Or manually:
uv sync --extra dev
cp .env.example .env
# Edit .env to set your vLLM server URL

Project Structure

zorac/
├── zorac/                  # Main package
│   ├── __init__.py        # Package exports
│   ├── __main__.py        # Module entry point
│   ├── commands.py        # Command registry
│   ├── config.py          # Configuration management
│   ├── console.py         # Rich console singleton
│   ├── llm.py             # LLM operations
│   ├── main.py            # Main event loop
│   ├── markdown_custom.py # Custom markdown renderer
│   ├── session.py         # Session persistence
│   └── utils.py           # Utility functions
├── tests/                  # Test suite
│   ├── __init__.py
│   ├── test_commands.py   # Command registry tests (NEW)
│   └── test_zorac.py      # Comprehensive tests
├── requirements/           # Requirements documents (NEW)
│   └── help_feature.md    # Help feature requirements
├── docs/                   # Documentation
│   ├── INSTALLATION.md
│   ├── CONFIGURATION.md
│   ├── USAGE.md
│   ├── DEVELOPMENT.md     # This file
│   ├── SERVER_SETUP.md    # vLLM server guide
│   └── TEST_TRAINING.md   # Multi-GPU training guide (NEW)
├── .github/workflows/      # CI/CD
│   └── release.yml        # Release automation
├── pyproject.toml          # Dependencies and metadata
├── uv.lock                 # Dependency lock file
├── .env.example            # Configuration template
├── README.md               # Project overview
├── CLAUDE.md               # AI assistant guide
├── CHANGELOG.md            # Version history
├── CONTRIBUTING.md         # Contribution guidelines
├── Makefile                # Development commands
└── LICENSE                 # MIT License

Running Tests

Basic Testing

# Run all tests
make test

# Run with coverage report (generates HTML report)
make test-coverage

# View coverage report
open htmlcov/index.html

Test Coverage

Current coverage: 42% with 34 passing tests

The test suite covers: - Token counting with various message types - Session management (save/load, error handling) - UI header rendering - Message summarization logic - Configuration validation - Command registry structure and validation - Help system (display and LLM awareness) - Integration workflows (save/load roundtrips, help feature)

Module coverage: - zorac/commands.py: 100% coverage - zorac/session.py: 100% coverage - zorac/console.py: 100% coverage - zorac/llm.py: 96% coverage - zorac/markdown_custom.py: 45% coverage (UI rendering, mostly tested via integration)

Coverage targets: - Minimum 80% code coverage - All core functions tested - Edge cases and error paths covered

Code Quality

Linting

# Run linter
make lint

# Auto-fix linting issues
make lint-fix

Formatting

# Format code
make format

Type Checking

# Run mypy type checking
make type-check

All Checks

# Run all checks (lint + type + test)
make all-checks

Pre-commit Hooks

Zorac uses pre-commit hooks for automated quality checks:

# Install pre-commit hooks
make pre-commit-install

# Run pre-commit on all files
make pre-commit

# Pre-commit will automatically run on git commit

Dependencies

Production Dependencies

  • openai (>=2.8.1): Client library for vLLM's OpenAI-compatible API
  • tiktoken (>=0.8.0): Token counting for accurate usage tracking
  • python-dotenv (>=1.0.0): Environment variable management from .env files
  • rich (>=14.2.0): Rich terminal formatting, markdown rendering, and live updates
  • textual (>=1.0.0): Modern TUI framework for the full terminal user interface

Development Dependencies

  • pytest (>=8.0.0): Testing framework
  • pytest-cov (>=4.1.0): Coverage reporting for pytest
  • pytest-mock (>=3.12.0): Mock fixtures for pytest
  • ruff (>=0.8.0): Fast Python linter and formatter
  • mypy (>=1.8.0): Static type checker
  • pre-commit (>=3.6.0): Git hook management

Architecture Overview

Module Breakdown

zorac/commands.py - Command Registry (NEW) - Centralized registry of all interactive commands - COMMANDS: List of command definitions with descriptions - get_help_text(): Generates formatted help for /help display - get_system_prompt_commands(): Provides command info to LLM via system prompt - Single source of truth for command documentation - Enables LLM awareness of Zorac's capabilities

zorac/config.py - Configuration Management - Three-tier priority: Environment Variables > Config File > Defaults - Type-safe getters: get_int_setting(), get_float_setting(), get_bool_setting() - Runtime configuration via /config command - Persistent storage in ~/.zorac/config.json

zorac/console.py - Rich Console Singleton - Single Rich console instance for consistent formatting - Used across all modules for output

zorac/llm.py - LLM Operations - summarize_old_messages(): Auto-summarization with status spinner - Handles API calls to vLLM server - Token limit management

zorac/markdown_custom.py - Custom Markdown Renderer - LeftAlignedMarkdown: Custom markdown renderer with left-aligned headings - Monkey-patches Rich's Heading.__rich_console__ to remove centered heading panels - Provides cleaner, more readable terminal output - Maintains Rich styling (bold, colored) while forcing left justification

zorac/main.py - Textual TUI application with chat log, stats bar, and input widgets - get_initial_system_message(): Generates enhanced system prompt with command info - Textual Input widget with SuggestFromList for slash command autocomplete - Real-time streaming via Markdown.get_stream() with persistent stats bar - Streaming and non-streaming response modes - Session auto-save after each response - Stats bar widget (Static#stats-bar) docked to bottom for always-visible metrics

zorac/session.py - Session Persistence - save_session(): Saves conversation to JSON - load_session(): Restores conversation from disk - Error handling for invalid sessions

zorac/utils.py - Utility Functions - count_tokens(): Uses tiktoken to count tokens in conversation - print_header(): Displays formatted welcome header - check_connection(): Verifies vLLM server connectivity

Adding Features

New Commands

When adding a new command, follow these steps to ensure consistency:

  1. Add to Command Registry (zorac/commands.py):
{
    "command": "/mycommand",
    "description": "Short one-line description for /help",
    "detailed": "Detailed explanation for the LLM system prompt. Describe what the command does and when to use it."
}
  1. Implement Command Handler in zorac/main.py:
if user_input.lower() == "/mycommand":
    # Your command logic here
    console.print("[green]Command executed![/green]")
    continue
  1. Add Tests in tests/test_commands.py or tests/test_zorac.py

  2. Update Documentation:

  3. Add to command list in docs/USAGE.md
  4. Update CLAUDE.md if architecture changes
  5. Update README.md if user-facing

Why use the command registry? - Single source of truth for all commands - /help command automatically displays your new command - LLM automatically knows about your command and can suggest it to users - Ensures consistency across documentation

New Configuration Settings

  1. Add default to DEFAULT_CONFIG in zorac/config.py
  2. Load it in main() using appropriate getter
  3. Use it in your code
  4. Update documentation

Custom Token Limits

Modify via .env file or /config command:

MAX_INPUT_TOKENS = get_int_setting("MAX_INPUT_TOKENS", 12000)

UI Customization

Panel Formatting:

Modify panel styling in zorac/main.py:

console.print(Panel(
    content,
    box=box.ROUNDED,
    style="cyan",
    expand=True  # Full width for left-aligned content
))

Custom Markdown Rendering:

Modify heading styles in zorac/markdown_custom.py:

def _left_aligned_heading_rich_console(self, console, options):
    if self.tag == "h1":
        yield Text(text.plain, style="bold #ffffff")  # Customize H1 style
    elif self.tag == "h2":
        yield Text(text.plain, style="bold #cccccc")  # Customize H2 style

Design Principles: - Full-width output for assistant responses - Left-align all content to match "Assistant:" label - Use Markdown.get_stream() during streaming for real-time content updates - Static#stats-bar displays real-time metrics during streaming - Stats bar updates in real-time during streaming, persists after response - Stats persist in bottom toolbar between interactions

Feature Requirements

The requirements/ directory contains detailed specifications for features. When adding significant new functionality:

  1. Create a requirements document: requirements/feature_name.md
  2. Include these sections:
  3. Overview and user stories
  4. Functional requirements
  5. Technical design
  6. Implementation plan
  7. Testing strategy
  8. Documentation updates
  9. Acceptance criteria

Example: See requirements/help_feature.md for a comprehensive example of a feature specification.

Benefits: - Clear specification before implementation - Easier code review - Better documentation - Prevents scope creep

Publishing Releases

Zorac is distributed via PyPI and Homebrew. Releases are automated via GitHub Actions.

Distribution Channels

  • PyPI: pip install zorac / pipx install zorac
  • Homebrew: brew tap chris-colinsky/zorac && brew install zorac

Semantic Versioning

  • MAJOR (X.0.0): Breaking changes, incompatible API changes
  • MINOR (x.Y.0): New features, backward compatible
  • PATCH (x.y.Z): Bug fixes, documentation updates only

Release Process

1. Prepare the Release

# Create a release branch from main
git checkout main
git pull origin main
git checkout -b release/vX.Y.Z

# Update version in pyproject.toml
# Update CHANGELOG.md with release notes

# Run all quality checks
make all-checks

# Commit changes
git add .
git commit -m "Prepare release vX.Y.Z"
git push origin release/vX.Y.Z

2. Create PR and Merge

# Create a pull request to main
gh pr create --base main --head release/vX.Y.Z --title "Release vX.Y.Z"

# After PR is approved and CI passes, merge via GitHub UI

3. Tag and Release

# Switch to main and pull the merged changes
git switch main
git pull

# Create and push an annotated tag
git tag -a vX.Y.Z -m "Release version X.Y.Z"
git push origin vX.Y.Z

# Clean up release branch
git branch -d release/vX.Y.Z

4. Automated Pipeline

When you push the tag, GitHub Actions automatically:

  1. Runs tests - Ensures all tests pass
  2. Builds package - Creates wheel and sdist
  3. Publishes to PyPI - Via Trusted Publisher (OIDC)
  4. Creates GitHub Release - With package artifacts
  5. Updates Homebrew formula - Automatically updates chris-colinsky/homebrew-zorac with the new version, URL, and SHA256

Monitor the release: - Visit: https://github.com/chris-colinsky/zorac/actions - Look for "Release" workflow - Verify PyPI publish and Homebrew update succeed

Note: The Homebrew update requires a HOMEBREW_TAP_TOKEN secret (a GitHub PAT with write access to chris-colinsky/homebrew-zorac).

5. Verify Release

  • Check PyPI: https://pypi.org/project/zorac/
  • Check GitHub Release: https://github.com/chris-colinsky/zorac/releases/tag/vX.Y.Z
  • Test installation:
    pip install zorac==X.Y.Z
    zorac --help
    
  • Test Homebrew (after formula update):
    brew upgrade zorac
    zorac --help
    

Release Checklist

Before creating a release:

  • [ ] All tests pass: make test
  • [ ] Code is linted: make lint
  • [ ] Type checking passes: make type-check
  • [ ] Version updated in pyproject.toml
  • [ ] CHANGELOG.md updated with release notes
  • [ ] All PRs for this release are merged
  • [ ] Documentation is up to date

After release:

  • [ ] PyPI page shows correct version
  • [ ] pip install zorac works
  • [ ] GitHub Release created with artifacts
  • [ ] Homebrew formula updated (automatic)

Quick Reference

# Complete release workflow
git checkout main && git pull
git checkout -b release/vX.Y.Z
# Update pyproject.toml version and CHANGELOG.md
make all-checks
git add . && git commit -m "Prepare release vX.Y.Z"
git push origin release/vX.Y.Z
gh pr create --base main --head release/vX.Y.Z --title "Release vX.Y.Z"
# Merge PR via GitHub UI
git switch main && git pull
git tag -a vX.Y.Z -m "Release version X.Y.Z"
git push origin vX.Y.Z
git branch -d release/vX.Y.Z
# Homebrew formula updates automatically after PyPI publish

Hotfix Releases

For urgent bug fixes:

git checkout main && git pull
git checkout -b hotfix/vX.Y.Z
# Fix the bug, then follow the same release process
# Use PATCH version bump (e.g., 1.1.0 -> 1.1.1)

Manual Publishing (Emergency)

If automated publishing fails:

# Build
python -m build

# Upload (requires PyPI token)
pip install twine
twine upload dist/*

LLM Command Awareness

Zorac includes an innovative feature where the LLM assistant running inside the chat client is aware of Zorac's own commands and capabilities.

How It Works

  1. Command Registry (zorac/commands.py):
  2. Centralized list of all commands with detailed descriptions
  3. Single source of truth for command information

  4. Enhanced System Prompt (zorac/main.py):

  5. get_initial_system_message() injects command information into the system prompt
  6. LLM receives command details at session start

  7. Natural Language Queries:

  8. Users can ask: "How do I save my session?"
  9. LLM responds with relevant command information
  10. LLM suggests appropriate commands based on user needs

Token Overhead

The enhanced system prompt adds ~400-450 tokens to each session. This is acceptable because: - Default context limit is 12,000 tokens - Overhead is <4% of available context - Users benefit from having an assistant that knows how to use the tool

Testing Command Awareness

See tests/test_zorac.py::TestHelpFeatureIntegration for tests that verify: - System message includes all commands - Token overhead is reasonable - Help text is properly formatted

AI-Friendly Documentation

This project includes CLAUDE.md, a comprehensive development guide designed to help AI coding assistants understand the project.

What it contains: - Detailed project structure and file purposes - Architecture overview and design decisions - Configuration documentation - Development workflows and commands - Dependencies and their usage

Why it's useful: AI-powered development tools can use this file to provide better suggestions and write code that matches project conventions.

Contribution Guidelines

See CONTRIBUTING.md for detailed contribution guidelines.

Quick Checklist

Before submitting a PR:

  • [ ] All tests pass: make test
  • [ ] Code is linted: make lint
  • [ ] Code is formatted: make format
  • [ ] Type checking passes: make type-check
  • [ ] Documentation is updated (README, USAGE, CLAUDE.md as needed)
  • [ ] New commands added to command registry (zorac/commands.py)
  • [ ] Requirements document created for significant features (requirements/)
  • [ ] CHANGELOG.md is updated
  • [ ] Commit messages are clear and descriptive

Troubleshooting Development

Virtual Environment Issues

# Remove and recreate virtual environment
rm -rf .venv
uv sync

Test Failures

# Run tests with verbose output
uv run pytest -vv

# Run specific test
uv run pytest tests/test_zorac.py::TestCountTokens -vv

Import Errors

# Ensure package is installed in development mode
uv sync --extra dev

Command Registry Issues

If commands aren't showing up in /help or the LLM doesn't know about them:

# Verify command is in the registry
python -c "from zorac.commands import COMMANDS; print([c['command'] for c in COMMANDS])"

# Test help text generation
python -c "from zorac.commands import get_help_text; print(get_help_text())"

# Test system prompt generation
python -c "from zorac.commands import get_system_prompt_commands; print(get_system_prompt_commands())"

Resources

Support

For questions or issues: 1. Check existing GitHub Issues 2. Review documentation in docs/ directory 3. Open a new issue with details

License

MIT License - see LICENSE for details.