Files
voculinux/CONTRIBUTING.md
T
Sergey SilaevandGitHub c0217a8aec refactor(installer): generate distro package map (#872)
* refactor(installer): generate distro package map

* fix(installer): handle Debian derivative package maps

* docs(installer): document generated package map

* fix(installer): validate package map and refresh derivative probes

* fix(installer): name openSUSE package roles
2026-09-29 21:16:03 -07:00

9.5 KiB

Contributing to Vocalinux

Thanks for your interest in contributing. This guide covers setup, style, testing, and pull requests.

Code of conduct

Participation is covered by our Code of Conduct. Be respectful and constructive.

Ways to contribute

Development setup

git clone https://github.com/YOUR-USERNAME/vocalinux.git
cd vocalinux
./install.sh --dev

This installs system dependencies, creates a venv from the system Python, installs the package in editable mode with dev tools, and runs the test suite.

install.sh always builds venv/ from the system Python (/usr/bin/python3, or $SYSTEM_PYTHON), because distro PyGObject is only importable from that interpreter. It ignores an activated virtualenv, so you can run it from a shell that still has uv's .venv active. See The two environments.

Manual setup

  1. Fork and clone the repository.

  2. Install system dependencies (examples):

    Ubuntu 24.04+ (just deps pip-builds PyGObject 3.56 from the lock):

    sudo apt update
    sudo apt install -y python3-pip python3-gi python3-gi-cairo \
         gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libgirepository1.0-dev \
         build-essential pkg-config libcairo2-dev \
        python3-dev portaudio19-dev python3-venv xdotool
    

    Debian 12 cannot pip-build PyGObject 3.56 (glib 2.74). Use ./install.sh --dev for tests and running from source, then venv/bin/pytest / venv/bin/python -m vocalinux.main --debug.

    sudo apt install -y python3-pip python3-gi python3-gi-cairo \
         gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository1.0-dev libcairo2-dev \
         build-essential pkg-config \
        python3-dev portaudio19-dev python3-venv xdotool
    

    Debian 13+:

    sudo apt install -y python3-pip python3-gi python3-gi-cairo \
         gir1.2-gtk-3.0 gir1.2-ibus-1.0 libgirepository-2.0-dev libcairo2-dev \
         build-essential pkg-config \
        python3-dev portaudio19-dev python3-venv xdotool
    

    AppIndicator (system tray):

    # Older Ubuntu:
    sudo apt install -y gir1.2-appindicator3-0.1
    # Debian 12+ or newer Ubuntu:
    sudo apt install -y gir1.2-ayatanaappindicator3-0.1
    
  3. Create the environment and install:

    # Requires uv (https://docs.astral.sh/uv/); creates .venv/ with dev + vad extras
    just deps
    

    just deps compiles PyGObject from the lock into .venv. That needs libgirepository-2.0-dev (Ubuntu 24.04+). Debian 12 cannot build it; use Option 1 and the installer venv/ instead.

  4. Run:

    just run-source-debug
    
  5. Optional pre-commit hooks:

    uv run --no-sync pre-commit install
    

    Hooks are optional. CI runs the same checks.

The two environments

The repository uses two virtual environments on purpose. Do not merge them:

Directory Created by Python Used for
.venv/ just deps (uv) pinned in .python-version dev tooling: pytest, black, mypy, just recipes
venv/ ./install.sh the system Python running the installed app, which needs distro PyGObject

gi (PyGObject) is the reason. just deps / uv sync build it from source into .venv/, which works wherever glib 2.80+ and libgirepository-2.0-dev are present (Arch, Fedora, Ubuntu 24.04 with that package, CI). Debian 12 cannot build PyGObject 3.56; use ./install.sh --dev and venv/bin/pytest. install.sh never reuses .venv/: it builds venv/ from the system Python with --system-site-packages, and rebuilds it if another interpreter created it. Set SYSTEM_PYTHON=/usr/bin/python3.12 ./install.sh on systems that ship several system interpreters.

Making changes

Branch naming

git checkout main
git pull origin main
git checkout -b feature/your-feature-name
# or fix/, docs/, refactor/, test/

Never push directly to main. Open a pull request for every change.

Code style

Tool Role
Black Formatting (line length 100)
isort Import sorting (black profile)
flake8 Linting
mypy Type checking (just typecheck)
just format    # black + isort
just lint      # flake8 + black --check + isort --check
just typecheck # mypy src/

Project structure

vocalinux/
├── src/vocalinux/            # Application
│   ├── main.py
│   ├── speech_recognition/   # Engines + command processor
│   ├── text_injection/       # X11 / Wayland injection
│   ├── ui/                   # GTK tray, settings, config
│   └── utils/
├── tests/
├── resources/                # Icons and sounds
├── docs/
├── install.d/                # Installer modules, including generated package data
├── packaging/                # AppImage, AUR, Flatpak
├── scripts/                  # Maintenance tools and distro package-map source
├── snap/                     # Snap recipe
└── web/                      # Marketing site (Next.js)
Task Start here
Voice command src/vocalinux/speech_recognition/command_processor.py
UI src/vocalinux/ui/
Recognition engines src/vocalinux/speech_recognition/recognition_manager.py
Text injection src/vocalinux/text_injection/text_injector.py
Settings / config src/vocalinux/ui/config_manager.py, settings_dialog.py
Installer package names scripts/distro-package-map.yaml

Installer package map

scripts/distro-package-map.yaml is the source of truth for package names, alternatives, and conditional package probes used by install.sh. Do not edit the generated install.d/package_map.sh directly. After changing the YAML, run:

just distro-packages
just distro-packages-check

Commit both the YAML and generated shell module. Package-manager control flow and installation policy remain handwritten in install.d/system_dependencies.sh; user systems never parse the YAML. The remote bootstrap's pre-checkout git installation is the deliberate exception because the generated module is not available yet.

Testing

just test
just test-cov
uv run --extra dev --extra vad --group lint pytest tests/test_command_processor.py
  • Place tests in tests/ as test_*.py
  • Aim for solid coverage on new code (roughly 80%+)
  • Use pytest-mock via the mocker fixture
def test_command_processor_new_line(mocker):
    """Test that 'new line' command returns correct action."""
    processor = CommandProcessor()
    result = processor.process("new line")
    assert result.action == "new_line"

Remote API test server

python scripts/test_remote_server.py
python scripts/test_remote_server.py --port 9000
python scripts/test_remote_server.py --delay 2

Endpoints:

  • whisper.cpp style: http://localhost:8080/inference
  • OpenAI-compatible: http://localhost:8080/v1/audio/transcriptions

In Settings → Speech Model, select Remote API, set the server URL, choose the endpoint format, and use Test Connection.

Pull requests

Automated agents may append three robot emoji (🤖) to the PR title to opt into the agent fast-track merge path when that process is active.

Before you open a PR

  • Style checks pass (just lint, just typecheck)
  • Tests pass (just test)
  • Docs updated when behavior or install steps change
  • Commits use Conventional Commits
type(scope): short description

Longer description if needed.

Fixes #123

Types: feat, fix, docs, style, refactor, test, chore

Examples:

feat(commands): add "select all" voice command
fix(tray): resolve icon not updating on Wayland
docs(readme): update installation instructions

Submit

  1. Push your branch to your fork
  2. Open a PR against main and fill out the template
  3. Link related issues
  4. Wait for CI; address review feedback
  5. Maintainers squash-merge when approved

Releases

Maintainers follow docs/RELEASE_PROCESS.md (version files, docs, website, tag, automated publish). Do not tag releases from feature branches.

Community

License

Contributions are licensed under the GNU Affero General Public License v3.0 (AGPL-3.0), matching the other VocaHQ distribution projects (VocaMac, VocaPhone, VocaGateway). By opening a pull request, you agree that your contribution may be distributed under that license.