Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
54d9416
add Waveshare ESP32-S3-Tiny-N8R8 board
stolen May 26, 2026
0207d70
Merge pull request #475 from stolen/waveshare_esp32_s3_tiny_n8r8
hathach Jun 2, 2026
b0439df
Add LilyGO T-Beam 1W
lyusupov Jun 9, 2026
34adc54
Merge pull request #477 from lyusupov/tbeam_1w
hathach Jun 10, 2026
62b3fb9
update senseBox MCU Eye name and flashsize
PaulaScharf Jun 16, 2026
14bec07
no hyphen
PaulaScharf Jun 16, 2026
9af754c
Merge pull request #478 from sensebox/master
hathach Jul 3, 2026
222cd03
Merge pull request #479 from jdavis-codes/add-waveshare-esp32-s3-eth-…
jdavis-codes Jul 31, 2026
3484abe
espressif: add ESP32-S3-DevKitC-1-N32R16 board
mikeysklar Aug 12, 2026
6ca7559
Merge pull request #482 from mikeysklar/esp32s3-devkitc-1-n32r16
hathach Aug 13, 2026
43c50cd
Fix Metro M7 factory SD test path
ladyada-eagleclaw Oct 9, 2026
fc5799c
Keep factory UF2 in application flash
ladyada-eagleclaw Oct 9, 2026
fa33587
Release factory analog pins before ADC conversion
ladyada-eagleclaw Oct 9, 2026
04f5ac4
Preserve mixed line endings in factory Arduino helpers
ladyada-eagleclaw Oct 9, 2026
577980a
Make Metro M7 AirLift factory apps usable with USB-host testers
ladyada-eagleclaw Oct 9, 2026
a996f5e
Merge pull request #484 from ladyada-eagleclaw/metro-m7-factory-test
ladyada Oct 11, 2026
42ce79b
Avoid AirLift fixture pin conflicts during programming and tests
ladyada-eagleclaw Oct 11, 2026
165bff8
Require an AirLift Wi-Fi scan for factory pass and add an idle demo
ladyada-eagleclaw Oct 11, 2026
f5de380
Add agent instructions and a build contract (#488)
hathach Oct 11, 2026
e1d27a8
Merge pull request #486 from ladyada-eagleclaw/metro-m7-airlift-apps
ladyada Oct 11, 2026
9718b8d
Add CodeRabbit config and README badges
hathach Oct 12, 2026
3d574a4
Merge pull request #499 from adafruit/add-coderabbit
hathach Oct 12, 2026
9d4b382
Add a local HIL contract and fix blank CURRENT.UF2 on mimxrt10xx (#507)
hathach Oct 12, 2026
fb69a92
Support verified i.MX RT provisioning through full-speed USB hosts
ladyada-eagleclaw Oct 9, 2026
dcf2318
Preserve upstream USB descriptor line endings
ladyada-eagleclaw Oct 9, 2026
28831b9
Enable Metro M7 flash quad mode through one-byte SR2 writes
ladyada-eagleclaw Oct 9, 2026
c13e26c
Set CodeRabbit review profile to chill
hathach Oct 13, 2026
bdb9f84
Merge pull request #517 from adafruit/coderabbit-chill
hathach Oct 13, 2026
2b558bd
Add an ESP32-S3 HIL battery and install through the build's flash tar…
hathach Oct 13, 2026
e7148ac
espressif: build with ESP-IDF v6.1 (#497)
hathach Oct 13, 2026
bb54352
Ship dfu_runtime as an example app and build it on mimxrt10xx (#519)
hathach Oct 13, 2026
8e75969
Add ESP32-P4 Function EV board (#532)
hathach Oct 14, 2026
ded0c41
build: report CMake errors and skip ninja progress lines in firstErro…
hathach Oct 14, 2026
c04cd72
Fix D+ pin mask in S3 Touch1200 USB-Serial-JTAG switch (#537)
hathach Oct 14, 2026
3c5e10c
espressif: keep tinyuf2.bin inside the uf2 partition under ESP-IDF 6.…
hathach Oct 14, 2026
abf80dd
Build every port with CMake (#538)
hathach Oct 14, 2026
0cf4e91
espressif: use ESP-IDF v6 sdkconfig names and esptool v5 spellings (#…
hathach Oct 14, 2026
5cad567
Build the FS and HS configurations from one descriptor macro
hathach Oct 14, 2026
34324fa
Handle the tester erase request in the mimxrt10xx port
hathach Oct 14, 2026
f7aaf66
Merge pull request #485 from ladyada-eagleclaw/metro-m7-usb-and-erase
hathach Oct 14, 2026
682189c
CI: build only the boards a pull request affects (#540)
hathach Oct 14, 2026
f96e428
espressif: drop CONFIG_VFS_SUPPORT_IO=n from the apps (#539)
hathach Oct 14, 2026
4521843
Run the AirLift idle Wi-Fi scan every two minutes
ladyada-eagleclaw Oct 11, 2026
0246464
Report which NINA SPI handshake or response field failed
ladyada-eagleclaw Oct 11, 2026
f30b9cf
Use hardware SPI and flush AirLift diagnostics
ladyada-eagleclaw Oct 11, 2026
36f7a4e
Bound ADC waits and drain AirLift diagnostic output
ladyada-eagleclaw Oct 11, 2026
170cb92
Use validated Metro LPSPI source frequency
ladyada-eagleclaw Oct 11, 2026
8b57802
Trace AirLift SPI replies through bounded ESP UART diagnostics
ladyada-eagleclaw Oct 11, 2026
30c19a6
Remove AirLift investigation output from shipping demo
ladyada-eagleclaw Oct 12, 2026
2301723
Show WiFi signal strength and encryption with LF-CR output
ladyada-eagleclaw Oct 12, 2026
6b00118
Keep optional WiFi detail failures separate from scan results
ladyada-eagleclaw Oct 12, 2026
aea5f49
CI: run pre-commit on every master push (#542)
hathach Oct 15, 2026
d6f26f5
Merge pull request #498 from ladyada-eagleclaw/metro-m7-airlift-two-m…
hathach Oct 15, 2026
289ae2a
Bump tinyusb to ea1d867 and adapt to its API changes
hathach Oct 15, 2026
0b1c26d
Merge pull request #549 from adafruit/bumpup-tinyusb
hathach Oct 15, 2026
b135710
Add stm32f411ve_discovery to the HIL (#547)
hathach Oct 15, 2026
ba97530
Add ESP32-S31 Function CoreBoard 1 (#554)
hathach Oct 15, 2026
c5c0b3f
lpc55: USB1 high speed, CMake CI, fix UF2 copy on rev 0A (#543)
hathach Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents
49 changes: 49 additions & 0 deletions .claude/skills/build/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
name: build
description: The TinyUF2 build contract. Use when a change must be verified by building, when an agent needs the boards a diff affects, or when a workflow asks for "the project's build". Resolves scope to boards, builds each the way CI does in a private dir, runs the ghostfat self-test when its inputs change, reports pass/fail with the first error.
---

# Build contract

`scripts/check_build.py` is the entry point for verifying a change; every agent and workflow that does so calls it rather than composing make, cmake or idf.py lines. A debug build for a probe session is not verification and uses the port's own build directly (below).

```bash
B=.claude/skills/build/scripts/check_build.py
python3 $B --scope <changed paths or dirs> # boards a change affects
python3 $B --base master # same, from the branch diff
python3 $B --base master --worktree # same, plus uncommitted and untracked changes
python3 $B --select-only (--scope ... | --base REF [--worktree] | --diff-file FILE) [--hil-config ROSTER] # the selection alone
python3 $B --board feather_stm32f405_express [--board ...]
python3 $B --board lpcxpresso55s69 -D LOG=2 # a define, passed to the board's backend
python3 $B ... --shared # keep cmake-build/cmake-build-<board>, the dir a flash or HIL run uses
python3 $B --board imxrt1060_evk --backend cmake --shared --hil-config test/hil/local.json # the HIL's receipt
python3 $B ... --idf local # exported ESP-IDF instead of CI's docker image
```

The last stdout line is JSON. An agent acts on `pass`; `firstError`, and `error` on exit 2, naming the cause or the remedy; `boards[]` (`status` `ok`, `failed` or `error`, `log`, `artifacts`, `notImplemented`); `built`, the artifacts of `--shared` roots, the only ones kept after a pass, with each one's hash in `boards[].sha256`; `nothingToBuild` and `uncovered`, each path with its reason; and `idf.matchesCi`. `input` and `defines` record what a build used (HEAD, `dirty`, submodule and `get_deps` dependency commits, `dirtyInputs` for any of those with local changes, `inputsDirtiedByBuild` for any that changed or moved while building, `.` for the checkout itself): the HIL contract binds firmware to this record. `paths` gives every changed path's `rule` and `reason`, the answer to why a path selected what it did. Exit 0 pass, 1 a build, self-test or test failed, 2 usage, resolution or a missing prerequisite, 3 an uncovered path; 2 outranks 1, and 1 outranks 3. `-v` streams the build to stderr; each build's log is `<buildDir>/check_build.log`.

`--select-only` prints the selection (`build.{ports,builds,ghostfat,tests,paths,ci}`) and stops before any toolchain check: what any agent or CI step reads for "what does this change affect". With `--hil-config <the host's roster>` it adds `hil.{boards,full,uncovered}`: the roster boards the change puts under the HIL battery, and as uncovered each selected port with no roster board on this host and each app source (`apps/`, `ports/<port>/apps/`) but `dfu_runtime`'s, which the battery never runs. `dfu_runtime` selects the esptool roster boards of the ports that build it, and is uncovered once for each port that builds it without one.

## Reporting

An agent that reports `buildOk` and `notes` (agentrc's `code-writer`) maps the JSON this way: `buildOk` is `pass`. A pass with no board, self-test or test run is "nothing to build": quote each `nothingToBuild` reason in `notes`, so nobody mistakes it for a build that ran. Exit 3 is `buildOk` false with each `uncovered` reason quoted verbatim: that path is firmware no CI build compiles, and a human adds coverage or accepts the gap. Exit 1 or 2 quotes `firstError` (and `error`). A pass on `--idf local` whose `idf.matchesCi` is false is supplemental evidence only: say so in `notes`; acceptance needs CI's IDF.

## Judgment

- **Selection is the script's**, first match per path, and `paths[].reason` says why. A mixed change takes the union, and an uncovered path fails it whatever else built. The policy behind the rules:
- A change to a port, board, app or dependency builds the boards that compile it: a board's own files that board; other port files the port's `REPRESENTATIVES` (rig boards first, every ESP32 target the port's boards use, both i.MX RT10xx and RT1170); an app or dependency the representatives of the ports whose CI backend compiles it.
- Shared code (`src/`, `lib/`, the shared build files) and anything unclassified builds every port's representatives, plus the ghostfat self-test when a ghostfat input changed.
- A change to how boards build (this script, `build.yml`, `build_util.yml`, `ci_set_matrix.py`, `.github/actions/`) runs the contract's own tests and one smoke build per backend, and a change to this script the HIL battery on every roster board, since the HIL runner imports it; no firmware build verifies workflow syntax or orchestration. A change to the contract's tests runs the tests only.
- CodeQL's make build of feather_stm32f405_express counts as coverage for the make files it reads; `ports/make.mk` and `ports/rules.mk` select the make ports' representatives and that build.
- Firmware no CI build compiles is uncovered: `ports/template_port/`, `ports/ch32v20x/` (`--board ch32v203_r0_1v0` builds it on request with make, with a note), a build file no CI build of the port reads (a make port's CMake files, a cmake port's make files but CodeQL's, Espressif's `Makefile`), a stub app.
- Docs and agent, editor and repo metadata build nothing.
- `build.ci` is CI's selection from the same rules (`.github/workflows/ci_set_matrix.py` reads it on a pull request): every board a change reaches, not the representatives. A board's own files select that board; a port file, dependency, shared app or `make.mk`/`rules.mk` every board of the ports it reaches; a port app the boards that enable it. Shared code and a change to how boards build select `full`, every board and the ghostfat self-test; a ghostfat input the self-test alone. Tests, docs, CodeQL's build and uncovered paths select nothing.
- **Builds are CI's.** Each port uses the backend `build.yml` gives it (`BACKENDS` in the script; `test_build.py` fails when the two disagree): make `all self-update`, then `copy-artifact`, then every `ports/<port>/apps/*` but `self_update`; cmake `-G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel`, which also builds each app the port's `CMakeLists.txt` adds with `family_add_app()` as its own project; Espressif `idf.py build`. A port moves from make to cmake in `build.yml` once a board of it passes the HIL battery with cmake. A board passes only with the artifacts its build must produce (`expected_artifacts()`): the bootloader images and the self-update and app images the port builds (cmake: its `family_add_app()` calls enabled for the board). A port without self-update, or a stub app, is listed in `notImplemented`, which is not a failure.
- **`--backend cmake`** is for the HIL's receipt only, with `--board` and `--shared`: on a make port it is no CI evidence, and it is how such a port's board gets the HIL run that moves the port to cmake. Espressif is refused. `-D` takes the cmake spellings: `LOGGER=RTT`, not `rtt`; `DEBUG` is ignored, the build stays MinSizeRel; `LOG` (default 0, as in make) sets `TUF2_LOG` and `CFG_TUSB_DEBUG`; raw flags go through `CFLAGS_CLI`.
- **A HIL record** (`--board --shared --hil-config <roster>`) binds the build's own flash target to the board's roster flasher: a J-Link board is configured with `JLINK_OPTION=-USB <probe serial> -ExitOnError 1 -NoGui 1` (so `tinyuf2-jlink` selects that probe and fails non-zero), an esptool board's ESP-IDF `flash` target takes the bridge's tty at flash time. `boards[].flash` holds the target, the roster flasher, the target's generated command (checked to name that probe or chip and the build's own image), the configure defines that bound it and the build-tree files it reads (in `sha256` too); a rebuild and a HIL flash hold the build dir's lock (`<build dir>.lock`). Another flasher, a roster `args` other than `-device <name>` or `--chip <chip>`, or a `-D` of `JLINK_OPTION`, `OPENOCD_SERIAL`, `OPENOCD_OPTION` or `ESPPORT` is refused. Before flashing, the HIL dry-runs that target under the lock and refuses any command but the flasher and, on ESP-IDF, the generated ones that write no flash input (its bootloader subproject dry-run too); ESP-IDF re-runs CMake after any commit (its `git describe` dependency), so rebuild the record on the commit under test.
- **ESP-IDF** builds in CI's image, `espressif/idf:<build.yml's toolchain_version>`, through docker by default; a missing image is exit 2 naming the `docker pull`. `--idf local` uses the exported IDF and records its version and `matchesCi`: evidence from a different IDF is supplemental, not acceptance.
- **ghostfat** builds each `ports/test_ghostfat/boards/*` config natively with cmake and runs its `selftest` target, which compares the generated image with the config's known-good one, in the private dir, one config at a time: `huge` needs about 3 GiB of disk while it runs. A passing config's dir is removed; a failing one keeps its log.
- **Private dirs for parallel agents.** Each build starts from an empty `cmake-build/cmake-build-agent-<pid>-<board>` (with `-<backend>` when a board builds with another backend than its port's) and is removed when it passes (`removed`; its `artifacts` say what it produced, nothing is left to flash); a failed one is kept for its log. Nothing is written into the source tree: make takes `BUILD_ROOT=<dir>`, cmake and idf `-DARTIFACT_PATH=<dir>/_bin`. `--shared` rebuilds `cmake-build/cmake-build-<board>` from clean and keeps it for flashing; it needs exclusive ownership of that board.
- **Dependencies are never fetched here.** `lib/tinyusb` and `lib/uf2` are submodules (`git submodule update --init lib/tinyusb lib/uf2`); a port's `tools/get_deps.py` rows must exist. In a worktree, symlink them from the primary checkout (CLAUDE.md), never run `get_deps.py` through a symlink: it runs `git reset --hard` in each dependency.
- **Debug builds** for a probe session use the port's backend directly: make `make -C ports/<port> BOARD=<b> DEBUG=1 LOG=2 LOGGER=rtt all` (`LOGGER=swo` too); cmake `-DCMAKE_BUILD_TYPE=Debug -DLOG=2 -DLOGGER=RTT` (only `RTT` adds the RTT sources). Flash targets: make `flash-jlink`, `flash-pyocd`, `flash-stlink`, `flash-uf2`; cmake `tinyuf2-jlink`, `-stlink`, `-openocd`, `-pyocd` (lpc55, kinetis_k32l2), `-dfu-util` (stm32l4), and `<app>-uf2` for each app; Espressif `idf.py -C ports/espressif -DBOARD=<b> flash`.
- **Printing a command is not verification.** A verdict is the JSON of a run that happened; an agent that cannot run the script reports the build as not run.
Loading
Loading