Skip to content

Contributing Guide

How to contribute to agentwatch development.

Development Setup

Prerequisites

  • Python 3.10+
  • tmux
  • uv (Python package manager)
  • Git

Clone and Install

# Clone repository
git clone https://github.com/agentwatch-sh/agentwatch.git
cd agentwatch

# Install in development mode
./install.sh --dev

# Install dependencies
uv sync

Running Tests

# All tests
uv run pytest

# Single module
uv run pytest agentwatch/tests/test_detectors_claude_code.py

# Tests matching pattern
uv run pytest -k "test_idle"

# Stop on first failure
uv run pytest -x

# Verbose output
uv run pytest -v

Running the Daemon

# Foreground (for debugging)
agentwatch start -f

# Or directly
./agentwatch.py --daemon --config ~/.config/agentwatch/config.yaml

Code Structure

agentwatch/
├── cli.py              # Command-line interface
├── capture.py          # tmux pane capture
├── config.py           # Configuration constants
├── utils.py            # Shared utilities
├── output/             # Output formatting
│   ├── text.py         # Plain text output
│   ├── json.py         # JSON output
│   └── png.py          # PNG rendering
├── detectors/          # Detection modules
│   ├── base.py         # BaseDetector ABC
│   ├── registry.py     # Detector orchestration
│   ├── patterns.py     # Detection patterns
│   ├── claude_code.py  # Claude Code detector
│   └── shell.py        # Shell detector
├── service/            # Daemon service
│   ├── daemon.py       # Main daemon loop
│   ├── monitor.py      # Session monitor
│   ├── state_store.py  # State management
│   ├── session_service.py  # Coding session operations (SessionService)
│   └── hooks/          # Hook system
├── server/             # Web viewer
│   ├── app.py          # aiohttp application
│   ├── routes.py       # HTTP routes (/api endpoints)
│   ├── websocket.py    # WebSocket handling
│   └── static/         # Frontend assets
├── sessions/           # Coding session management
│   ├── session.py      # Session lifecycle (CodingSessionManager)
│   ├── spawner.py      # Spawns sub-agent processes
│   └── tmux.py         # Async tmux client wrapper
└── tests/              # Test suite

Coding Conventions

Python Style

  • Full type annotations on all functions
  • Use dataclasses for structured data
  • Async code uses asyncio
  • Follow PEP 8 naming conventions

Example

from dataclasses import dataclass
from typing import Optional

@dataclass
class DetectionResult:
    """Result of a detection operation."""
    name: str
    confidence: float
    detail: Optional[str] = None

def detect_program(text: str) -> DetectionResult:
    """Detect which program is running.

    Args:
        text: Terminal content to analyze

    Returns:
        Detection result with program name and confidence
    """
    # Implementation
    pass

Detectors

Detectors extend BaseDetector and implement:

class MyDetector(BaseDetector):
    """Detector for MyProgram."""

    @property
    def priority(self) -> int:
        """Higher priority detectors run first."""
        return 50

    def detect_program(self, text: str) -> Optional[DetectionResult]:
        """Detect if MyProgram is running."""
        pass

    def detect_state(self, text: str, program: str) -> Optional[DetectionResult]:
        """Detect current state of MyProgram."""
        pass

Tests

Organize tests by module:

class TestClaudeCodeIdleDetection:
    """Tests for Claude Code idle state detection."""

    def test_idle_with_prompt_at_bottom(self):
        """Should detect idle when prompt is at bottom."""
        pass

    def test_idle_with_autosuggest(self):
        """Should detect idle when autosuggest is visible."""
        pass

Frontend

  • Use remoteLog() instead of console.log() for debugging
  • Follow existing patterns in app.js
  • Keep JavaScript in separate .js files

Making Changes

Before Starting

  1. Check existing issues for related work
  2. Open an issue to discuss major changes
  3. Make sure tests pass on main branch

Development Workflow

  1. Create a feature branch:

    git checkout -b feature/my-feature
    

  2. Make changes with tests

  3. Run tests:

    uv run pytest
    

  4. Check code style:

    uv run ruff check agentwatch/
    

  5. Commit with descriptive message:

    git commit -m "feat: add X to improve Y"
    

Commit Message Format

type: short description

Optional longer description explaining the change.

Fixes #123

Types:

  • feat: New feature
  • fix: Bug fix
  • refactor: Code refactoring
  • docs: Documentation
  • test: Tests
  • chore: Maintenance

Pull Request Process

  1. Push your branch
  2. Open a PR against main
  3. Fill in the PR template
  4. Wait for CI to pass
  5. Address review feedback
  6. Maintainer merges when ready

Adding Features

New Detector

  1. Create agentwatch/detectors/my_program.py
  2. Extend BaseDetector
  3. Register in agentwatch/detectors/__init__.py
  4. Add tests in agentwatch/tests/test_detectors_my_program.py
  5. Document in detection reference

New Hook Type

  1. Create agentwatch/service/hooks/my_hook.py
  2. Extend BaseHook
  3. Register in agentwatch/service/hooks/__init__.py
  4. Add config schema in config_loader.py
  5. Document in hooks guide

New Session Operation

  1. Add the operation in service/session_service.py (SessionService)
  2. Expose it via an /api route in server/routes.py
  3. Wire up the agentwatch session ... CLI verb
  4. Add tests
  5. Document in the sessions reference

New API Endpoint

  1. Add route in server/routes.py
  2. Follow existing auth patterns
  3. Add tests
  4. Regenerate API docs:
    python scripts/generate_api_docs.py
    

Testing

Writing Tests

import pytest
from agentwatch.detectors import detect_all

class TestMyFeature:
    """Tests for my feature."""

    def test_basic_case(self):
        """Should handle basic case."""
        result = my_function("input")
        assert result == "expected"

    def test_edge_case(self):
        """Should handle edge case."""
        with pytest.raises(ValueError):
            my_function("")

Test Fixtures

Use fixtures for common setup:

@pytest.fixture
def sample_capture():
    """Sample terminal capture for testing."""
    return """
    Claude Code v1.0.30
    ❯ hello
    """

Capture Files for Tests

Save real captures for testing:

# Enable debug captures
agentwatch restart

# Trigger the scenario you want to test

# Copy the capture file
cp ~/.cache/agentwatch/captures/session/capture_*.txt \
   agentwatch/tests/fixtures/my_test_case.txt

Documentation

Updating Docs

  1. Edit files in docs/
  2. Preview locally:
    mkdocs serve
    
  3. Check at http://localhost:8000

API Documentation

API docs are auto-generated. After changing /api routes or session operations:

python scripts/generate_api_docs.py

Docstring Format

def my_function(param: str) -> Result:
    """Short description of the function.

    Longer description if needed, explaining behavior,
    edge cases, and important notes.

    Args:
        param: Description of the parameter

    Returns:
        Description of the return value

    Raises:
        ValueError: When param is invalid
    """
    pass

Releases

Releases are automated. Maintainers:

  1. Update version in agentwatch/__init__.py
  2. Commit and tag:
    git commit -m "chore: bump version to X.Y.Z"
    git tag vX.Y.Z
    git push && git push --tags
    

The GitHub workflow handles building and publishing.