Contributing to AudioBard
Thanks for your interest. AudioBard is a small project with a strong opinion: one change per PR, backed by real evidence. This document is short on purpose — the longer it gets, the less it gets read.
- Code of conduct
- What gets merged (and what doesn't)
- Development setup
- Reporting bugs
- Opening a pull request
- The benchmark bar
- Style guide
- Testing
- Prompts: the versioning rule
- Adding a provider
- The guards: touching tools/guards.py
Code of conduct
Contributor Covenant. By participating you agree to uphold it.
What gets merged (and what doesn't)
The maintainer applies the same bar to every PR. Precedents shape the bar; these are the recurring rejection reasons:
- Evidence of the real input, not a constructed one. A fix for an input shape the actual code path never produces gets closed. Before opening a PR for a parser/LLM fix, show a real sample (a real EPUB/TXT file, a real provider response) that exhibits the condition. If you can't reproduce it in the real environment, say so — a "paused, not a verdict" PR is welcome, and it gets re-opened with evidence.
- Reproducible on current dependencies, through the real code path. A test that injects a string by bypassing the actual reading path doesn't count as evidence. Show the actual command/output that fails.
- One change per PR. Personal fork configuration (AGENTS.md, local scripts, editor config) never enters a PR. Separate concerns into separate PRs; mixed PRs are closed on sight.
- Duplicates lose to first-filed-with-tests. Search open issues and PRs before implementing. If the same bug already has an issue or a PR with tests, the earlier one wins — no competing implementations.
- Tests must be real pins. A test that copies the constant it watches drifts together with the file it should police. Derive the watched value from the source file when you can, and assert exact equality over every affected file, not substring membership.
- If a PR takes pieces of someone else's closed PR/issue, the commits
that use them must carry
Co-authored-by: Name <id+user@users.noreply.github.com>for that author. No exception.
Development setup
Prerequisites: Python 3.10+, Ollama running with a
pulled model (e.g. ollama pull qwen2.5:7b), the
Piper binary on PATH, and
ffmpeg.
git clone https://github.com/oscarbol09/audiobard.git
cd audiobard
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev,llm-ollama,tts-piper]"
pytest
Run the whole local gate the same way CI does:
ruff check src tests tools
mypy src/audiobard
pytest --cov=audiobard --cov-fail-under=90 -m "not integration"
python tools/guards.py
Reporting bugs
Use the bug report template and include:
- Python version,
audiobard --version, OS - A minimal reproduction that goes through the real code path — the actual file or the actual provider response, not a paraphrase
- The full error traceback
Anything security-related goes to the security policy, never into a public issue.
Opening a pull request
- Check for existing issues/PRs about the same bug first (see above).
- Branch from
mainwith a descriptive name:fix/,feat/,docs/. - Make the change. One change per PR. Keep the diff reviewable.
- Add or update tests that would fail against the pre-fix code.
- Run the local gate (above) until clean.
- If you touched prompts or parsers, run the benchmark (next section) and attach its output to the PR.
- Open the PR with the template, referencing any related issues. At least one approving review is required; the merge is squash or linear only.
Titles follow Conventional Commits:
feat:, fix:, docs:, refactor:, test:, chore:. Messages in
English.
The benchmark bar
audiobard benchmark --llm ollama --model qwen2.5:7b scores attribution
accuracy against the gold standard (eval/gold_standard/p_and_p_ch3.json).
Any PR that changes src/audiobard/llm/prompts.py, the parsers, or the
attribution logic must include benchmark output showing no regression.
Without it the PR is marked incomplete, not merged. This is the project's
core contract: quality is measured, not asserted.
Style guide
- PEP 8 + ruff defaults, line length 100. Type hints on all public APIs.
- Pydantic models for every data structure crossing a module boundary — the LLM JSON schemas are generated from them.
async deffor I/O (LLM, TTS, filesystem); CPU-bound work viaasyncio.to_thread.- Logging via
structlog; neverprint()for diagnostics. - Docstrings: Google style for modules, classes, public functions.
Testing
- Unit tests in
tests/mock external calls (respxfor httpx). - Integration tests are marked
@pytest.mark.integrationand excluded from CI (they need local Ollama/Piper). - Coverage gate: 90%.
Prompts: the versioning rule
Prompts live in src/audiobard/llm/prompts.py, versioned
(PROMPT_V1, ...). Never edit a versioned prompt in place — add a new
version and switch the default, so the benchmark can compare versions.
Document what changed and why in the prompt's docstring.
Adding a provider
- Subclass the relevant ABC (
LLMClientorTTSProvider). - Implement all abstract methods with the base's async/sync signature.
- Register it in the factory (
src/audiobard/llm/__init__.pyortts/__init__.py). - Add a config example and tests with mocked network calls.
- Update the README providers table.
- Providers that clone voices or impersonate real people trigger the
ethics-reviewRFC requirement (dev plan §10) — open the RFC issue before implementing.
The guards: touching tools/guards.py
tools/guards.py (run in CI) pins personal-data protection: .env never
committed, data/books/ only allowlisted public-domain samples, no audio
output tracked, no literal API keys in source. Its allowlists
(REQUIRED_IGNORE_RULES, ALLOWED_IGNORE_NEGATIONS,
ALLOWED_DATA_BOOK_FILES, SECRET_EXEMPT_PATHS) are the reviewed
contract: if your change legitimately needs a new allowlist entry, add it
in the same PR with a comment explaining why. Changing a guard and
changing the thing it guards in separate PRs is a rejection.
Questions?
Open an issue or ask in the PR itself. If an issue is long, make the ask at the top.