Thanks for your interest in NeverDry — a Home Assistant custom integration for ET-based smart irrigation. Contributions of all kinds are welcome: bug reports, fixes, features, documentation, and help verifying the science.
- Report a bug or request a feature — open a GitHub issue with clear steps / context. For irrigation behaviour, include your delivery mode and valve type.
- Send a pull request — see the workflow below.
- Improve the docs — user/developer manuals live in
docs/; the engineering design notes live in the project documentation set (see Understanding the architecture below). - Help verify the science — the bibliography behind the model is being reviewed claim-by-claim against primary sources. The method is documented and reproducible; picking up a few references is a great first contribution.
NeverDry targets Python 3.11 / 3.12 and runs inside Home Assistant.
# clone your fork, then from the repo root:
# (the project pins dependencies via uv.lock; uv or pip both work)
pip install -r requirements_test.txt # pytest + pytest-asyncio
pip install pytest-cov ruff bandit # tooling used by CI
# Home Assistant must be importable to run the test suite.The integration code lives in custom_components/never_dry/; tests in tests/.
CI will reject a PR that fails any of these, so run them locally first. They
mirror .github/workflows/lint.yml and tests.yml:
# 1. Lint + format (CI runs BOTH check and format --check)
ruff check custom_components/never_dry/ tests/
ruff format custom_components/never_dry/ tests/ # then re-run with --check
# 2. Tests (must keep coverage >= 75%)
python -m pytest tests/ -v
# 3. Security scan
bandit -r custom_components/never_dry/ --severity-level medium --confidence-level mediumA pre-commit config is provided (ruff --fix, ruff-format, bandit):
pip install pre-commit && pre-commit install
⚠️ Never runruff formatonmanifest.jsonor other JSON files. It adds trailing commas and corrupts the JSON, which breaks the integration and CI. The format command above is scoped to Python paths on purpose.
CI also validates the integration with hassfest and HACS; keep
manifest.json and hacs.json valid.
- Branch off
mainwith a descriptive name (e.g.fix/valve-close-leak,feat/valve-entity-support). Do not commit directly tomain. - Keep commits focused; write commit messages and PR descriptions in English.
- Open a PR against
main. Describe what and why, and link any related issue (Closes #123). Make sure all CI checks pass. - For new behaviour, add or update tests. For changes to irrigation/valve safety, a non-regression test of existing behaviour is expected.
- Enforced by ruff (config in
pyproject.toml): line length 120, targetpy311, rule setsE,W,F,I,B,S,UP,SIM,RUF. - Match the style and altitude of the surrounding code; prefer clarity over cleverness.
To make a meaningful change, read the design notes — start with the index:
docs/design/README.md— design & engineering notes in reading order (architecture → direction → science → testing).docs/developer_manual.md— architecture overview, formulas, module map.docs/ha_integration_guide.md— Home Assistant integration patterns.
The actuator-abstraction direction
(docs/design/actuator-abstraction.md)
is a Draft open for input on #74 — see How we record design decisions below.
Significant or cross-cutting changes are captured as a single design note
whose Status field moves through a lifecycle. RFC and ADR are not separate
file types — they are phases of the same document:
Draft → internal proposal, not yet circulated for comment
→ Proposed → open for comment (the "RFC" phase — usually on a GitHub issue)
→ Accepted → the decision is settled (this is the "ADR")
Plus terminal states: Rejected / Withdrawn / Deferred, and later
Superseded / Deprecated. A note is never marked Accepted while the
decision is still open for input.
What this means for you: for a large or architectural change, open or comment
on the relevant design note before sending a big PR — it avoids rework. For
example, the actuator-abstraction direction
(docs/design/actuator-abstraction.md,
currently Draft) grew out of @fpytloun's proposal
in #74 — community input like that is exactly how the bigger decisions get
shaped, and it's much appreciated. Small, self-contained fixes can go straight to
a PR.
NeverDry is maintained by one person. These roles exist to remove friction, not to build a hierarchy, and none of them is a claim on your time.
Contributor — anyone who has had a commit merged, reported a bug, tested a pre-release, or shaped a design note. This is not granted and there is nothing to apply for: if you have done any of that, you already are one.
Triage — an invited role (GitHub's triage permission). It allows labelling,
assigning, closing and reopening issues, and being formally requested as a
reviewer. It carries no write access: you cannot push or merge anything. It is
offered to people already doing this work informally — mostly so that asking you
for a review stops requiring a hand-written ping.
Write — push and merge rights. Offered to contributors with a track record of merged changes who would rather carry work through themselves than hand it over.
- It is an offer, not an assignment. No expected workload, no rota, no response-time expectation.
- Declining costs nothing and changes nothing about how your contributions are received.
- It is revocable from both sides, at any time, without it being a statement.
- The invitation will always say what specifically prompted it. A role handed out by volume rather than by contribution is worth nothing to the person receiving it.
You get asked first, in a thread where you are already active. A GitHub invitation is only sent after you say yes.
Please do not open public issues for security vulnerabilities. See
SECURITY.md for private disclosure.
By contributing, you agree that your contributions are licensed under the project's LICENSE.