From c10c06eeb5ca7dbc49e7edf07a841bebbdc99b94 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:44:33 +1200 Subject: [PATCH] [ci] Add code-review agent skill for GitHub Copilot (#19370) --- .agents/skills/code-review/SKILL.md | 116 ++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .agents/skills/code-review/SKILL.md diff --git a/.agents/skills/code-review/SKILL.md b/.agents/skills/code-review/SKILL.md new file mode 100644 index 0000000000..f0c2891ba4 --- /dev/null +++ b/.agents/skills/code-review/SKILL.md @@ -0,0 +1,116 @@ +--- +name: code-review +description: Review guidance for ESPHome pull requests. Use this when reviewing a pull request that changes ESPHome Python, C++, or component code, to check it against the project's coding conventions, embedded-systems memory rules, testing requirements, and breaking-change policy. +--- + +# Reviewing ESPHome pull requests + +ESPHome parses YAML into C++ firmware for memory-constrained microcontrollers +(ESP32, ESP8266, RP2040, LibreTiny). Review changes with that in mind: RAM and +flash are scarce, and code runs unattended for months. + +`AGENTS.md` in the repository root is the full contributor guide and the +authority when it disagrees with this summary. The developer documentation at +https://developers.esphome.io explains the component lifecycle and the reasoning +behind these rules. This skill lists the concrete things worth flagging in a +review; read `AGENTS.md` for the detail behind any item. + +Only raise findings that the diff actually introduces or changes. Do not ask for +drive-by cleanup of pre-existing code the PR did not touch. + +## Memory and embedded constraints (highest value) + +Heap allocation after `setup()` is treated as a reliability bug, not a +performance nit, because it fragments a small shared heap. Flag: + +- New heap allocation on a hot path or after setup that could be avoided. +- `std::vector` where the size is known at compile time (use `std::array`, or + `StaticVector` when a `push_back` API is needed) or fixed at runtime + init (use `FixedVector`). +- Listener / child-entity registration lists stored as `std::vector`; these have + a compile-time-known count and should use `cg.slot_counter()` plus + `StaticVector`. +- `std::vector` for a byte buffer that never grows: prefer + `std::unique_ptr` or `std::array`. +- `std::map` / `std::set` / `std::unordered_map` for small datasets (1-16 + elements): a `std::vector` of a small struct with linear search is lighter. +- `std::deque` anywhere: it allocates 512-byte blocks and should be avoided. +- `std::string` storing a value set once from config: prefer `StringRef` (the + literal already lives in flash). +- `std::string` / `std::to_string` / string-returning helpers on hot paths where + a buffer or view API exists. + +## C++ conventions + +- Include what you use: a file referencing a symbol must include the header that + declares it, even if it currently arrives transitively. New or changed uses of + a symbol need the matching include. +- Prefix all member access with `this->`. +- Naming: `lower_snake_case` for functions/methods/variables, `UpperCamelCase` + for classes/structs/enums, `UPPER_SNAKE_CASE` for namespace-scope constants, + trailing underscore on protected/private fields. +- `enum class` values must be prefixed with the enum name in `UPPER_SNAKE_CASE` + (e.g. `UARTFlushResult::UART_FLUSH_RESULT_SUCCESS`). Bare names like `SUCCESS`, + `FAIL`, or `OK` collide with SDK macros on some platforms and break the build. +- Prefer `const`/`enum` over `#define`; `#define` is only for conditional + compilation and code-generation sizes. +- Never call `millis()` in a `loop()` body; use + `App.get_loop_component_start_time()`. A rate-limit gate below ~16 ms (the loop + period) does nothing. +- Pick the timing primitive by cadence: gated `loop()` under 250 ms, + `set_interval` at 500 ms and above. +- Do not override a base method to return the value it already returns (e.g. + `get_setup_priority()` returning `setup_priority::DATA`). +- Wrap string literals passed as printf `%s` args in `LOG_STR_LITERAL()`. +- Required, invariant dependencies should be constructor parameters, not setters. +- Callback registration methods must be templated (`template`), not + typed as `std::function`, so lightweight forwarders avoid a heap allocation. +- Two-space indent, `using` over `typedef`, wrap at 120 columns. + +## Python conventions + +- Type-annotate every new function signature (params and return), new dataclass + fields, and new module-level variables. Import `ConfigType` from + `esphome.types`. +- Use the walrus operator to avoid a double lookup, e.g. + `if (blah := config.get(CONF_BLAH)) is not None:`. +- Reuse existing validators from `config_validation.py` (`cv.rename_key`, + `cv.has_exactly_one_key`, etc.) via `cv.All(...)` instead of hand-rolling. +- `esphome/const.py` is frozen: no new `CONF_` constants there. Define them in + the component's own `.py`, or in `esphome/components/const/__init__.py` when + shared. The same constant defined in three or more component files fails CI. +- State that must persist during code generation goes in `CORE.data` namespaced + under the component `DOMAIN` (a `@dataclass`), not module-level mutable globals. +- Prefer callback-based triggers via `build_callback_automation()`; only use a + `Trigger` subclass when the forwarder needs mutable state. + +## Testing and coverage + +- New and changed lines and branches need test coverage, including defensive + early-returns, error paths, and no-op guards. A mocked-out function is not + covered; exercise the real call path too. +- Component YAML tests live in `tests/components//`. Never define + buses (uart, i2c, spi, modbus) directly in a test file: pull them from + `tests/test_build_components/common/` through dict-style `packages:` so CI can + group builds. List-style packages or top-level merge keys block grouping. +- Config-only checks use the `validate.*.yaml` prefix; compiled checks use + `test.*.yaml`. + +## Breaking changes and public API + +- Base classes under `esphome/core/` and documented config options are public + API. Undocumented `public` members of a component are internal. +- A breaking change needs justification, a migration path in the PR description, + and a deprecation window where feasible (`ESPDEPRECATED` in C++, + `cv.rename_key(..., removed_in=...)` in Python). Changing a codegen-injected + lambda signature is not a breaking change. + +## Process and PR hygiene + +- PR titles start with a `[tag]` prefix: the component name (e.g. `[uart] ...`) + or `[core]` for shared code. +- Prose in docs, comments, and commit messages should be plain English. Keep + inline comments short and only where the code is not self-explanatory; do not + restate what the code says. +- Verify the PR fills out `.github/PULL_REQUEST_TEMPLATE.md` and adds + `CODEOWNERS` entries for a new component.