From 363175c3bff1f9cfbf40568e062ddba3bf000f47 Mon Sep 17 00:00:00 2001 From: "nominal-skills[bot]" <269248221+nominal-skills[bot]@users.noreply.github.com> Date: Sat, 29 Aug 2026 04:26:08 +0000 Subject: [PATCH] chore(skills): sync validate-driver-hardware --- .claude/skills/validate-driver-hardware | 1 + .../skills/validate-driver-hardware/SKILL.md | 252 ------------------ skills-lock.json | 10 +- 3 files changed, 6 insertions(+), 257 deletions(-) create mode 120000 .claude/skills/validate-driver-hardware delete mode 100644 .claude/skills/validate-driver-hardware/SKILL.md diff --git a/.claude/skills/validate-driver-hardware b/.claude/skills/validate-driver-hardware new file mode 120000 index 00000000..9b2fba80 --- /dev/null +++ b/.claude/skills/validate-driver-hardware @@ -0,0 +1 @@ +../../.agents/skills/validate-driver-hardware \ No newline at end of file diff --git a/.claude/skills/validate-driver-hardware/SKILL.md b/.claude/skills/validate-driver-hardware/SKILL.md deleted file mode 100644 index c1ee9314..00000000 --- a/.claude/skills/validate-driver-hardware/SKILL.md +++ /dev/null @@ -1,252 +0,0 @@ ---- -name: validate-driver-hardware -description: >- - Write and run a standalone hardware-validation script for an instro driver - against the real device, then iterate on the driver until every supported - method passes. Use after authoring a driver (the add-instrument-driver skill - hands off here) or when asked to "validate on hardware", "smoke-test - this driver against the real instrument", or similar. Produces a - self-contained, runnable test script under tests/// and a - triaged pass/fail report; self-corrects driver bugs found along the way. ---- - -# Validate a driver against real hardware - -This skill takes an already-authored driver and proves it works against the -physical instrument. It writes a **standalone, runnable** validation script that -exercises *every* method the driver implements, runs it against the connected -device, triages the results, and **iterates on the driver** to fix real bugs the -hardware surfaces. It is the hardware counterpart to `add-instrument-driver`'s -mocked unit tests. - -Borrow the *style* of the runnable example scripts in `examples/` — especially -`examples/modbus/labjack_t4_loopback_test.py` (per-check `OK`/`FAIL`, -accumulated failures, final summary, `try/finally` close). - -## Step 1 — Confirm hardware availability and gather connection details - -This skill is only useful with the physical device attached. **Ask the user -first**, and do not write or run anything until you have: - -1. **Do you have the instrument connected right now?** If no, stop — offer to - write the script anyway (with placeholder config) so they can run it later, - but make clear nothing will be validated until hardware is present. -2. **Device identifier / connection string.** The exact value the transport - needs: a VISA resource (`USB0::0x...::INSTR`, `TCPIP0::192.168.1.5::INSTR`, - `ASRL3::INSTR`), a `host:port` for Modbus/TCP, a serial port, etc. Match the - transport the driver actually uses. -3. **Vendor, model, and exact unit** (e.g. "Siglent SDS1104X-E"). Confirm it is - in the family the driver targets. -4. **Channel count / channel map.** How many channels, and what is physically - wired to each one. -5. **Stimulus and wiring.** What signal/load/DUT is present, on which channel, - and its known properties (e.g. "LabJack T4 DAC0 → CH1, 100 Hz sine, ~0–2.5 V - unipolar"). This is what makes value checks meaningful — capture it precisely, - including limitations (e.g. a unipolar source never crosses 0 V, so a 0 V - trigger won't fire). Ask for loopback wiring where the category needs it - (DAQ analog/digital loopback, I2C target address, etc.). -6. **Safety constraints.** For sourcing instruments (PSU, eload, DAQ AO, - relays), confirm safe limits before any output is enabled and what state to - leave the device in. - -Use `AskUserQuestion` when several of these are unknown — don't guess a VISA -resource or a wiring map. - -## Step 2 — Enumerate the full driver surface to cover - -The script must exercise **everything the driver implements** — that is the -point of hardware validation. Build the list from code, not memory: - -- Read the category base `instro//.py` (named `driver.py` - in some categories, e.g. `instro/scope/driver.py`, or living under - `packages/instro-unstable/` for in-development categories) for the required + - optional method set. -- Read the concrete driver module and list every method it actually overrides. - Skip only the ones that raise `FeatureNotSupportedError`/`NotImplementedError`. -- Read the matching HAL (`InstroPSU`, `InstroDMM`, `InstroScope`, `InstroDAQ`, - …) to learn the **public** call surface and the return types. Methods return - `Measurement` / `Command` (see `instro/lib/types.py`); unwrap with `.latest`, - `.values`, or `.channel_data[...]` exactly as the example scripts and the - HAL's own signatures dictate. Don't invent accessors. - -Group the surface into validation steps: connection/sync, per-channel setting -roundtrips (set → readback within tolerance), coupling/mode enums, queries -(sample rate, status), acquisition/read/fetch, built-in measurements, run/stop, -and file ops (screenshots, settings save/load). Cover each implemented method at -least once. - -## Step 3 — Write the validation script - -Place it where the category's hardware tests live; default to -`tests///test___hardware.py` (mirrors -`tests/scope/siglent/`). Requirements: - -- **Mark it `@pytest.mark.hardware`** so `just test` deselects it (the repo sets - `addopts = "-m 'not hardware'"`), AND make it runnable standalone via a - `main()` and `if __name__ == "__main__": sys.exit(main())`. The user runs it - with `uv run python `. -- **Module-level config block** at the top — connection string, channel map, - stimulus description, tolerances, and optional `EXPECTED_*` constants — each - clearly marked to edit before running. Fill these from Step 1. -- **No publishers.** Construct the HAL with `publishers=None`; the script pushes - data to no external backend. -- **Continue through all steps**, accumulating failures rather than aborting on - the first — you want the full picture in one run (the loopback example does - this). Print `OK`/`FAIL` per step and a final summary with a non-zero exit code - if anything failed. -- **`try/finally`**: always `close()` the instrument and restore a safe state - (outputs off, attenuation/level reset) in `finally`. -- **Two check tiers**, following the existing scope hardware test's philosophy: - *structural sanity* (finite, positive, `max > min`, expected length) is always - asserted; *strict value* checks (measured frequency ≈ expected) run only when - the corresponding `EXPECTED_*` constant is set, since they depend on the - stimulus. - -Skeleton to adapt (category-agnostic; fill in the real driver, HAL, and steps): - -```python -"""Hardware validation for via . Self-contained; no publishers. - -Wiring / stimulus: - - -Run: - uv run python tests///test___hardware.py -""" - -import sys -import pytest - -from instro. import -from instro..drivers import # or unstable path - -RESOURCE = "" # <-- edit before running -SIGNAL_CHANNEL = 1 -# EXPECTED_FREQUENCY_HZ = None # set to enable the strict value check - - -def _make_hal() -> : - hal = (name="hw_validate", driver=(RESOURCE), - num_channels=, publishers=None) - hal.open() - return hal - - -def _run(name, fn, failures): - try: - fn() - print(f" [OK] {name}") - except Exception as exc: # noqa: BLE001 - report, don't abort - print(f" [FAIL] {name}: {exc}") - failures.append((name, exc)) - - -def run_all() -> list: - hal = _make_hal() - failures: list = [] - try: - # one _run(...) per implemented driver method / capability - ... - finally: - # restore safe state, then close - hal.close() - return failures - - -@pytest.mark.hardware -def test__hardware(): - failures = run_all() - assert not failures, f"{len(failures)} hardware check(s) failed: {failures}" - - -def main() -> int: - failures = run_all() - print(f"\n{'PASSED' if not failures else f'FAILED ({len(failures)})'}") - return 1 if failures else 0 - - -if __name__ == "__main__": - sys.exit(main()) -``` - -## Step 4 — Run it against the hardware - -Delegate execution to the **`hardware-test-runner` subagent** -(`.claude/agents/hardware-test-runner.md`). The run is noisy — verbose VISA/ -transport I/O and long tracebacks — and the subagent keeps that out of this -conversation, returning a structured triage: overall pass/fail, per-step status, -trimmed error excerpts, and a hypothesis classifying each failure as -**driver-bug**, **script/config**, or **hardware/wiring**. - -Give it the script path and the exact run command -(`uv run python `), plus the Step 1 stimulus notes so it can tell a real -driver bug from "no signal on CH1". - -If hardware tests are quick and quiet for this device, you may run the script -inline instead — use judgment. - -## Step 5 — Triage and self-correct - -For each failure, classify before touching code (this is the same discipline as -diagnosing a real bug — wrong command/format/parse vs. wrong test config vs. no -signal): - -- **Driver bug** — wrong wire command, bad value formatting (e.g. sending - `10.0` where the device wants `10`), mis-parsed response, buffer desync on a - binary read. **Fix the driver, and only the driver.** Confine every edit to the - concrete driver module and its accompanying tests (`tests//...`). - **Never edit shared/core library code** — `instro/lib/` (transports like - `VisaDriver`, `instro/lib/types.py`), the category base class - (`DriverBase`), or the HAL. Those are out of scope for a single-driver - validation. If a fix looks like it needs a transport capability the public API - doesn't expose (e.g. a length-aware binary read, or disabling the read - terminator for a raw block), **use the VISA escape hatch** — reach the - underlying pyvisa resource from inside the driver via `self._visa._inst` while - holding `self._visa.lock()`, restoring any attribute you change (e.g. - `read_termination`) in a `finally`. If the cause genuinely cannot be fixed - without changing core lib, **stop and flag it for the user**; do not edit lib - yourself. After fixing the driver, update the mocked unit tests if the wire - command changed, and re-run `just check` and `just test` to confirm no - regression. -- **Script/config** — wrong channel, timebase showing too few cycles, a trigger - level the signal never crosses, tolerance too tight. **Fix the script**, not - the driver. -- **Hardware/wiring** — no stimulus, wrong probe attenuation, unipolar source vs - bipolar expectation. **Report to the user**; don't paper over it in code. - -Re-run via the subagent after each change. Loop until the script passes or the -only remaining failures are genuine hardware/wiring issues the user must resolve. -Make the smallest change that fixes the cause; don't refactor the driver beyond -the bug. - -## Step 6 — Wrap up - -- Ensure `just check` and `just test` still pass (mocked tests green, types and - lint clean) — the hardware test is deselected there by design. -- Summarize for the user: which methods were validated, what was fixed in the - driver (with the wire-level before/after), any steps that depend on stimulus - the user should tune via `EXPECTED_*`, and any unresolved hardware/wiring - issues. -- The script ships in the same branch/PR as the driver. It is a `hardware`-marked - test, so it won't run in CI — note that in the PR description and that the - driver was confirmed against a real unit. - -## Anti-patterns to refuse - -- **No data-publishing backend in the validation script** — no publisher, - client, or dataset identifiers. If the user wants streaming/telemetry, that is - a separate example, not this script. -- **Don't fix the driver to match a broken test.** If the test config or wiring - is wrong, fix that instead — the driver's wire behavior must stay correct. -- **Don't edit shared/core library code.** A driver fix must land only in the - driver module and its tests — never in `instro/lib/` (incl. `VisaDriver` and - other transports), `DriverBase`, or the HAL. Need a capability the - transport API lacks? Use the VISA escape hatch (`self._visa._inst` under - `self._visa.lock()`) from within the driver, or flag the gap to the user — do - not modify lib. -- **Don't silently skip methods.** If a method can't be validated with the - available stimulus, print it as `SKIPPED` with the reason, not omit it. -- **Don't leave the instrument in an unsafe state.** Restore outputs/levels in - `finally`. -- **Don't invent expected values.** Strict value checks come from the user's - stated stimulus; otherwise assert structure only. diff --git a/skills-lock.json b/skills-lock.json index 1b5d0966..991b4796 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -6,16 +6,16 @@ "derivedPaths": [ ".claude/skills/add-instrument-driver/SKILL.md" ], - "hash": "59a916e18aeb6a913b77c2c1de0ef2776d91c98a2df6240f80266205ce7f324d", - "lastSynced": "2026-06-12T04:47:22.109Z" + "hash": "5993575ad1de4f09c829b5f084d87f3ddb1e17b111db8c0bd46940653f0b2612", + "lastSynced": "2026-08-29T04:26:03.417Z" }, "validate-driver-hardware": { "canonicalPath": ".agents/skills/validate-driver-hardware/SKILL.md", "derivedPaths": [ - ".claude/skills/validate-driver-hardware/SKILL.md" + ".claude/skills/validate-driver-hardware" ], - "hash": "bf2567faf7ee026527b41b5aa73439deaa2e49057dbef0d73748fbb100c26e5f", - "lastSynced": "2026-06-12T04:47:22.110Z" + "hash": "bdf79db08acdfd52baebe4c908f8cfa0402697cc473950e0d0455ce878c84566", + "lastSynced": "2026-08-29T04:26:03.417Z" } } }