[build-system] requires = ["setuptools>=77", "wheel"] build-backend = "setuptools.build_meta" [project] name = "agentic-hil" version = "0.25.1.dev0" description = "Hardware-in-the-loop MCP server for AI coding agents: develop firmware on the real board behind your debug probe, flashing, resetting and driving UART and CAN against it. The run on the hardware is the gate that decides the work is done, and a pytest plugin runs the same gate in CI." readme = "README.md" requires-python = ">=3.10" license = "Apache-2.0" authors = [ { name = "Hannes Pauli", email = "mail@hannes-pauli.de" } ] keywords = [ "mcp", "model-context-protocol", "embedded", "hardware-in-the-loop", "openocd", "firmware", "serial", "stlink", "debugger", "agentic-hil", "agentic", "agentic_hil", "stm32", "mcp-server", "uart", "can", "can-bus", "pyocd", "pytest", "hardware-testing", "firmware-testing" ] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Framework :: Pytest", "Intended Audience :: Developers", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.15", "Topic :: Software Development :: Embedded Systems", "Topic :: Software Development :: Testing", "Topic :: System :: Hardware" ] dependencies = [ "jsonschema>=4.22", "PyYAML>=6.0", "pyserial>=3.5", "python-can>=4.4", "tomli>=2; python_version < '3.11'" ] [project.optional-dependencies] # python-can is a dependency of the distribution itself, so an install or # upgrade line that names no extra keeps CAN support. The extra stays declared # because receipts, install scripts and documents name `agentic-hil[can]`. can = ["python-can>=4.4"] pyocd = ["pyocd>=0.36"] esptool = ["esptool>=5,<6"] dev = [ "build>=1.2", "pytest>=8.0", # Required rather than a convenience: `addopts` below asks for `--dist # loadgroup`, so an environment without this plugin refuses every session with # "unrecognized arguments: --dist". The worker count stays off `addopts`, so a # bare `pytest` is still one process and `pytest -n auto` is still the parallel # invocation, which CONTRIBUTING.md documents. "pytest-xdist>=3.6", "ruff>=0.6", "twine>=5.0" ] [project.urls] Homepage = "https://github.com/agentic-hil/agentic-hil" Documentation = "https://agentic-hil.github.io/docs/" Repository = "https://github.com/agentic-hil/agentic-hil.git" Changelog = "https://github.com/agentic-hil/agentic-hil/blob/master/CHANGELOG.md" Issues = "https://github.com/agentic-hil/agentic-hil/issues" [project.scripts] agentic-hil = "agentic_hil.cli:entrypoint" [project.entry-points.pytest11] agentic_hil = "agentic_hil.pytest_plugin" [tool.setuptools.packages.find] where = ["src"] [tool.setuptools.package-data] agentic_hil = [ "schemas/*.json", "templates/*.json", "skills/agentic-hil/SKILL.md" ] [tool.pytest.ini_options] testpaths = ["tests"] # The evaluation harness under evals/ is not a package that gets installed, so # the repository root has to be importable. "python -m pytest" adds it, a bare # "pytest" does not, which is how CI collected zero evaluation tests. pythonpath = ["."] python_files = ["test_*.py"] # `--dist loadgroup` is the scheduler, not the worker count: without `-n` it # starts nothing, and with it every test carrying an `xdist_group` mark runs on # one worker instead of being spread by the default balancing. It is configured # here rather than passed by each caller so that a bare `pytest -n auto`, the CI # legs, tools/ci_linux.py and the container invocation all schedule the same way, # and a caller that forgets it cannot exist. What needs it today: the two tests # in tests/test_install_scripts.py that drive step 5 of install.ps1 against the # machine's real process table, one planting a process named like an agent CLI # and the other asserting that none is running. addopts = "--strict-markers --dist loadgroup" # The two tiers that need something the hosted matrix has not got. Registered # here because `--strict-markers` above refuses a marker nothing declared, and # named here rather than only in the directories they live in so `pytest -m # "not container and not bench"` is a selection anybody can write without # reading the tree first. Both tiers also skip themselves unless their own # environment variable says the run is where they belong, so a bare `pytest` in # a checkout collects them and runs none of them. markers = [ "container: needs the real uv, OpenOCD and /proc, and no hardware; runs in tools/container/Dockerfile with AGENTIC_HIL_CONTAINER_TESTS=1", "bench: needs a debug probe and the board behind it; runs on a bench with AGENTIC_HIL_BENCH=1", "without_device_group: the bench stage with the probe's groups withheld; runs alone in tools/bench_in_container.py --without-device-group", "wheelhouse: installs the product from the package index tools/bench/Dockerfile builds into the bench image; runs only where that index is", "usb_uart: drives the board over the USB-UART adapter wired beside the probe; runs only where AGENTIC_HIL_BENCH_USB_UART names it", "power_supply: drives the bench power supply that feeds the board, in the workspace AGENTIC_HIL_BENCH_POWER_SUPPLY names; runs only where one is named", "reenumerates_the_probe: can make the in-circuit debugger remove its serial port and create it again; on a bench it runs after every other test of the tier", "tdqs_model: scores the real tools/list with the real model through the Claude Code command line; deselected unless AGENTIC_HIL_TDQS_MODEL=1", "cubeclt: drives the board through the STM32CubeCLT tree AGENTIC_HIL_BENCH_CUBECLT names (tools/bench_in_container.py --cubeclt-archive builds one into the bench image); runs only where one is named" ] [tool.ruff] line-length = 120 target-version = "py310" [tool.ruff.lint] select = ["E", "F", "I", "UP", "B", "SIM"] ignore = ["E501"] # The type check covers the modules the safety boundary is made of # (docs/security-design.md): the configuration and its writes, the contracts, # artifact staging, the tool dispatch and its coordination, device access, # redaction, the reports, and the MCP and stdio transports. The rest of the # package is read for the types these modules import from it and is not # reported on. CI runs a bare `mypy` from requirements/typecheck.txt. # # These modules switch between POSIX and Windows code on `os.name`, which mypy # does not narrow on, so the check is pinned to one platform: Linux, where CI # runs it. The POSIX branches are checked as written; the few Windows-only # calls carry `type: ignore[attr-defined]` on their own line, and # `warn_unused_ignores` reports one that is no longer needed. [tool.mypy] python_version = "3.10" platform = "linux" mypy_path = "src" files = [ "src/agentic_hil/artifacts.py", "src/agentic_hil/backends/rs3005p.py", "src/agentic_hil/bench.py", "src/agentic_hil/config.py", "src/agentic_hil/configstate.py", "src/agentic_hil/configwrite.py", "src/agentic_hil/contracts.py", "src/agentic_hil/coordination.py", "src/agentic_hil/device_access.py", "src/agentic_hil/mcp.py", "src/agentic_hil/power_supply.py", "src/agentic_hil/redact.py", "src/agentic_hil/report.py", "src/agentic_hil/stdio.py", "src/agentic_hil/tools.py", "src/agentic_hil/types.py" ] follow_imports = "silent" check_untyped_defs = true warn_unused_ignores = true warn_redundant_casts = true