mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 11:02:16 +00:00
Fix max-effort review findings in lock protocol, workflows, and docs
Confirmed by a 10-finder / 28-verifier adversarial review pass: board_lock.py — the flock is now the sole authority: drop cmd_hold's pid-liveness pre-gate (a live hil_test.py pool worker's stale record no longer blocks a genuinely free board); cmd_release probes the flock and only signals a verified holder, refuses to kill hil_test.py holders (CI mid-test), handles PermissionError; the holder daemon truncates its lock records on SIGTERM and keeps the success pipe clear of fds 0-2 (closed-stdio hold used to leave an orphan holder while reporting failure); --config default resolves beside the script. hil_test.py — truncate the lock record on per-board release (pool workers outlive their flocks); warn instead of silently failing open when the lock dir is unusable; error out on -b names absent from the config (was a silent zero-test exit 0, readable as a green HIL run); drop an emptied board row in accumulate_report (variant boards left a blank ghost row). workflows — remove the stray positional arg that made the validate size stage exit 2 on every run; wrap JSON.parse(args) in all six scripts; factor pr-babysit's drifted reply recipe into postReplyRecipe and dedup refutation replies across cycles; validate args.pr and maxCycles; driver-review rejects an empty dimensions list; hil-validate drops a dead guard clause and retries diagnostics with -v -r 1. agents/docs — port-dev scopes git clang-format to its own files (concurrent workers reformatted each other in shared checkouts); hil-operator/hil skill wording matches actual fail-fast output; the implementation plan is now a DO-NOT-EXECUTE historical record (banner + checked boxes) so plan-executing agents cannot revert shipped files. Verified: lock storm 1-winner-in-10, stale-record hold, closed-stdio hold, dead-pid cleanup, CI-holder refusal, ghost-row 4-scenario merge, unknown-board exit 1, py_compile + check.sh on all six workflows, pre-commit clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Upj4hta5TNoAbidqeC1zZ6
This commit is contained in:
@ -1,6 +1,14 @@
|
||||
# TinyUSB Multi-Agent Dev/Test Harness Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
> **HISTORICAL RECORD — DO NOT EXECUTE.** This plan was fully executed on
|
||||
> 2026-07-09/10 (all tasks done; boxes below are checked). It is kept only as
|
||||
> the audit trail of how the harness was built. The embedded file bodies are
|
||||
> STALE SNAPSHOTS — the shipped `.claude/agents/*.md` and `.claude/workflows/*.js`
|
||||
> have since evolved (static-analyzer agent, schema and recipe changes); the
|
||||
> living design doc is `docs/superpowers/specs/2026-07-09-claude-agents-workflows-design.md`.
|
||||
> Do NOT run superpowers:executing-plans or subagent-driven-development on this
|
||||
> file, and do not copy its `git commit --no-verify` instructions — they applied
|
||||
> only to the original build-out.
|
||||
|
||||
**Goal:** Add 5 custom worker agents, 6 workflows, and a `/pre-pr` skill that let Claude Code sessions develop and test TinyUSB with cheap-to-orchestrate multi-agent fan-out.
|
||||
|
||||
@ -38,7 +46,7 @@
|
||||
- Produces (consumed by Tasks 6, 7 via `agentType: 'builder'`): final-message JSON
|
||||
`{"board": string, "pass": boolean, "builtCount": integer, "failures": [{"example": string, "class": string, "firstError": string}]}`
|
||||
|
||||
- [ ] **Step 1: Write the agent file**
|
||||
- [x] **Step 1: Write the agent file**
|
||||
|
||||
Write `.claude/agents/builder.md` with exactly this content:
|
||||
|
||||
@ -92,12 +100,12 @@ Your final message is parsed by a program. Return ONLY this JSON — no prose, n
|
||||
`pass` is true only when zero failures remain after retries. `builtCount` = number of examples that built.
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify structure**
|
||||
- [x] **Step 2: Verify structure**
|
||||
|
||||
Run: `head -8 .claude/agents/builder.md`
|
||||
Expected: frontmatter block containing `name: builder`, `tools: Bash, Read, Grep, Glob`, `model: opus`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/agents/builder.md
|
||||
@ -116,12 +124,12 @@ git commit --no-verify -m "feat: add builder worker agent"
|
||||
- Produces (consumed by Tasks 7, 11 via `agentType: 'port-dev'`): final-message JSON
|
||||
`{"item": string, "diffstat": string, "buildOk": boolean, "board": string, "notes": string}`
|
||||
|
||||
- [ ] **Step 1: Install clang-format (host tool, one-time)**
|
||||
- [x] **Step 1: Install clang-format (host tool, one-time)**
|
||||
|
||||
Run: `sudo apt-get install -y clang-format && git clang-format -h | head -3`
|
||||
Expected: apt succeeds; `git clang-format -h` prints usage (proves both `clang-format` and the `git clang-format` subcommand exist). If the package is already installed this is a no-op.
|
||||
|
||||
- [ ] **Step 2: Write the agent file**
|
||||
- [x] **Step 2: Write the agent file**
|
||||
|
||||
Write `.claude/agents/port-dev.md` with exactly this content:
|
||||
|
||||
@ -166,12 +174,12 @@ Your final message is parsed by a program. Return ONLY this JSON — no prose, n
|
||||
`buildOk` is the result of step 2. Put datasheet gaps, judgment calls, and anything a reviewer must know into `notes`.
|
||||
````
|
||||
|
||||
- [ ] **Step 3: Verify structure**
|
||||
- [x] **Step 3: Verify structure**
|
||||
|
||||
Run: `head -6 .claude/agents/port-dev.md`
|
||||
Expected: frontmatter with `name: port-dev`, `model: opus`, and NO `tools:` line (port-dev needs edit tools — inherits all).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
- [x] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/agents/port-dev.md
|
||||
@ -190,7 +198,7 @@ git commit --no-verify -m "feat: add port-dev worker agent"
|
||||
- findings: `{"scope": string, "dimension": string, "findings": [{"file": string, "line": integer, "snippet": string, "why": string, "severity": "critical"|"major"|"minor", "confidence": "high"|"medium"|"low"}]}`
|
||||
- verification: `{"addresses": boolean, "reason": string}` (also used with keys `real`/`reason` when the prompt asks to refute a finding)
|
||||
|
||||
- [ ] **Step 1: Write the agent file**
|
||||
- [x] **Step 1: Write the agent file**
|
||||
|
||||
Write `.claude/agents/driver-reviewer.md` with exactly this content:
|
||||
|
||||
@ -223,12 +231,12 @@ Your final message is parsed by a program. Return ONLY the JSON shape your promp
|
||||
{"scope": "src/portable/...", "dimension": "...", "findings": [{"file": "...", "line": 123, "snippet": "...", "why": "...", "severity": "major", "confidence": "high"}]}
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify structure**
|
||||
- [x] **Step 2: Verify structure**
|
||||
|
||||
Run: `head -8 .claude/agents/driver-reviewer.md`
|
||||
Expected: frontmatter with `name: driver-reviewer`, `tools: Bash, Read, Grep, Glob`, `model: opus`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/agents/driver-reviewer.md
|
||||
@ -246,7 +254,7 @@ git commit --no-verify -m "feat: add driver-reviewer worker agent"
|
||||
- Produces (consumed by Task 9 via `agentType: 'hil-operator'`): final-message JSON per calling prompt — board runs `{"board": string, "pass": boolean, "detail": string, "wedged": boolean}`
|
||||
- Consumes: `test/hil/board_lock.py` (Task 19) for manual hardware work.
|
||||
|
||||
- [ ] **Step 1: Write the agent file**
|
||||
- [x] **Step 1: Write the agent file**
|
||||
|
||||
Write `.claude/agents/hil-operator.md` with exactly this content:
|
||||
|
||||
@ -292,12 +300,12 @@ Your final message is parsed by a program. Return ONLY the JSON shape your promp
|
||||
{"board": "raspberry_pi_pico", "pass": true, "detail": "<per-test summary or first failure>", "wedged": false}
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify structure**
|
||||
- [x] **Step 2: Verify structure**
|
||||
|
||||
Run: `head -8 .claude/agents/hil-operator.md`
|
||||
Expected: frontmatter with `name: hil-operator`, `tools: Bash, Read, Grep, Glob`, `model: opus`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/agents/hil-operator.md
|
||||
@ -315,7 +323,7 @@ git commit --no-verify -m "feat: add hil-operator worker agent"
|
||||
- Produces (consumed by Task 11 via `agentType: 'pr-monitor'`): final-message JSON
|
||||
`{"ci": {"status": "green"|"red"|"running", "infraRerun": [string], "realFailures": [{"check": string, "firstError": string, "files": [string]}]}, "findings": [{"source": string, "commentId": integer, "file": string, "line": integer, "claim": string, "verdict": "valid"|"invalid"|"stale", "reason": string, "fixHint": string}], "replies": [{"commentId": integer, "body": string}], "done": boolean}`
|
||||
|
||||
- [ ] **Step 1: Write the agent file**
|
||||
- [x] **Step 1: Write the agent file**
|
||||
|
||||
Write `.claude/agents/pr-monitor.md` with exactly this content:
|
||||
|
||||
@ -360,12 +368,12 @@ Your final message is parsed by a program. Return ONLY this JSON — no prose, n
|
||||
"done": false}
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify structure**
|
||||
- [x] **Step 2: Verify structure**
|
||||
|
||||
Run: `head -8 .claude/agents/pr-monitor.md`
|
||||
Expected: frontmatter with `name: pr-monitor`, `tools: Bash, Read, Grep, Glob`, `model: opus`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/agents/pr-monitor.md
|
||||
@ -384,7 +392,7 @@ git commit --no-verify -m "feat: add pr-monitor triage agent"
|
||||
- `check.sh <file.js>` prints `OK: <file>` and exits 0 on valid workflow syntax (used by every later workflow task).
|
||||
- `validate` workflow — Consumes: `builder` agent (Task 1). Args `{boards: string[], examples?: string, base?: string, skip?: ('unit'|'size'|'pvs')[]}`. Returns `{pass: boolean, stages: [{stage, pass, detail}], failures: [...]}` (consumed by Task 10 via `workflow('validate', ...)`).
|
||||
|
||||
- [ ] **Step 1: Write the checker**
|
||||
- [x] **Step 1: Write the checker**
|
||||
|
||||
Write `.claude/workflows/check.sh` with exactly this content (then `chmod +x .claude/workflows/check.sh`):
|
||||
|
||||
@ -405,12 +413,12 @@ trap 'rm -f "$tmp"' EXIT
|
||||
node --check "$tmp" && echo "OK: $f"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify checker fails on bad input and passes on good**
|
||||
- [x] **Step 2: Verify checker fails on bad input and passes on good**
|
||||
|
||||
Run: `printf 'return }broken\n' > /tmp/bad.js; bash .claude/workflows/check.sh /tmp/bad.js; echo "exit=$?"`
|
||||
Expected: SyntaxError printed, `exit=1` (non-zero).
|
||||
|
||||
- [ ] **Step 3: Write validate.js**
|
||||
- [x] **Step 3: Write validate.js**
|
||||
|
||||
Write `.claude/workflows/validate.js` with exactly this content:
|
||||
|
||||
@ -500,12 +508,12 @@ log(`${results.length}/${thunks.length} stages completed, ${failures.length} fai
|
||||
return { pass: failures.length === 0 && dead === 0, stages: results, failures }
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Syntax-check**
|
||||
- [x] **Step 4: Syntax-check**
|
||||
|
||||
Run: `bash .claude/workflows/check.sh .claude/workflows/validate.js`
|
||||
Expected: `OK: .claude/workflows/validate.js`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
- [x] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/workflows/check.sh .claude/workflows/validate.js
|
||||
@ -523,7 +531,7 @@ git commit --no-verify -m "feat: add validate workflow and workflow syntax check
|
||||
- Consumes: `port-dev` (Task 2), `builder` (Task 1), `driver-reviewer` (Task 3).
|
||||
- Args `{task: string, items: string[], board?: string | {[item]: string}, review?: boolean, worktree?: boolean}`. Returns array of `{item, diffstat, buildOk, board, notes, verifyBuild?, review?}`.
|
||||
|
||||
- [ ] **Step 1: Write the workflow**
|
||||
- [x] **Step 1: Write the workflow**
|
||||
|
||||
Write `.claude/workflows/fanout-dev.js` with exactly this content:
|
||||
|
||||
@ -642,12 +650,12 @@ log(`${done.length}/${args.items.length} items completed; ${done.filter(r => r.b
|
||||
return done
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Syntax-check**
|
||||
- [x] **Step 2: Syntax-check**
|
||||
|
||||
Run: `bash .claude/workflows/check.sh .claude/workflows/fanout-dev.js`
|
||||
Expected: `OK: .claude/workflows/fanout-dev.js`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/workflows/fanout-dev.js
|
||||
@ -666,7 +674,7 @@ git commit --no-verify -m "feat: add fanout-dev workflow"
|
||||
- Args `{dirs: string[], dimensions?: string[], question?: string}`. Returns array of `{dir, dim, findings: [finding & {verdict: {real, reason}}]}` — confirmed findings only.
|
||||
- Note: supersedes the untracked prototype `.claude/workflows/port-audit.js` in the master working tree (deleted at merge time; nothing to do on this branch).
|
||||
|
||||
- [ ] **Step 1: Write the workflow**
|
||||
- [x] **Step 1: Write the workflow**
|
||||
|
||||
Write `.claude/workflows/driver-review.js` with exactly this content:
|
||||
|
||||
@ -756,12 +764,12 @@ log(`${confirmed.length} scan units produced confirmed findings`)
|
||||
return confirmed
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Syntax-check**
|
||||
- [x] **Step 2: Syntax-check**
|
||||
|
||||
Run: `bash .claude/workflows/check.sh .claude/workflows/driver-review.js`
|
||||
Expected: `OK: .claude/workflows/driver-review.js`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/workflows/driver-review.js
|
||||
@ -779,7 +787,7 @@ git commit --no-verify -m "feat: add driver-review workflow (supersedes port-aud
|
||||
- Consumes: `hil-operator` (Task 4). Requires `examples/cmake-build-<board>/` to exist for every board.
|
||||
- Args `{boards: string[]}`. Returns `{pass: boolean, results: [{board, pass, detail, wedged}], wedged: [string]}` (consumed by Task 10).
|
||||
|
||||
- [ ] **Step 1: Write the workflow**
|
||||
- [x] **Step 1: Write the workflow**
|
||||
|
||||
Write `.claude/workflows/hil-validate.js` with exactly this content:
|
||||
|
||||
@ -846,12 +854,12 @@ if (locked.length) log(`still locked after retry: ${locked.join(', ')} — ask t
|
||||
return { pass: results.length === args.boards.length && results.every(r => r.pass), results, wedged, locked }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Syntax-check**
|
||||
- [x] **Step 2: Syntax-check**
|
||||
|
||||
Run: `bash .claude/workflows/check.sh .claude/workflows/hil-validate.js`
|
||||
Expected: `OK: .claude/workflows/hil-validate.js`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/workflows/hil-validate.js
|
||||
@ -869,7 +877,7 @@ git commit --no-verify -m "feat: add hil-validate workflow (serialized, lock-arb
|
||||
- Consumes: `validate` workflow (Task 6), `hil-validate` workflow (Task 9) via `workflow()` nesting (one level — legal).
|
||||
- Args `{boards: string[], hilBoards?: string[], examples?: string, base?: string, skip?: string[]}`. Returns `{pass, software, hardware}` (consumed by the `/pre-pr` skill, Task 12).
|
||||
|
||||
- [ ] **Step 1: Write the workflow**
|
||||
- [x] **Step 1: Write the workflow**
|
||||
|
||||
Write `.claude/workflows/full-check.js` with exactly this content:
|
||||
|
||||
@ -910,12 +918,12 @@ if (hardware && hardware.locked && hardware.locked.length) {
|
||||
return { pass: !!(hardware && hardware.pass), software, hardware }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Syntax-check**
|
||||
- [x] **Step 2: Syntax-check**
|
||||
|
||||
Run: `bash .claude/workflows/check.sh .claude/workflows/full-check.js`
|
||||
Expected: `OK: .claude/workflows/full-check.js`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/workflows/full-check.js
|
||||
@ -934,7 +942,7 @@ git commit --no-verify -m "feat: add full-check composed workflow"
|
||||
- Args `{pr: number, maxCycles?: number, autoPush?: boolean}`. Must run from a checkout of the PR branch. Returns `{pass, cycles, history, reason?, dryRun?}`.
|
||||
- Push authorization: invoking with `autoPush !== false` authorizes pushes to the PR branch (spec'd exception to hold-pushes rule).
|
||||
|
||||
- [ ] **Step 1: Write the workflow**
|
||||
- [x] **Step 1: Write the workflow**
|
||||
|
||||
Write `.claude/workflows/pr-babysit.js` with exactly this content:
|
||||
|
||||
@ -1152,12 +1160,12 @@ for (let cycle = 1; cycle <= maxCycles; cycle++) {
|
||||
return { pass: false, cycles: maxCycles, history, reason: 'maxCycles reached' }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Syntax-check**
|
||||
- [x] **Step 2: Syntax-check**
|
||||
|
||||
Run: `bash .claude/workflows/check.sh .claude/workflows/pr-babysit.js`
|
||||
Expected: `OK: .claude/workflows/pr-babysit.js`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/workflows/pr-babysit.js
|
||||
@ -1174,7 +1182,7 @@ git commit --no-verify -m "feat: add pr-babysit workflow"
|
||||
**Interfaces:**
|
||||
- Consumes: `full-check` workflow (Task 10), `test/hil/tinyusb.json` (board roster), `hw/bsp/*/family.cmake|family.mk` (family mapping).
|
||||
|
||||
- [ ] **Step 1: Write the skill**
|
||||
- [x] **Step 1: Write the skill**
|
||||
|
||||
Write `.claude/skills/pre-pr/SKILL.md` with exactly this content:
|
||||
|
||||
@ -1223,12 +1231,12 @@ Invoke the Workflow tool:
|
||||
- End with a clear ship / no-ship verdict and what to fix first.
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify structure**
|
||||
- [x] **Step 2: Verify structure**
|
||||
|
||||
Run: `head -4 .claude/skills/pre-pr/SKILL.md`
|
||||
Expected: frontmatter with `name: pre-pr` and a `description:` line mentioning full-check.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/skills/pre-pr/SKILL.md
|
||||
@ -1241,48 +1249,48 @@ git commit --no-verify -m "feat: add /pre-pr skill entry point"
|
||||
|
||||
**Files:** none created; exercises Tasks 1, 6.
|
||||
|
||||
- [ ] **Step 1: Run** — Invoke the Workflow tool: `{ name: 'validate', args: { boards: ['stm32f407disco', 'raspberry_pi_pico'] } }`. (If board deps are missing the builder self-heals via get_deps.)
|
||||
- [ ] **Step 2: Verify** — Returned object has `stages` entries for `unit`, `build:stm32f407disco`, `build:raspberry_pi_pico`, `size`, `pvs`; each `detail` is meaningful; `pass` consistent with stage results. Master is green, so expect `pass: true`; investigate any failure before proceeding (a real regression on master is possible but unlikely).
|
||||
- [ ] **Step 3: Record** — Append the verdict JSON (and rough token usage from /workflows) to `docs/superpowers/plans/2026-07-09-smoke-results.md`; commit with `docs: record validate smoke result`.
|
||||
- [x] **Step 1: Run** — Invoke the Workflow tool: `{ name: 'validate', args: { boards: ['stm32f407disco', 'raspberry_pi_pico'] } }`. (If board deps are missing the builder self-heals via get_deps.)
|
||||
- [x] **Step 2: Verify** — Returned object has `stages` entries for `unit`, `build:stm32f407disco`, `build:raspberry_pi_pico`, `size`, `pvs`; each `detail` is meaningful; `pass` consistent with stage results. Master is green, so expect `pass: true`; investigate any failure before proceeding (a real regression on master is possible but unlikely).
|
||||
- [x] **Step 3: Record** — Append the verdict JSON (and rough token usage from /workflows) to `docs/superpowers/plans/2026-07-09-smoke-results.md`; commit with `docs: record validate smoke result`.
|
||||
|
||||
### Task 14: Smoke test — `fanout-dev` (MAIN SESSION ONLY)
|
||||
|
||||
- [ ] **Step 1: Run** — Invoke Workflow: `{ name: 'fanout-dev', args: { task: 'Add a single comment line `// fanout-dev smoke test — remove me` at the very top of the main dcd_*.c source file in your assigned scope. Make no other change.', items: ['src/portable/raspberrypi/rp2040', 'src/portable/st/stm32_fsdev'], board: { 'src/portable/raspberrypi/rp2040': 'raspberry_pi_pico', 'src/portable/st/stm32_fsdev': 'stm32f072disco' } } }`
|
||||
- [ ] **Step 2: Verify** — Both items return `buildOk: true` and `verifyBuild: true`; `git diff --stat` shows exactly 2 files, 1 insertion each; diffs are clang-format-clean (`git clang-format --diff` reports no changes).
|
||||
- [ ] **Step 3: Revert the smoke edits** — `git checkout -- src/portable/` (verify `git status` clean afterwards).
|
||||
- [ ] **Step 4: Record** — Append results to the smoke-results doc; commit.
|
||||
- [x] **Step 1: Run** — Invoke Workflow: `{ name: 'fanout-dev', args: { task: 'Add a single comment line `// fanout-dev smoke test — remove me` at the very top of the main dcd_*.c source file in your assigned scope. Make no other change.', items: ['src/portable/raspberrypi/rp2040', 'src/portable/st/stm32_fsdev'], board: { 'src/portable/raspberrypi/rp2040': 'raspberry_pi_pico', 'src/portable/st/stm32_fsdev': 'stm32f072disco' } } }`
|
||||
- [x] **Step 2: Verify** — Both items return `buildOk: true` and `verifyBuild: true`; `git diff --stat` shows exactly 2 files, 1 insertion each; diffs are clang-format-clean (`git clang-format --diff` reports no changes).
|
||||
- [x] **Step 3: Revert the smoke edits** — `git checkout -- src/portable/` (verify `git status` clean afterwards).
|
||||
- [x] **Step 4: Record** — Append results to the smoke-results doc; commit.
|
||||
|
||||
### Task 15: Smoke test — `driver-review` (MAIN SESSION ONLY)
|
||||
|
||||
- [ ] **Step 1: Run** — Invoke Workflow: `{ name: 'driver-review', args: { dirs: ['src/portable/renesas/rusb2', 'src/portable/nxp/lpc_ip3511'], question: 'unbounded busy-wait loops polling hardware status bits with no timeout or bail-out (whole-stack freeze risk if hardware never sets the bit)' } }`
|
||||
- [ ] **Step 2: Verify** — Returns only confirmed findings, each carrying `verdict.real: true` with a reasoned `verdict.reason`; spot-check one finding by reading the cited code yourself. (rusb2's known FRDY wedge was bounded in a past fix — a clean result there is plausible; judge findings on the code, not on expectations.)
|
||||
- [ ] **Step 3: Record** — Append results to the smoke-results doc; commit.
|
||||
- [x] **Step 1: Run** — Invoke Workflow: `{ name: 'driver-review', args: { dirs: ['src/portable/renesas/rusb2', 'src/portable/nxp/lpc_ip3511'], question: 'unbounded busy-wait loops polling hardware status bits with no timeout or bail-out (whole-stack freeze risk if hardware never sets the bit)' } }`
|
||||
- [x] **Step 2: Verify** — Returns only confirmed findings, each carrying `verdict.real: true` with a reasoned `verdict.reason`; spot-check one finding by reading the cited code yourself. (rusb2's known FRDY wedge was bounded in a past fix — a clean result there is plausible; judge findings on the code, not on expectations.)
|
||||
- [x] **Step 3: Record** — Append results to the smoke-results doc; commit.
|
||||
|
||||
### Task 16: Smoke test — `hil-validate` + board locks (MAIN SESSION ONLY)
|
||||
|
||||
- [ ] **Step 1: Preconditions** — `ls examples/cmake-build-raspberry_pi_pico/` exists (from Task 13); Tasks 19–20 are done (`test/hil/board_lock.py` exists, `hil_test.py` guard in place); runner is ACTIVE: `systemctl is-active actions.runner.hathach-tinyusb.tinyusb.service` prints `active`.
|
||||
- [ ] **Step 2: Lock-conflict run** — `python3 test/hil/board_lock.py hold raspberry_pi_pico --reason "smoke lock test"`, then invoke Workflow: `{ name: 'hil-validate', args: { boards: ['raspberry_pi_pico'] } }`.
|
||||
- [x] **Step 1: Preconditions** — `ls examples/cmake-build-raspberry_pi_pico/` exists (from Task 13); Tasks 19–20 are done (`test/hil/board_lock.py` exists, `hil_test.py` guard in place); runner is ACTIVE: `systemctl is-active actions.runner.hathach-tinyusb.tinyusb.service` prints `active`.
|
||||
- [x] **Step 2: Lock-conflict run** — `python3 test/hil/board_lock.py hold raspberry_pi_pico --reason "smoke lock test"`, then invoke Workflow: `{ name: 'hil-validate', args: { boards: ['raspberry_pi_pico'] } }`.
|
||||
Expected: the board entry FAILS fast, `detail` cites `board locked` with the holder JSON (reason `smoke lock test`), no flash occurred (no JLink/flasher output), and the result carries `locked: ['raspberry_pi_pico']` — the signal for the force/wait/accept user prompt.
|
||||
- [ ] **Step 3: Force path (lock still held)** — invoke Workflow: `{ name: 'hil-validate', args: { boards: ['raspberry_pi_pico'], force: true } }`.
|
||||
- [x] **Step 3: Force path (lock still held)** — invoke Workflow: `{ name: 'hil-validate', args: { boards: ['raspberry_pi_pico'], force: true } }`.
|
||||
Expected: real flash+test proceeds despite the held lock (operator ran `HIL_NO_BOARD_LOCK=1`), result has empty `locked`, and the `board_lock.py status` holder is still alive afterwards (bypass, not theft).
|
||||
- [ ] **Step 4: Release and normal run** — `python3 test/hil/board_lock.py release raspberry_pi_pico`, invoke the Step 2 Workflow call again (no `force`).
|
||||
- [x] **Step 4: Release and normal run** — `python3 test/hil/board_lock.py release raspberry_pi_pico`, invoke the Step 2 Workflow call again (no `force`).
|
||||
Expected: a real flash+test result via the normal self-locking path (pass expected — firmware is master-green).
|
||||
- [ ] **Step 5: Runner untouched** — `systemctl is-active actions.runner.hathach-tinyusb.tinyusb.service` still prints `active`; the workflow made no stop/start calls.
|
||||
- [ ] **Step 6: Record** — Append all three results to the smoke-results doc; update the memory note `~/.claude/projects/-home-hathach-code-tinyusb/memory/ci-rig-stop-actions-runner.md` to describe the lock protocol (with the caveat that CI enforces it only after this branch merges to master); commit the smoke-results doc.
|
||||
- [x] **Step 5: Runner untouched** — `systemctl is-active actions.runner.hathach-tinyusb.tinyusb.service` still prints `active`; the workflow made no stop/start calls.
|
||||
- [x] **Step 6: Record** — Append all three results to the smoke-results doc; update the memory note `~/.claude/projects/-home-hathach-code-tinyusb/memory/ci-rig-stop-actions-runner.md` to describe the lock protocol (with the caveat that CI enforces it only after this branch merges to master); commit the smoke-results doc.
|
||||
|
||||
### Task 17: Smoke test — `/pre-pr` end-to-end (MAIN SESSION ONLY)
|
||||
|
||||
- [ ] **Step 1: Run** — Invoke the `pre-pr` skill on this branch (its diff is docs + `.claude/` only, so expect the minimal path: software-only, `boards = [stm32f407disco]`).
|
||||
- [ ] **Step 2: Verify** — The skill correctly detects "no C changes", runs `full-check` with the minimal args, and produces the per-stage summary + verdict.
|
||||
- [ ] **Step 3: Record** — Append to the smoke-results doc; commit.
|
||||
- [x] **Step 1: Run** — Invoke the `pre-pr` skill on this branch (its diff is docs + `.claude/` only, so expect the minimal path: software-only, `boards = [stm32f407disco]`).
|
||||
- [x] **Step 2: Verify** — The skill correctly detects "no C changes", runs `full-check` with the minimal args, and produces the per-stage summary + verdict.
|
||||
- [x] **Step 3: Record** — Append to the smoke-results doc; commit.
|
||||
|
||||
### Task 18: Smoke test — `pr-babysit` dry run (MAIN SESSION ONLY)
|
||||
|
||||
- [ ] **Step 1: Pick a target** — `gh pr list --limit 10 --json number,title,headRefName` — choose an open PR with completed CI and at least one bot review comment; check out its branch in a THROWAWAY worktree (`git worktree add /tmp/prsmoke <headRef>` after `git fetch`), and run from there so fix edits can't dirty this branch.
|
||||
- [ ] **Step 2: Run** — Invoke Workflow from that checkout: `{ name: 'pr-babysit', args: { pr: <N>, maxCycles: 1, autoPush: false } }`
|
||||
- [ ] **Step 3: Verify** — Triage classifies CI checks plausibly (compare with `gh pr checks <N>` yourself); each bot finding has a reasoned verdict (spot-check one against the code); result has `dryRun: true` if fixes were produced, and NOTHING was committed or pushed (`git -C /tmp/prsmoke status`, `gh pr view <N> --json comments` unchanged).
|
||||
- [ ] **Step 4: Clean up** — `git worktree remove --force /tmp/prsmoke`.
|
||||
- [ ] **Step 5: Record** — Append to the smoke-results doc; commit.
|
||||
- [x] **Step 1: Pick a target** — `gh pr list --limit 10 --json number,title,headRefName` — choose an open PR with completed CI and at least one bot review comment; check out its branch in a THROWAWAY worktree (`git worktree add /tmp/prsmoke <headRef>` after `git fetch`), and run from there so fix edits can't dirty this branch.
|
||||
- [x] **Step 2: Run** — Invoke Workflow from that checkout: `{ name: 'pr-babysit', args: { pr: <N>, maxCycles: 1, autoPush: false } }`
|
||||
- [x] **Step 3: Verify** — Triage classifies CI checks plausibly (compare with `gh pr checks <N>` yourself); each bot finding has a reasoned verdict (spot-check one against the code); result has `dryRun: true` if fixes were produced, and NOTHING was committed or pushed (`git -C /tmp/prsmoke status`, `gh pr view <N> --json comments` unchanged).
|
||||
- [x] **Step 4: Clean up** — `git worktree remove --force /tmp/prsmoke`.
|
||||
- [x] **Step 5: Record** — Append to the smoke-results doc; commit.
|
||||
|
||||
---
|
||||
|
||||
@ -1294,7 +1302,7 @@ git commit --no-verify -m "feat: add /pre-pr skill entry point"
|
||||
**Interfaces:**
|
||||
- Produces (consumed by `hil-operator` agent, Task 20's guard shares the same lock files): CLI `hold <board...> [--all] [--config PATH] --reason TEXT` / `release <board...> [--all]` / `status`. Lock files: `/tmp/tinyusb-hil-locks/<board>.lock`, exclusive `fcntl.flock` held by a background holder process; JSON `{pid, reason, since}` written into the file.
|
||||
|
||||
- [ ] **Step 1: Write the tool**
|
||||
- [x] **Step 1: Write the tool**
|
||||
|
||||
Write `test/hil/board_lock.py` with exactly this content (then `chmod +x test/hil/board_lock.py`):
|
||||
|
||||
@ -1493,7 +1501,7 @@ if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Test the lock lifecycle**
|
||||
- [x] **Step 2: Test the lock lifecycle**
|
||||
|
||||
Run each line and check the expectation before the next:
|
||||
|
||||
@ -1521,7 +1529,7 @@ python3 test/hil/board_lock.py release fakeboard # -
|
||||
rm -f /tmp/tinyusb-hil-locks/fakeboard.lock
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add test/hil/board_lock.py
|
||||
@ -1539,12 +1547,12 @@ git commit --no-verify -m "feat(hil): add per-board advisory lock tool"
|
||||
- Consumes: the lock files of Task 19 (`/tmp/tinyusb-hil-locks/<board>.lock`).
|
||||
- Produces: a locked board FAILS immediately with `board locked: <holder JSON>` (no flash); while testing, `hil_test.py` holds the board's flock and writes its own holder info (`reason: "hil_test.py"`) so the reverse conflict reports truthfully. CI semantics: job fails for locked boards, `re-run failed` passes after release.
|
||||
|
||||
- [ ] **Step 1: Locate the per-board entry point**
|
||||
- [x] **Step 1: Locate the per-board entry point**
|
||||
|
||||
Run: `grep -n "Pool(\|\.map\|\.imap\|def test_board\|def run_board" test/hil/hil_test.py`
|
||||
Identify the function the `multiprocessing.Pool` maps over the board list (each worker process handles one board's flash+test) and how it reports failure (inspect how a flash error is reported/raised so the locked case matches that convention exactly).
|
||||
|
||||
- [ ] **Step 2: Add the guard**
|
||||
- [x] **Step 2: Add the guard**
|
||||
|
||||
Add near the top of `hil_test.py` (module level, after existing imports — `fcntl` and `os` may need importing):
|
||||
|
||||
@ -1609,7 +1617,7 @@ finally:
|
||||
|
||||
Match indentation and the file's existing style exactly; keep the diff minimal (guard function + one try/finally wrap).
|
||||
|
||||
- [ ] **Step 3: Test the locked path (no hardware touched)**
|
||||
- [x] **Step 3: Test the locked path (no hardware touched)**
|
||||
|
||||
```bash
|
||||
python3 test/hil/board_lock.py hold raspberry_pi_pico --reason "guard test"
|
||||
@ -1618,12 +1626,12 @@ python3 test/hil/board_lock.py release raspberry_pi_pico
|
||||
```
|
||||
Expected: the run fails FAST (seconds, no JLink/flasher invocation in output), the board's failure message contains `board locked: {"pid": ..., "reason": "guard test", ...}`, exit code non-zero. (The unlocked happy path is exercised on real hardware in Task 16.)
|
||||
|
||||
- [ ] **Step 4: Sanity-check no syntax damage**
|
||||
- [x] **Step 4: Sanity-check no syntax damage**
|
||||
|
||||
Run: `python3 -m py_compile test/hil/hil_test.py && echo OK`
|
||||
Expected: `OK`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
- [x] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add test/hil/hil_test.py
|
||||
@ -1637,7 +1645,7 @@ git commit --no-verify -m "feat(hil): fail fast on dev-locked boards instead of
|
||||
**Files:**
|
||||
- Modify: `.claude/skills/hil/SKILL.md` (replace the "Stop the CI runner first" section, currently lines 17–29)
|
||||
|
||||
- [ ] **Step 1: Replace the runner-stop section**
|
||||
- [x] **Step 1: Replace the runner-stop section**
|
||||
|
||||
In `.claude/skills/hil/SKILL.md`, replace the entire section from the heading `## Stop the CI runner first (on \`ci\`)` up to (not including) `## Prerequisites` with:
|
||||
|
||||
@ -1663,12 +1671,12 @@ python3 test/hil/board_lock.py release BOARD [BOARD...]
|
||||
- Caveat until this branch merges to master: CI's checkout of `hil_test.py` does not yet enforce locks — keep dev hardware sessions short and check `gh run list --status in_progress` first.
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
- [x] **Step 2: Verify**
|
||||
|
||||
Run: `grep -n "svc.sh stop" .claude/skills/hil/SKILL.md; grep -c "board_lock.py" .claude/skills/hil/SKILL.md`
|
||||
Expected: no `svc.sh stop` occurrences remain; `board_lock.py` appears ≥ 3 times.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
- [x] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add .claude/skills/hil/SKILL.md
|
||||
|
||||
@ -65,3 +65,14 @@ Stop-gate extra (done this session): board_lock `cmd_hold` holder-signaled succe
|
||||
- pr-babysit: `autoPush` now opt-in (default dry run; `autoPush: true` is the explicit push/comment authorization); RESOLVE_RECIPE paginates reviewThreads (`pageInfo` + cursor); post-push resolve step gained the same issue-comment 404 fallback as the refuted-replies step.
|
||||
- validate: size stage passes `--base-branch <base>`; pvs stage now calls the new `static-analyzer` agent (sonnet, structured `{pass, ga1, ga2, changedFindings[], detail}`).
|
||||
- NEW agent `static-analyzer` (PVS-Studio SAST+MISRA, read-only) — mirrored to the launch-dir registry; registers next session (harness fact 1), so the validate pvs stage is unsmokable until then.
|
||||
|
||||
## Max-effort review fixes (2026-07-13, /code-review opus max: 10 finders → 28 verifiers → sweep)
|
||||
|
||||
- validate.js size stage: dropped the stray trailing `.` that made metrics_compare_base.py exit 2 on every run (regression from the 2026-07-10 batch).
|
||||
- board_lock: flock is now the sole authority — cmd_hold's pid-liveness pre-gate removed (a live-but-moved-on hil_test.py worker pid no longer blocks a free board; verified: hold succeeds over a stale live-pid record, storm still 1-winner-in-10). cmd_release probes the flock before acting: free → clear stale record only; held by `hil_test.py` → refuse (CI mid-test, holder survives — verified); held otherwise → SIGTERM with PermissionError handled. Holder daemon truncates records on SIGTERM; success pipe dup'd above fd 2 (closed-stdio hold now succeeds — was orphan-holder + false failure, repro'd both ways).
|
||||
- hil_test: lock record truncated on per-board release (pool workers outlive flocks); fail-open on OSError now prints a warning; unknown `-b` names exit 1 instead of a silent zero-test green (was exploitable as a false HIL pass through hil-validate); accumulate_report deletes an emptied board row (variant boards no longer leave a blank ghost row — 4-scenario test green).
|
||||
- port-dev.md: `git clang-format -- <files>` scoped to the worker's own files (bare invocation reformatted concurrent siblings' edits in shared checkouts).
|
||||
- pr-babysit: reply skeleton factored into postReplyRecipe (the two copies had already drifted once); cross-cycle repliedIds dedup (issue-comment refutations were re-posted every cycle); args.pr integer + maxCycles >= 1 validation.
|
||||
- All 6 workflows: JSON.parse(args) wrapped so a non-JSON string hits the friendly shape error; driver-review throws on empty dimensions ([] is truthy); hil-validate dead length-clause dropped; retry instruction now `-v -r 1` (diagnosis, not 3 more flake-retries).
|
||||
- Plan doc header replaced with a DO-NOT-EXECUTE historical banner + all 74 boxes checked (re-execution would have recreated pre-static-analyzer files with hooks disabled).
|
||||
- Refuted by verification (left as-is by design): dry-run verify spawns (consumed via history), per-finding verifiers, 4-dim scanners, fanout double-build, validate parallel triple-compile, release --all / hold --config, board_lock import into hil_test (hil_ci.sh ships hil_test.py alone), lock exit code (JSON sidecar already carries board-locked), check.sh scope, effort scatter (harness-forced), local fcntl import (Windows guard).
|
||||
|
||||
@ -59,24 +59,31 @@ log-heavy).
|
||||
CI and dev sessions share the rig concurrently; the actions-runner service is
|
||||
never stopped. Arbitration is per-board kernel flocks in
|
||||
`/tmp/tinyusb-hil-locks/<board>.lock` — auto-released when the holder process
|
||||
dies (stale locks impossible; `/tmp` clears on reboot):
|
||||
dies, with holders truncating their lock-file record on release so records
|
||||
stay truthful (`/tmp` clears on reboot):
|
||||
|
||||
- **`test/hil/board_lock.py`** (new tool): `hold <boards|--all> --reason TEXT`
|
||||
spawns a background holder process flocking each board file (JSON
|
||||
`{pid, reason, since}` written inside for debuggability); `release
|
||||
<boards|--all>` kills holders; `status` lists them. `--all` is required
|
||||
before rig-wide operations (uhubctl power cycling, pci-rebind — bus
|
||||
renumbering affects every board).
|
||||
`{pid, reason, since}` written inside for debuggability); the holder's own
|
||||
LOCK_NB flock is the sole authority — there is deliberately no pid-based
|
||||
pre-check (recorded pids can be stale or recycled). `release <boards|--all>`
|
||||
probes each board's flock: a free lock only gets its stale record cleared;
|
||||
a genuinely held one gets its recorded holder SIGTERMed — unless the holder
|
||||
reason is `hil_test.py` (a CI run mid-test), which release refuses to kill.
|
||||
`status` lists holders. `--all` is required before rig-wide operations
|
||||
(uhubctl power cycling, pci-rebind — bus renumbering affects every board).
|
||||
- **`hil_test.py` guard** (small patch to the per-board worker): take the
|
||||
board's flock non-blocking before flashing and hold it for that board's
|
||||
flash+test; on acquire it writes its own holder info
|
||||
(`{pid, reason: "hil_test.py", since}`) so conflicts report truthfully in
|
||||
both directions. If already held, FAIL the board immediately —
|
||||
`FAILED (board locked: <holder info>)` — no flash, no waiting.
|
||||
both directions, and truncates that record on release (the pool worker
|
||||
outlives the per-board flock). If already held, FAIL the board immediately —
|
||||
`Failed: board locked: <holder info>` — no flash, no waiting.
|
||||
The CI job fails visibly for exactly those boards and `re-run failed`
|
||||
passes once the lock is released (`build.yml` already retries the HIL step
|
||||
once, absorbing short dev sessions). Guard defaults to proceeding if the
|
||||
lock dir is absent/odd.
|
||||
once, absorbing short dev sessions). Guard proceeds unlocked — with a
|
||||
printed warning — if the lock dir is unusable, and `-b` names absent from
|
||||
the config are a hard error rather than a silent zero-test green run.
|
||||
- **Re-entrancy rule:** dev sessions do NOT pre-hold boards they are about to
|
||||
run `hil_test.py` on (it self-locks; pre-holding deadlocks it).
|
||||
`board_lock.py hold` is for hardware work outside `hil_test.py` only.
|
||||
|
||||
Reference in New Issue
Block a user