Add YAGNI guidance to agent instructions
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 ebe2ba6..b10654a 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