- Follow PEP 8. Use
rufffor linting and formatting. - Max line length: 120 characters.
- Use type hints on function signatures. Skip them for obvious local variables.
- Imports: stdlib → third-party → local, separated by blank lines. One import per line.
snake_casefor modules, functions, variables.PascalCasefor classes.UPPER_SNAKEfor constants.- Private internals prefixed with
_.
- One responsibility per module. If a file grows past ~300 lines, split it.
- Keep
core/,ble/,gui/boundaries clean — no GUI imports in core, no BLE imports in GUI. - Shared state flows through explicit arguments or queues, never module-level globals.
- Validate at boundaries: CLI args, profile TOML parsing, BLE callbacks.
- Internal code trusts its callers — don't over-validate between modules.
- Log errors with
structlogcontext, don't silently swallow exceptions.
- All cross-thread data goes through
queue.Queueorthreading.Event. - Never share mutable state between threads without synchronization.
- BLE and signal threads must not import or call GUI code directly.
- Tests live in
tests/mirroring the source layout. - Test names:
test_<what>_<expected_behavior>. - Unit tests must not require a BLE adapter or display server.
- Use
pytest. No unittest classes unless grouping is genuinely needed.
- Pin minimum versions in
pyproject.toml, not exact versions. - No new dependencies without justification — stdlib first.
- Small, focused commits. One logical change per commit.
- Message format:
<area>: <what changed>(e.g.,encoder: fix sign extension for negative µV).
- Docstrings on public functions only, one line when possible.
- Comments explain why, not what. If the code needs a what comment, simplify the code.