Fix agent (Cursor) rules

This commit is contained in:
2026-07-06 01:12:37 +02:00
parent 93b081d518
commit 0ff803b4c3
16 changed files with 1199 additions and 72 deletions
+45
View File
@@ -0,0 +1,45 @@
---
description: DigiRadio core non-negotiables (always on)
alwaysApply: true
---
# DigiRadio — core rules
Firmware for ESP32-S3 on ESP-IDF v5.5.x. C++23 (-std=gnu++23),
strongly typed, class-based. C++ exceptions disabled. Companion chips:
Si4684 (DAB+/FM), ADAU1701 (SigmaDSP), FSC-BT1035 (Bluetooth).
Full spec: @AGENTS.md
## Behaviour
- Blockers first: state what breaks the build or the hardware before the
solution.
- Never invent a register address, opcode, bit field, or boot sequence.
If it is not in the datasheet, say so and stop. Cite the doc section.
- No silent failure: every fallible op returns a typed error.
- Ask when a hardware invariant is unclear — don't assume.
## Code That Fits in Your Head (hard limits)
- Cyclomatic complexity <= 7 per method; at 8, decompose.
- Method fits an 80x24 box: <= 80 cols wide, <= 24 lines tall.
- One method does one thing at one level of abstraction.
- Name for intent (`tuneTo`), not mechanism (`writeReg0x30`).
- Delete before you add.
## Errors
- Typed result `std::expected<T, Error>` (native under C++23), never a
bare int code. Every timeout is an explicit error value. No C++
exceptions (disabled in ESP-IDF).
## Embedded
- No dynamic allocation in audio or ISR paths, ever.
- No virtual calls in IRAM-safe ISRs (vtables live in flash).
- Every wait has a timeout and a defined failure path.
## Version control
- Small commits, each compiles and keeps tests green.
- Commit messages: 50/72 (summary <= 50 chars, body wrapped at 72).
## Definition of Done (summary — full list in @AGENTS.md §10)
Compiles warnings-as-errors; clang-tidy clean; Apache header on every
file; doc block on every class/method; `doxygen Doxyfile` exits 0;
typed errors; host tests green; no plaintext secrets.
+59
View File
@@ -0,0 +1,59 @@
---
description: C++ typing, style, file headers and mandatory doc blocks
globs: **/*.hpp, **/*.h, **/*.cpp
alwaysApply: false
---
# C++ typing, headers and documentation
Full spec: @AGENTS.md §2 and §3.
## Typing
- No primitive obsession: a frequency, a gain, a station id are their own
types, not int/float/uint8_t. Validate at construction.
- `enum class` always; never a bare enum. No bool for mode selection
(`setBand(Band::Dab)`, not `setBand(true)`).
- Parse untrusted input (network, UART, flash) once at the boundary into
a domain type; trust it downstream.
- `const` by default; `[[nodiscard]]` on status/value returns.
- Rule of zero: wrap every HW/OS handle in RAII. No raw new/delete.
- Borrow with `std::span`, never pointer+length.
- Command Query Separation. Functional core (pure, host-testable) /
imperative shell (all I2C/SPI/UART/flash). Core includes no ESP-IDF.
## File header (every .hpp/.cpp) — Apache-2.0
```cpp
/**
* @file <name>
* @brief <one line>
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date <YYYY-MM-DD of creation>
*/
```
## Doc block — every class AND every method
Fields, in order, using Doxygen tags (aliases @dname/@pubstate defined
in Doxyfile):
```cpp
/**
* @brief <name> — one-line intent.
*
* @dname <name>
* @param <p> <meaning> // per parameter; none -> "none"
* @return <success + each error cause> // or void / n/a
* @pubstate <member state read/written; injected deps used; or none>
*
* Description: intent and contract, NOT a restatement of the code.
*
* @author Michele Bigi
* @date <YYYY-MM-DD>
*/
```
`doxygen Doxyfile` must exit 0 with an empty warnings log. Do not use
`EXTRACT_ALL = YES` to silence missing-doc warnings.
+34
View File
@@ -0,0 +1,34 @@
---
description: Chip driver rules (Si4684, ADAU1701, FSC-BT1035)
globs: **/*Driver.*, **/drivers/**, **/*Adau*, **/*Si4684*, **/*Bt1035*
alwaysApply: false
---
# Chip drivers
Full spec: @AGENTS.md §7.17.3.
## Si4684 (DAB+/FM tuner)
- Boot flow POWER_UP -> load patch -> load image -> BOOT must follow the
AN649 sequence exactly; cite the section per step in a comment.
- Stream firmware images in bounded chunks from flash via an injected
`IFirmwareSource`; never load a whole image into a heap buffer.
- Opcodes/property IDs are `enum class`; validate the CTS/STATUS byte
before trusting any payload. Public API is intent-level; registers
are private. One driver owns one SPI/I2C handle via RAII.
## ADAU1701 (SigmaDSP, RAM boot, no EEPROM)
- ESP32 writes the SigmaStudio export to DSP RAM at every boot. Model the
export as an ordered list of RegisterWrite{address, bytes} parsed in
the pure core, replayed by the shell over I2C.
- EQ and mixer runtime changes use safeload (click-free). A raw param
write while audio runs is a bug.
- Typed control surfaces: setEqBand(EqBandIndex, GainDb, FrequencyHz, Q);
setInputMix(MixSource, GainDb) with enum class MixSource {Si4684,Esp32}.
- Biquad/gain math lives in the pure core with host tests vs reference.
## FSC-BT1035 (QCC3056, AT over UART)
- Typed command builder; explicit OK/ERROR/timeout parsing.
- AT+AUXCFG=1 (Line-In) is mandatory in the init sequence and covered by
a test on the command string. Unknown responses are an error, not
ignored.
+21
View File
@@ -0,0 +1,21 @@
---
description: Network provisioning and web UI rules
globs: **/net/**, **/network/**, **/web/**, **/ui/**, **/*.html, **/*.css, **/*.js
alwaysApply: false
---
# Network config + Web UI
Full spec: @AGENTS.md §7.4.
- Provisioning: SoftAP/captive portal first, then STA. State machine is
an explicit `enum class NetState` — no ad-hoc flags.
- UI: minimal single-page app served gzipped from flash. No heavy
frameworks. Design tokens (spacing, type scale, one accent) defined
once and reused — consistency over decoration. The UI is a thin client
over a typed JSON API and holds no business logic.
- API: typed DTOs. Parse every request body into a domain type at the
boundary before use; reject malformed input with a clear status; never
partially apply.
- No raw filesystem or debug endpoint in a shipping build (guard behind a
build flag).
+19
View File
@@ -0,0 +1,19 @@
---
description: Secure storage and secret handling
globs: **/*Secret*, **/*Store*, **/secure/**, **/*Credential*, **/*Config*
alwaysApply: false
---
# Secure storage
Full spec: @AGENTS.md §7.5.
- Stores Wi-Fi SSID/password, user credentials, station list. Encrypted
at rest (NVS encryption on an encrypted partition, or a device key in
eFuse). Confirm the mechanism against current ESP-IDF security docs
before implementing.
- A `Secret` wrapper: no operator<<, no implicit conversion to a loggable
string, buffer zeroised on destruction.
- Secrets are never logged, never placed in URLs, never serialised to
plaintext. Access goes through `ISecureStore` so core and tests never
touch real flash or real keys.
+19
View File
@@ -0,0 +1,19 @@
---
description: Testing conventions (host-first, TDD for the pure core)
globs: **/test/**, **/tests/**, **/*Test*, **/*_test.*
alwaysApply: false
---
# Testing
Full spec: @AGENTS.md §8.
- Pure core is developed test-first (red -> green -> refactor):
coefficient math, blob framing, config parsing, station-list logic —
all host-tested, zero hardware.
- Arrange-Act-Assert; one behaviour per test; names state the behaviour
(`tuneTo_rejectsFrequencyOutsideFmBand`).
- Fakes over mocks for driver interfaces; assert on observable behaviour,
not internal call order.
- Hardware-in-the-loop tests are separate, explicitly marked, and never
block the host suite.