diff options
Diffstat (limited to 'docs')
3 files changed, 110 insertions, 84 deletions
diff --git a/docs/superpowers/plans/2026-07-09-claude-agents-workflows.md b/docs/superpowers/plans/2026-07-09-claude-agents-workflows.md index 32efe1a2e..02e064fc4 100644 --- a/docs/superpowers/plans/2026-07-09-claude-agents-workflows.md +++ b/docs/superpowers/plans/2026-07-09-claude-agents-workflows.md @@ -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 diff --git a/docs/superpowers/plans/2026-07-09-smoke-results.md b/docs/superpowers/plans/2026-07-09-smoke-results.md index 7fb26a347..a15638247 100644 --- a/docs/superpowers/plans/2026-07-09-smoke-results.md +++ b/docs/superpowers/plans/2026-07-09-smoke-results.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). diff --git a/docs/superpowers/specs/2026-07-09-claude-agents-workflows-design.md b/docs/superpowers/specs/2026-07-09-claude-agents-workflows-design.md index f37929439..63788720c 100644 --- a/docs/superpowers/specs/2026-07-09-claude-agents-workflows-design.md +++ b/docs/superpowers/specs/2026-07-09-claude-agents-workflows-design.md @@ -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. |
