Merge pull request #3888 from hathach/claude/codex-default-reviewer
validate: make the Claude review opt-in
diff --git a/.claude/skills/pre-pr/SKILL.md b/.claude/skills/pre-pr/SKILL.md
index 56db5a5..81c2330 100644
--- a/.claude/skills/pre-pr/SKILL.md
+++ b/.claude/skills/pre-pr/SKILL.md
@@ -11,45 +11,36 @@
- `BASE` = `master` unless the user names another base.
- `git diff --name-only $(git merge-base HEAD $BASE)..HEAD`
-- If NO C sources changed (only docs / `.claude/` / tools): say so, and run a minimal software-only gate — `boards = [stm32f407disco]`, no HIL — unless the user asks for more.
## 2. Map changes to boards
-- `python3 tools/ci_select.py --base $BASE test/hil/tinyusb.json` → JSON with the affected
- bsp `families`, the affected rig `boards`, and per-file `reasons`. `full: true` means a
- broad/infra change.
-- Affected families = `families` ∪ the family of every name in `boards`. Neither half is
- enough alone: only the port and bsp rules fill `families` (a class/core/example change
- reports boards but no families), and `boards` only ever names rig boards (an off-rig driver
- change — `dcd_samx7x.c` → `same7x`, `boards: {}` — would never be compiled).
+- `python3 tools/ci_select.py --base $BASE test/hil/tinyusb.json` → JSON with independent
+ top-level HIL and nested `build` selections.
+- `buildBoards`: when `build.full` is false, sample one board per `build.families`; prefer a
+ rig-roster board of that family, else the first entry in `hw/bsp/<family>/boards/`. When
+ `build.full` is true, use `stm32f407disco` + `raspberry_pi_pico` as the broad representative
+ set.
+- A non-default build target selected by the changed path must run through its existing
+ command (for example, `tinyusb_metrics` for `tools/metrics.py`). A default board sweep is
+ not evidence for a target it does not execute; report the target unverified if the current
+ workflow cannot express it.
+- `hilBoards`: derive only from the top-level HIL selection, never from `buildBoards`. When
+ top-level `full` is false, sample one board per family from the keys of top-level `boards`;
+ when it is true, use the broad representative set above. Run HIL only if the rig is reachable
+ per `.claude/skills/hil/SKILL.md`.
- A board's family is the `hw/bsp/<family>/boards/<board>/` directory holding it.
- - When `full: true`, `boards` names every rig board and carries no signal — use `families`
- alone there, plus the representative set below.
-- Sample ONE board per affected family: prefer a rig-roster board of that family, else the
- first entry in `hw/bsp/<family>/boards/`.
- Rig roster: `python3 -c "import json;print([b['name'] for b in json.load(open('test/hil/tinyusb.json'))['boards']])"`
-- Add the representative set `stm32f407disco` + `raspberry_pi_pico` when `full: true` (broad
- change — class/core/common/infra).
-- Cap at 4 boards and tell the user which families the cap dropped. A broad change affects ~20
- families, so the order matters: keep `stm32f407disco` and `raspberry_pi_pico` first whenever
- their families are affected, then fill from the remaining families (spread across vendors —
- don't let one vendor's family names take every slot). The list must NEVER end up empty —
- final fallback is `[stm32f407disco]` (full-check throws on an empty list).
-- Docs-only (`full: false`, no families, no boards) keeps §1's minimal software-only gate.
+- Cap the union of both lists at 4 boards and report what the cap dropped; spread the sample
+ across vendors.
+- Empty selections: run `pre-commit run --all-files` plus the smallest targeted check for changed tooling; skip board build/HIL.
+- A non-empty selection yielding no sampled board is a mapping failure.
-## 3. HIL boards
+## 3. Launch
-- `hilBoards` = chosen boards that are on the rig roster. This host must be able to reach the rig (per `.claude/skills/hil/SKILL.md`: host `ci`/`tusb` = local, any other host (dev PC) = remote). If none qualify, run software-only.
+If the unique union of `buildBoards` and `hilBoards` is non-empty, invoke the Workflow tool:
+`{ name: 'full-check', args: { boards: [...new Set([...buildBoards, ...hilBoards])], hilBoards, base: BASE } }`
-## 4. Launch
-
-Invoke the Workflow tool:
-
-```
-{ name: 'full-check', args: { boards: [...], hilBoards: [...], base: BASE } }
-```
-
-## 5. Summarize
+## 4. Summarize
- Per-stage table: unit / build:<board> / size / pvs, then HIL per board — pass/fail with the first error for each failure.
- If the hardware result has non-empty `locked` (a CI job held those boards): ask the user to choose **Force now** (re-invoke `hil-validate` with `force: true` for those boards; user accepts the risk of colliding with a mid-test CI job), **Keep waiting** (re-invoke `hil-validate` for them after a few minutes; ask again if still locked), or **Accept** the partial verdict. Never force without the user's answer.
diff --git a/CLAUDE.md b/CLAUDE.md
index 40d1f9c..5e29828 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -9,10 +9,10 @@
Bias toward caution over speed. For trivial tasks, use judgment.
- **Think first** — state assumptions; ask if unclear; when a choice matters, name it and recommend one rather than picking silently or surveying every option.
-- **Simplicity** — no features, abstractions, flexibility, or error handling beyond what was asked. If 200 lines could be 50, rewrite.
+- Follow YAGNI. Reuse existing code, standard-library, and native-platform features before adding dependencies or abstractions. Prefer the smallest clear solution, but never sacrifice correctness, safety, or necessary tests.
- **Surgical changes** — touch only what the task requires; match existing style; don't refactor working code; mention unrelated dead code rather than deleting it. Remove only orphans *your* changes created.
- **Goal-driven** — turn tasks into verifiable goals ("write failing test, make it pass"). For multi-step work, state a brief `step → verify` plan.
-- **Worktrees** — default to a git worktree for any branch or multi-step work; never switch the shared primary checkout's branch. Sessions run concurrently: switching the primary checkout mid-flight disrupts other sessions and can silently point a review, build, or commit at the wrong diff. Only trivial one-shot fixes may skip this. Standard location: `.worktrees/<branch-name>` at the repo root (gitignored), e.g. `git worktree add .worktrees/my-branch -b my-branch`. In a new worktree, symlink the dependency dirs (`lib/*`, `hw/mcu/*`, `tools/linkermap` — the keys of `deps_all` in `tools/get_deps.py`) to the primary checkout instead of re-cloning them; only if the branch needs a different dep revision, replace that one symlink with a real dir and run `get_deps.py` for it.
+- **Worktrees** — For branch or multi-step work, run `git worktree add .worktrees/<branch> -b <branch>`; never switch the primary checkout. Symlink the `deps_all` paths from `tools/get_deps.py` to the primary checkout; replace only a dependency that needs a different revision, then run `get_deps.py` for it.
## Claude and Codex Collaboration