Thanks for your interest in contributing. This document covers how the project is actually built and tested today - see docs/ARCHITECTURE.md for why it's built this way.
This project follows the Home Assistant Code of Conduct.
ha-dev-tools/
├── custom_components/ha_dev_tools/
│ ├── __init__.py # Integration setup
│ ├── manifest.json
│ ├── config_flow.py
│ ├── const.py
│ ├── llm_api.py # Tool definitions, registered into HA's llm.API registry
│ ├── access_control.py # The arm-file + admin gate every tool goes through
│ ├── security.py # Path allowlist/denylist for file-touching tools
│ ├── file_manager.py / log_manager.py / automation_manager.py /
│ │ entity_manager.py / helper_manager.py / dashboard_manager.py /
│ │ template_manager.py / supervisor_manager.py / audit_manager.py /
│ │ config_tools.py # Backing logic each Tool calls into
│ └── ws_call.py # In-process WebSocket API loopback (helpers, dashboards)
├── tests/ # Flat pytest files, one per module above
│ └── property/ # Hypothesis property-based tests
├── docs/ # ARCHITECTURE.md, SECURITY.md
├── .github/workflows/ # test.yml, test-latest-ha.yml, hassfest.yml, validate.yml
├── requirements-test.txt # current HA release (default)
├── requirements-test-ha-min.txt # minimum HA from hacs.json
├── requirements-test-mariadb.txt # + MariaDB driver, for the statistics tests
└── requirements-test-common.txt # shared by both
- Python 3.14+.
requirements-test.txtpinshomeassistanttransitively to a version whose ownRequires-Pythonfloor is 3.14.2 - anything older can't install it at all.test.yml's CI matrix runs Python 3.14 exclusively for the same reason. - Git.
git clone https://github.com/YOUR_USERNAME/ha-dev-tools.git
cd ha-dev-tools
python3.14 -m venv .venv
source .venv/bin/activate
pip install -r requirements-test.txtrequirements-test.txt pins pytest-homeassistant-custom-component to an
exact version, which transitively pins an exact homeassistant version too
- this isn't a loose floor, it's deliberately reproducible. It targets the
current HA release. CI also runs the suite against the minimum HA
version in
hacs.json(requirements-test-ha-min.txt), and a nightly workflow (test-latest-ha.yml) runs it against the newest HA release, so a breaking HA change shows up before users hit it (issue #84).
When the nightly run goes red (a new HA release): bump
pytest-homeassistant-custom-component in requirements-test.txt, then
re-verify the conversation pins (gazetteer-matcher, hassil,
home-assistant-intents) against homeassistant/components/conversation/ manifest.json in the newly installed HA - the nightly job prints the
component requirements it read. When raising the minimum HA version,
update hacs.json and requirements-test-ha-min.txt together. See the
comments in the requirements files for why each pin exists, and
docs/ARCHITECTURE.md for what broke the last time a pin was assumed rather
than checked.
To test against the minimum HA locally, use a separate venv with
pip install -r requirements-test-ha-min.txt.
Statistics writes on MariaDB (issue #148): only there do HA's statistics
import and sum adjustment retry a lock-wait timeout or a deadlock, so CI's
test (MariaDB) job runs tests/test_statistics_*.py against a MariaDB
10.11 recorder, and tests/test_statistics_mysql.py (skipped on SQLite)
adds real row locks. Locally, with a MariaDB server and libmariadb-dev:
pip install -r requirements-test-mariadb.txt
PYTHONPATH=. pytest tests/test_statistics_*.py \
--dburl "mysql://USER:PASSWORD@127.0.0.1:3306/ha_test?charset=utf8mb4" \
--drop-existing-dbThe user needs to create and drop databases and to SET GLOBAL innodb_lock_wait_timeout (the lock tests shorten it to 1 s and restore it).
A note on local verification: if your local Python isn't genuinely 3.14.2+, you may not be able to install the real dependency graph at all
pipwill silently resolve to whichever olderhomeassistantrelease your interpreter can satisfy instead of erroring, which looks identical to "nothing changed" until you check what actually got installed (pip show homeassistant). When in doubt, trust CI's output over a local run of unknown provenance.
PYTHONPATH=. pytest tests/ --ignore=tests/property/ -v
PYTHONPATH=. pytest tests/property/ -v --hypothesis-show-statisticsThere's no tests/unit//tests/integration/ split - tests live flat in
tests/, one file per module, using real
pytest-homeassistant-custom-component
fixtures (a real hass instance, not a mock) rather than hand-rolled
mocks. Prefer that pattern for new tests: exercise the real Home Assistant
machinery (real WS command dispatch, real config entry setup) wherever
practical, and reserve mocks for things that genuinely can't run in a
sandbox (Supervisor, an actual MCP client).
A recurring gotcha: the default hass fixture reuses one physical
testing_config directory across test runs rather than a fresh
per-test tmp dir. Any test that writes a real file into
hass.config.config_dir needs its own cleanup (see
test_access_control.py's _clean_arm_file fixture for the pattern) or it
will leak state into whatever test runs next.
- 4-space indentation, type hints on function signatures, docstrings on public functions/classes.
- No comments explaining what code does - name things so it's obvious. Comments here explain why, especially where behavior was verified against actual Home Assistant source rather than assumed - see any existing module's docstring for the house style.
- Branch off
mainwith a descriptive name (feature/...,fix/...). - Add tests for new functionality; don't reduce coverage.
- Run the full local suite, and if you touched anything version-sensitive (HA API surface, Python version handling), verify against a genuinely fresh install rather than your accumulated local venv - see the note above.
- Add a CHANGELOG.md entry under
[Unreleased]. - Open a PR describing what changed and why.
Bug reports should include: what you expected vs. what happened,
reproduction steps, your Home Assistant and this integration's versions,
and relevant log entries (custom_components.ha_dev_tools: debug under
logger.logs in configuration.yaml gets you more detail).
Security issues: please don't open a public issue. See docs/SECURITY.md for the threat model this project defends against, then report privately via a GitHub security advisory or to the maintainer directly.
By contributing, you agree your contributions are licensed under this project's MIT License.