Contributor Guide¶
How to set up a development environment, follow project conventions, write tests, and submit changes to kazunoko.
Development Setup¶
See Installation — For Developers for environment setup and prerequisites.
Quality checks:
uvx ruff check src/ # lint
uvx ruff format src/ # format
uvx mypy src/ # type check
uv run pytest # tests with coverage
uvx pre-commit run --all-files # all checks at once
Code Conventions¶
Variable naming¶
| Context | Convention | Example |
|---|---|---|
| CLI / example scripts | detector |
detector = connect(port, ...) |
| Library API | device |
def measure(device: DeviceProtocol) |
| Output file path | output or output_path |
output = Path("events.jsonl") |
| Input directory | input_dir |
input_dir = Path("20241216/") |
| File handle | fd |
with open(path) as fd: |
| File counter | file_id |
generate_filename(mac, file_id=0) |
| Command object | cmd |
cmd = Command(device) |
| Response object | response |
response = cmd.status() |
| Response view | view |
view = ResponseView(response) |
| List of DataFrames | dataframes |
dataframes: list[pd.DataFrame] |
Console output¶
import sys
from rich.console import Console
stdout = Console(file=sys.stdout) # data output
stderr = Console(file=sys.stderr) # status / logging
- stdout: data only (JSONL, tables, final results)
- stderr: user-facing status messages (
stderr.print()) and structured logs (logger)
Color markup¶
| Color | Use |
|---|---|
[cyan] |
Progress, info, verbose messages |
[green] |
Success, completion |
[red] |
Errors |
[yellow] |
Warnings |
[magenta] |
Table column styling |
Comments¶
Write comments only when the why is non-obvious (a hidden constraint, a workaround, a subtle invariant). Do not describe what the code does — well-named identifiers do that.
Package vs module name¶
osechi-kazunoko— inpip installcommands and PEP 723dependenciesblockskazunoko— in Pythonimportstatements
Testing¶
Tests live in tests/ and use pytest with coverage reporting.
uv run pytest # run all tests
uv run pytest -v # verbose output
uv run pytest tests/test_cli.py # single file
Test philosophy¶
Prefer coverage of real behavior over exhaustive edge cases. The current suite (~12 tests across 3 modules) covers:
test_parser.py—parse_jsonlhappy path and error casestest_workspace.py—get_workspace()env var andgenerate_filename()formattest_cli.py— CLI smoke tests viaCliRunner(--help,query,sync,generate)
Writing CLI tests¶
Use typer.testing.CliRunner with --mock so tests run without hardware:
from typer.testing import CliRunner
from kazunoko.cli import app
runner = CliRunner()
def test_status_mock():
result = runner.invoke(app, ["--mock", "status"])
assert result.exit_code == 0
Writing library tests¶
Use MockDevice as a drop-in for PortDevice:
from kazunoko import MockDevice, Command
def test_command_status():
with MockDevice() as device:
cmd = Command(device)
response = cmd.status()
assert response.status == "ok"
Commit Conventions¶
kazunoko uses Conventional Commits enforced by Commitizen via pre-commit hooks.
Format¶
Common types:
| Type | Use |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
refactor |
Code change without feature or fix |
test |
Tests only |
chore |
Build, deps, config |
Examples¶
feat(cli): add sync command for standalone RTC sync
fix(measure): handle EventTimeout during setup()
docs(design): add measurement layer design page
test(cli): add smoke tests for query and generate commands
Pre-commit hooks¶
The commitizen check hook validates commit message format on every commit. If it fails, rewrite the message to follow the format above.
Adding a New CLI Command¶
- Add a function to
cli.pywith@app.command(). - Use
typer.Argument()for positional args andOptions.*fromoptions.pyfor flags. - Use
Command(device)for device operations. - Use
ResponseView+ResponseFormatterfor output. - Catch
KazunokoErrorat the top level and print tostderr. - Add a smoke test in
tests/test_cli.py. - Add a documentation page in
docs/cli-guide/.
Extending the Command Class¶
- Add a method to
Commandincommand.py. - Call
self._device.query(command_string)for request/response interactions. - Call
self._device.receive_response(field_type="event")for event reads. - Do not validate input — the device validates and returns
status: "error"if invalid. - Do not catch exceptions — let them propagate to the caller.
Version Management¶
Do not bump versions without explicit user authorization.
When a release is requested, follow this order:
task bump:check # preview next version (dry run)
task bump:patch # or bump:minor / bump:major
task docs:release -- X.Y.Z # create release notes from template
# fill in release notes, update zensical.toml nav, commit
task push:tags # push commits and tags
task push:release -- X.Y.Z # create GitLab release